Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b3904ce
Preserve canonical names in V10 schema roundtrips
Shubham8287 Oct 25, 2026
0000e90
Use typed hashes for IDC outbox signatures
Shubham8287 Oct 25, 2026
88d1d9d
deserialization test
Shubham8287 Oct 27, 2026
4aae432
update deserializer to check with both canonical and accessor names
Shubham8287 Oct 27, 2026
6149619
Validate IDC outbox callbacks and preserve raw schema round trips
Shubham8287 Oct 24, 2026
028d9ae
Generate Rust IDC receiver bindings and reducer metadata
Shubham8287 Oct 24, 2026
0b66f6c
Wire Rust receiver binding generation into spacetime generate
Shubham8287 Oct 24, 2026
d2f9396
Scope generated receiver types by dependency
Shubham8287 Oct 24, 2026
d7c17b3
Test CLI-generated receiver types from two dependencies end to end
Shubham8287 Oct 24, 2026
5853743
address comments
Shubham8287 Oct 27, 2026
4e7dd6e
update test
Shubham8287 Oct 27, 2026
5870f22
comments
Shubham8287 Oct 27, 2026
4dc0fa6
use qualified types
Shubham8287 Oct 27, 2026
6b0169e
Add Rust outbox declarations and runtime registration
Shubham8287 Oct 24, 2026
2e8dca2
Map outbox columns to receiver parameters with param attributes
Shubham8287 Oct 24, 2026
934b0b9
Restrict mutation APIs on IDC outbox tables
Shubham8287 Oct 24, 2026
e5a4c52
Accept receiver dependencies in publish configuration
Shubham8287 Oct 24, 2026
1879f69
Test generated Rust IDC bindings and remote delivery end to end
Shubham8287 Oct 24, 2026
bc9b759
Restore two-dependency IDC delivery smoketest
Shubham8287 Oct 24, 2026
744559c
lint
Shubham8287 Oct 25, 2026
a0aaf65
Give copied smoke test fixtures unique Cargo package names
Shubham8287 Oct 26, 2026
9d87560
Avoid stale receiver reads when polling detailed IDC delivery
Shubham8287 Oct 26, 2026
3fd3bc7
Resolve self-referential outbox signatures from the local module
Shubham8287 Oct 24, 2026
f8c0e5a
Test self-referential IDC delivery with no case conversion
Shubham8287 Oct 24, 2026
df96d50
Use receiver handle metadata for local and generated outboxes
Shubham8287 Oct 25, 2026
6e20884
Fix formatting in local outbox compile test
Shubham8287 Oct 26, 2026
dbd7486
remove schema dependency
Shubham8287 Oct 26, 2026
8e78f4e
lint
Shubham8287 Oct 26, 2026
b72a7bf
snap
Shubham8287 Oct 26, 2026
32401dd
local outbox
Shubham8287 Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ mod sym {
symbol!(repr);
symbol!(sats);
symbol!(scheduled);
symbol!(outbox);
symbol!(on_result);
symbol!(param);
symbol!(target);
symbol!(unique);
symbol!(update);
symbol!(default);
Expand Down Expand Up @@ -229,7 +233,10 @@ fn derive_table_helper_attr() -> Attribute {
///
/// Provides helper attributes for `#[spacetimedb::table]`, so that we don't get unknown attribute errors.
#[doc(hidden)]
#[proc_macro_derive(__TableHelper, attributes(sats, unique, auto_inc, primary_key, index, default))]
#[proc_macro_derive(
__TableHelper,
attributes(sats, unique, auto_inc, primary_key, target, index, default, param)
)]
pub fn table_helper(input: StdTokenStream) -> StdTokenStream {
schema_type(input)
}
Expand Down
102 changes: 101 additions & 1 deletion crates/bindings-macro/src/reducer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::sym;
use crate::util::{check_duplicate, check_duplicate_msg, ident_to_litstr, match_meta, native_test_utils_registration};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use quote::{format_ident, quote, quote_spanned};
use syn::ext::IdentExt;
use syn::parse::Parser as _;
use syn::spanned::Spanned;
use syn::{FnArg, Ident, ItemFn, LitStr, PatType};
Expand All @@ -10,6 +11,7 @@ use syn::{FnArg, Ident, ItemFn, LitStr, PatType};
pub(crate) struct ReducerArgs {
name: Option<LitStr>,
lifecycle: Option<LifecycleReducer>,
on_result: Option<syn::Path>,
}

enum LifecycleReducer {
Expand Down Expand Up @@ -51,6 +53,12 @@ impl ReducerArgs {
check_duplicate(&args.name, &meta)?;
args.name = Some(meta.value()?.parse()?);
}
sym::on_result => {
check_duplicate(&args.on_result, &meta)?;
let content;
syn::parenthesized!(content in meta.input);
args.on_result = Some(content.parse()?);
}
});
Ok(())
})
Expand Down Expand Up @@ -101,12 +109,26 @@ pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn
assert_only_lifetime_generics(original_function, "reducers")?;

let lifecycle = args.lifecycle.iter().filter_map(|lc| lc.to_lifecycle_value());
let on_result_outbox = args.on_result.as_ref().map(|path| {
let accessor = &path.segments.last().unwrap().ident;
quote!(
const ON_RESULT_OUTBOX: Option<&'static str> =
Some(spacetimedb::rt::on_result_outbox(#path::#accessor));
)
});
let on_result_typecheck = args.on_result.as_ref().map(|path| {
let accessor = &path.segments.last().unwrap().ident;
quote! {
spacetimedb::rt::on_result_typecheck(#func_name, #path::#accessor);
}
});

let typed_args = extract_typed_args(original_function)?;

let explicit_name = args.name.as_ref();

let generate_explicit_names = generate_explicit_names_impl(&reducer_name.value(), func_name, explicit_name);
let outbox_typecheck = generate_local_outbox_typecheck(func_name, &typed_args);

// Extract all function parameter names.
let opt_arg_names = typed_args.iter().map(|arg| {
Expand Down Expand Up @@ -156,6 +178,7 @@ pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn
#(let _ = <#first_arg_ty as spacetimedb::rt::ReducerContextArg>::_ITEM;)*
#(let _ = <#rest_arg_tys as spacetimedb::rt::ReducerArg>::_ITEM;)*
#(let _ = <#ret_ty as spacetimedb::rt::IntoReducerResult>::into_result;)*
#on_result_typecheck
}
};
impl #func_name {
Expand All @@ -170,14 +193,91 @@ pub(crate) fn reducer_impl(args: ReducerArgs, original_function: &ItemFn) -> syn
type FnKind = spacetimedb::rt::FnKindReducer;
const NAME: &'static str = #reducer_name;
#(const LIFECYCLE: Option<spacetimedb::rt::LifecycleReducer> = Some(#lifecycle);)*
#on_result_outbox
const ARG_NAMES: &'static [Option<&'static str>] = &[#(#opt_arg_names),*];
const INVOKE: Self::Invoke = #func_name::invoke;
}

impl spacetimedb::rt::OutboxReceiver for #func_name {
type Identity = spacetimedb::Identity;
const NAME: &'static str = <Self as spacetimedb::rt::FnInfo>::NAME;
const SIGNATURE_HASH: Option<&'static str> = None;
}

#generate_explicit_names
#outbox_typecheck
})
}

fn generate_local_outbox_typecheck(func_name: &Ident, args: &[&PatType]) -> TokenStream {
let macro_name = format_ident!("__spacetimedb_typecheck_outbox_{}", func_name.unraw());
let mut helpers = Vec::new();
let mut checks = Vec::new();
for arg in args.iter().skip(1) {
let syn::Pat::Ident(pattern) = arg.pat.as_ref() else {
checks.push(quote!(compile_error!(
"local outbox reducer parameters must have names"
)));
continue;
};
let param = pattern.ident.unraw();
let ty = &arg.ty;
let lookup = format_ident!("{}_{}", macro_name, param);
let reject_duplicate = format_ident!("{}_reject_duplicate", lookup);
helpers.push(quote! {
#[doc(hidden)]
macro_rules! #reject_duplicate {
($module:ident;) => {};
($module:ident; ($index:literal, $field:ident : $column_ty:ty as #param), $($rest:tt)*) => {
compile_error!(concat!("outbox has multiple columns matching reducer parameter `", stringify!(#param), "`"));
};
($module:ident; ($index:literal, $field:ident : $column_ty:ty as $other:ident), $($rest:tt)*) => {
$module::#reject_duplicate!($module; $($rest)*);
};
}
#[doc(hidden)]
#[allow(unused_imports)]
pub(crate) use #reject_duplicate;

#[doc(hidden)]
macro_rules! #lookup {
($module:ident;) => {{
compile_error!(concat!("outbox has no column matching reducer parameter `", stringify!(#param), "`"));
0u16
}};
($module:ident; ($index:literal, $field:ident : $column_ty:ty as #param), $($rest:tt)*) => {{
#[allow(unused_imports)]
use $module::*;
let _ = |x: $column_ty| { let _: #ty = x; };
$module::#reject_duplicate!($module; $($rest)*);
$index
}};
($module:ident; ($index:literal, $field:ident : $column_ty:ty as $other:ident), $($rest:tt)*) => {
$module::#lookup!($module; $($rest)*)
};
}
#[doc(hidden)]
#[allow(unused_imports)]
pub(crate) use #lookup;
});
checks.push(quote!(__outbox_receiver::#lookup!(__outbox_receiver; $($columns)*)));
}
quote! {
#(#helpers)*
#[doc(hidden)]
macro_rules! #macro_name {
($module:path, { $($columns:tt)* }) => {{
#[allow(unused_imports)]
use $module as __outbox_receiver;
&[#(#checks),*]
}};
}
#[doc(hidden)]
#[allow(unused_imports)]
pub(crate) use #macro_name;
}
}

pub(crate) fn generate_explicit_names_impl(
func_name: &str,
func_handle: &Ident,
Expand Down
Loading
Loading