From f2401e652eee82114e95876309dcbf73c2651810 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 12 Feb 2026 13:43:38 -0500 Subject: [PATCH 01/24] Add CanBeLookupTable trait, event table support in query-builder/bindings-macro, and codegen - Add `event` attribute to #[table] macro for declaring event tables - Add CanBeLookupTable trait (non-event tables implement it) - Generate CanBeLookupTable impls in Rust codegen - Update physical plan and query-builder for event table support - Update module-test with example event table - Update codegen snapshots --- crates/bindings-macro/src/lib.rs | 1 + crates/bindings-macro/src/table.rs | 22 + crates/bindings/src/rt.rs | 3 +- crates/bindings/src/table.rs | 1 + crates/codegen/src/rust.rs | 222 ++- .../snapshots/codegen__codegen_csharp.snap | 166 ++ .../snapshots/codegen__codegen_rust.snap | 1497 +++++++++++++---- .../codegen__codegen_typescript.snap | 63 + crates/physical-plan/src/plan.rs | 24 + crates/query-builder/src/join.rs | 10 +- crates/query-builder/src/lib.rs | 2 + crates/query-builder/src/table.rs | 5 + modules/module-test/src/lib.rs | 11 + 13 files changed, 1598 insertions(+), 429 deletions(-) diff --git a/crates/bindings-macro/src/lib.rs b/crates/bindings-macro/src/lib.rs index 52b13f00267..50da833bae1 100644 --- a/crates/bindings-macro/src/lib.rs +++ b/crates/bindings-macro/src/lib.rs @@ -61,6 +61,7 @@ mod sym { symbol!(unique); symbol!(update); symbol!(default); + symbol!(event); symbol!(u8); symbol!(i8); diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index 9d8143fce6f..b93f907ec90 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -19,6 +19,7 @@ pub(crate) struct TableArgs { scheduled: Option, name: Ident, indices: Vec, + event: Option, } enum TableAccess { @@ -71,6 +72,7 @@ impl TableArgs { let mut scheduled = None; let mut name = None; let mut indices = Vec::new(); + let mut event = None; syn::meta::parser(|meta| { match_meta!(match meta { sym::public => { @@ -91,6 +93,10 @@ impl TableArgs { check_duplicate(&scheduled, &meta)?; scheduled = Some(ScheduledArg::parse_meta(meta)?); } + sym::event => { + check_duplicate(&event, &meta)?; + event = Some(meta.path.span()); + } }); Ok(()) }) @@ -107,6 +113,7 @@ impl TableArgs { scheduled, name, indices, + event, }) } } @@ -852,6 +859,18 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R ); let table_access = args.access.iter().map(|acc| acc.to_value()); + let is_event = args.event.iter().map(|_| { + quote!( + const IS_EVENT: bool = true; + ) + }); + let can_be_lookup_impl = if args.event.is_none() { + quote! { + impl spacetimedb::query_builder::CanBeLookupTable for #original_struct_ident {} + } + } else { + quote! {} + }; let unique_col_ids = unique_columns.iter().map(|col| col.index); let primary_col_id = primary_key_column.clone().into_iter().map(|col| col.index); let sequence_col_ids = sequenced_columns.iter().map(|col| col.index); @@ -977,6 +996,7 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R const TABLE_NAME: &'static str = #table_name; // the default value if not specified is Private #(const TABLE_ACCESS: spacetimedb::table::TableAccess = #table_access;)* + #(#is_event)* const UNIQUE_COLUMNS: &'static [u16] = &[#(#unique_col_ids),*]; const INDEXES: &'static [spacetimedb::table::IndexDesc<'static>] = &[#(#index_descs),*]; #(const PRIMARY_KEY: Option = Some(#primary_col_id);)* @@ -1088,6 +1108,8 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R } } + #can_be_lookup_impl + }; let table_query_handle_def = quote! { diff --git a/crates/bindings/src/rt.rs b/crates/bindings/src/rt.rs index 4c2ec69254d..d2d690d14e1 100644 --- a/crates/bindings/src/rt.rs +++ b/crates/bindings/src/rt.rs @@ -740,7 +740,8 @@ pub fn register_table() { .inner .build_table(T::TABLE_NAME, product_type_ref) .with_type(TableType::User) - .with_access(T::TABLE_ACCESS); + .with_access(T::TABLE_ACCESS) + .with_event(T::IS_EVENT); for &col in T::UNIQUE_COLUMNS { table = table.with_unique_constraint(col); diff --git a/crates/bindings/src/table.rs b/crates/bindings/src/table.rs index 394d2bfdf8d..850f31e625a 100644 --- a/crates/bindings/src/table.rs +++ b/crates/bindings/src/table.rs @@ -128,6 +128,7 @@ pub trait TableInternal: Sized { const PRIMARY_KEY: Option = None; const SEQUENCES: &'static [u16]; const SCHEDULE: Option> = None; + const IS_EVENT: bool = false; /// Returns the ID of this table. fn table_id() -> TableId; diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index a55abbbd54d..cef3a821a14 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -258,7 +258,7 @@ impl<'ctx> __sdk::TableWithPrimaryKey for {table_handle}<'ctx> {{ " #[doc(hidden)] pub(super) fn parse_table_update( - raw_updates: __ws::v2::TableUpdate, + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, ) -> __sdk::Result<__sdk::TableUpdate<{row_type}>> {{ __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {{ __sdk::InternalError::failed_parse( @@ -344,6 +344,7 @@ pub(super) fn parse_table_update( let reducer_name = reducer.name.deref(); let func_name = reducer_function_name(reducer); + let set_reducer_flags_trait = reducer_flags_trait_name(reducer); let args_type = function_args_type_name(&reducer.name); let enum_variant_name = reducer_variant_name(&reducer.name); @@ -367,8 +368,11 @@ pub(super) fn parse_table_update( out.newline(); + let callback_id = reducer_callback_id_name(&reducer.name); + let FormattedArglist { arglist_no_delimiters, + arg_type_refs, arg_names, } = FormattedArglist::for_arguments(module, &reducer.params_for_generate.elements); @@ -413,6 +417,8 @@ impl __sdk::InModule for {args_type} {{ type Module = super::RemoteModule; }} +pub struct {callback_id}(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `{reducer_name}`. /// @@ -422,36 +428,68 @@ pub trait {func_name} {{ /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`{func_name}:{func_name}_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_{func_name}`] callbacks. + fn {func_name}(&self, {arglist_no_delimiters}) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `{reducer_name}`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`{callback_id}`] can be passed to [`Self::remove_on_{func_name}`] + /// to cancel the callback. + fn on_{func_name}(&self, callback: impl FnMut(&super::ReducerEventContext, {arg_type_refs}) + Send + 'static) -> {callback_id}; + /// Cancel a callback previously registered by [`Self::on_{func_name}`], + /// causing it not to run in the future. + fn remove_on_{func_name}(&self, callback: {callback_id}); +}} + +impl {func_name} for super::RemoteReducers {{ fn {func_name}(&self, {arglist_no_delimiters}) -> __sdk::Result<()> {{ - self.{func_name}_then({arg_names} |_, _| {{}}) + self.imp.call_reducer({reducer_name:?}, {args_type} {{ {arg_names} }}) + }} + fn on_{func_name}( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, {arg_type_refs}) + Send + 'static, + ) -> {callback_id} {{ + {callback_id}(self.imp.on_reducer( + {reducer_name:?}, + Box::new(move |ctx: &super::ReducerEventContext| {{ + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext {{ + event: __sdk::ReducerEvent {{ + reducer: super::Reducer::{enum_variant_name} {{ + {arg_names} + }}, + .. + }}, + .. + }} = ctx else {{ unreachable!() }}; + callback(ctx, {arg_names}) + }}), + )) + }} + fn remove_on_{func_name}(&self, callback: {callback_id}) {{ + self.imp.remove_on_reducer({reducer_name:?}, callback.0) }} +}} - /// Request that the remote module invoke the reducer `{reducer_name}` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `{reducer_name}`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait {set_reducer_flags_trait} {{ + /// Set the call-reducer flags for the reducer `{reducer_name}` to `flags`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn {func_name}_then( - &self, - {arglist_no_delimiters} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// This type is currently unstable and may be removed without a major version bump. + fn {func_name}(&self, flags: __ws::CallReducerFlags); }} -impl {func_name} for super::RemoteReducers {{ - fn {func_name}_then( - &self, - {arglist_no_delimiters} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> {{ - self.imp.invoke_reducer_with_callback({args_type} {{ {arg_names} }}, callback) +impl {set_reducer_flags_trait} for super::SetReducerFlags {{ + fn {func_name}(&self, flags: __ws::CallReducerFlags) {{ + self.imp.set_call_reducer_flags({reducer_name:?}, flags); }} }} " @@ -823,6 +861,11 @@ struct FormattedArglist { /// /// Always carries a trailing comma, unless it's zero elements. arglist_no_delimiters: String, + /// The argument types as `&ty, &ty, &ty,`, + /// for use as the params in a function/closure type. + /// + /// Always carries a trailing comma, unless it's zero elements. + arg_type_refs: String, /// The argument names as `ident, ident, ident,`, /// for passing to function call and struct literal expressions. /// @@ -836,8 +879,13 @@ impl FormattedArglist { write_arglist_no_delimiters(module, &mut arglist_no_delimiters, params, None) .expect("Writing to a String failed... huh?"); + let mut arg_type_refs = String::new(); let mut arg_names = String::new(); - for (arg_ident, _) in params { + for (arg_ident, arg_ty) in params { + arg_type_refs += "&"; + write_type(module, &mut arg_type_refs, arg_ty).expect("Writing to a String failed... huh?"); + arg_type_refs += ", "; + let arg_name = arg_ident.deref().to_case(Case::Snake); arg_names += &arg_name; arg_names += ", "; @@ -845,6 +893,7 @@ impl FormattedArglist { Self { arglist_no_delimiters, + arg_type_refs, arg_names, } } @@ -1050,6 +1099,10 @@ fn reducer_variant_name(reducer_name: &ReducerName) -> String { reducer_name.deref().to_case(Case::Pascal) } +fn reducer_callback_id_name(reducer_name: &ReducerName) -> String { + reducer_name.deref().to_case(Case::Pascal) + "CallbackId" +} + fn reducer_module_name(reducer_name: &ReducerName) -> String { reducer_name.deref().to_case(Case::Snake) + "_reducer" } @@ -1070,6 +1123,10 @@ fn procedure_function_with_callback_name(procedure: &ProcedureDef) -> String { procedure_function_name(procedure) + "_then" } +fn reducer_flags_trait_name(reducer: &ReducerDef) -> String { + format!("set_flags_for_{}", reducer_function_name(reducer)) +} + /// Iterate over all of the Rust `mod`s for types, reducers, views, and tables in the `module`. fn iter_module_names(module: &ModuleDef, visibility: CodegenVisibility) -> impl Iterator + '_ { itertools::chain!( @@ -1107,7 +1164,12 @@ fn print_module_reexports(module: &ModuleDef, visibility: CodegenVisibility, out for reducer in iter_reducers(module, visibility) { let mod_name = reducer_module_name(&reducer.name); let reducer_trait_name = reducer_function_name(reducer); - writeln!(out, "pub use {mod_name}::{reducer_trait_name};"); + let flags_trait_name = reducer_flags_trait_name(reducer); + let callback_id_name = reducer_callback_id_name(&reducer.name); + writeln!( + out, + "pub use {mod_name}::{{{reducer_trait_name}, {flags_trait_name}, {callback_id_name}}};" + ); } for procedure in iter_procedures(module, visibility) { let mod_name = procedure_module_name(&procedure.name); @@ -1188,12 +1250,34 @@ impl __sdk::InModule for Reducer {{ }, "}\n", ); - writeln!(out, "#[allow(clippy::clone_on_copy)]"); + }, + "}\n", + ); + + out.delimited_block( + "impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer {", + |out| { + writeln!(out, "type Error = __sdk::Error;"); + // We define an "args struct" for each reducer in `generate_reducer`. + // This is not user-facing, and is not exported past the "root" `mod.rs`; + // it is an internal helper for serialization and deserialization. + // We actually want to ser/de instances of `enum Reducer`, but: + // + // - `Reducer` will have struct-like variants, which SATS ser/de does not support. + // - The WS format does not contain a BSATN-serialized `Reducer` instance; + // it holds the reducer name or ID separately from the argument bytes. + // We could work up some magic with `DeserializeSeed` + // and/or custom `Serializer` and `Deserializer` types + // to account for this, but it's much easier to just use an intermediate struct per reducer. + // + // As such, we deserialize from the `value.args` bytes into that "args struct," + // then convert it into a `Reducer` variant via `Into::into`, + // which we also implement in `generate_reducer`. out.delimited_block( - "fn args_bsatn(&self) -> Result, __sats::bsatn::EncodeError> {", + "fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result {", |out| { out.delimited_block( - "match self {", + "match &value.reducer_name[..] {", |out| { for reducer in iter_reducers(module, visibility) { write!(out, "Reducer::{}", reducer_variant_name(&reducer.name)); @@ -1218,34 +1302,26 @@ impl __sdk::InModule for Reducer {{ write!( out, - " => __sats::bsatn::to_vec(&{}::{}", + "{:?} => Ok(__sdk::parse_reducer_args::<{}::{}>({:?}, &value.args)?.into()),", + reducer.name.deref(), reducer_module_name(&reducer.name), - function_args_type_name(&reducer.name) - ); - out.delimited_block( - " {", - |out| { - for (ident, _) in &reducer.params_for_generate.elements { - let field = ident.deref().to_case(Case::Snake); - writeln!(out, "{field}: {field}.clone(),"); - } - }, - "}),\n", + function_args_type_name(&reducer.name), + reducer.name.deref(), ); } - // Write a catch-all pattern to handle the case where the module defines zero reducers, - // 'cause references are always considered inhabited, - // even references to uninhabited types. - writeln!(out, "_ => unreachable!(),"); + writeln!( + out, + "unknown => Err(__sdk::InternalError::unknown_name(\"reducer\", unknown, \"ReducerCallInfo\").into()),", + ); }, "}\n", - ); + ) }, "}\n", ); }, "}\n", - ); + ) } fn print_db_update_defn(module: &ModuleDef, visibility: CodegenVisibility, out: &mut Indenter) { @@ -1271,11 +1347,11 @@ fn print_db_update_defn(module: &ModuleDef, visibility: CodegenVisibility, out: out.delimited_block( " -impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { +impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { type Error = __sdk::Error; - fn try_from(raw: __ws::v2::TransactionUpdate) -> Result { + fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result { let mut db_update = DbUpdate::default(); - for table_update in __sdk::transaction_update_iter_table_updates(raw) { + for table_update in raw.tables { match &table_update.table_name[..] { ", |out| { @@ -1498,7 +1574,7 @@ type ErrorContext = ErrorContext; type Reducer = Reducer; type DbView = RemoteTables; type Reducers = RemoteReducers; -type Procedures = RemoteProcedures; +type SetReducerFlags = SetReducerFlags; type DbUpdate = DbUpdate; type AppliedDiff<'r> = AppliedDiff<'r>; type SubscriptionHandle = SubscriptionHandle; @@ -1559,6 +1635,20 @@ impl __sdk::InModule for RemoteProcedures {{ type Module = RemoteModule; }} +#[doc(hidden)] +/// The `set_reducer_flags` field of [`DbConnection`], +/// with methods provided by extension traits for each reducer defined by the module. +/// Each method sets the flags for the reducer with the same name. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub struct SetReducerFlags {{ + imp: __sdk::DbContextImpl, +}} + +impl __sdk::InModule for SetReducerFlags {{ + type Module = RemoteModule; +}} + /// The `db` field of [`EventContext`] and [`DbConnection`], /// with methods provided by extension traits for each table defined by the module. pub struct RemoteTables {{ @@ -1591,6 +1681,11 @@ pub struct DbConnection {{ /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, #[doc(hidden)] + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, @@ -1606,6 +1701,7 @@ impl __sdk::DbContext for DbConnection {{ type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView {{ &self.db @@ -1616,6 +1712,9 @@ impl __sdk::DbContext for DbConnection {{ fn procedures(&self) -> &Self::Procedures {{ &self.procedures }} + fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{ + &self.set_reducer_flags + }} fn is_active(&self) -> bool {{ self.imp.is_active() @@ -1719,6 +1818,7 @@ impl __sdk::DbConnection for DbConnection {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, procedures: RemoteProcedures {{ imp: imp.clone() }}, + set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, imp, }} }} @@ -1770,11 +1870,13 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle {{ pub trait RemoteDbContext: __sdk::DbContext< DbView = RemoteTables, Reducers = RemoteReducers, + SetReducerFlags = SetReducerFlags, SubscriptionBuilder = __sdk::SubscriptionBuilder, > {{}} impl, >> RemoteDbContext for Ctx {{}} ", @@ -1856,6 +1958,11 @@ pub struct {struct_and_trait_name} {{ pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -1872,6 +1979,7 @@ impl __sdk::AbstractEventContext for {struct_and_trait_name} {{ Self {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, + set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, procedures: RemoteProcedures {{ imp: imp.clone() }}, event, imp, @@ -1891,6 +1999,11 @@ pub struct {struct_and_trait_name} {{ pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, imp: __sdk::DbContextImpl, @@ -1906,6 +2019,7 @@ impl __sdk::AbstractEventContext for {struct_and_trait_name} {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, procedures: RemoteProcedures {{ imp: imp.clone() }}, + set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, imp, }} }} @@ -1925,6 +2039,7 @@ impl __sdk::DbContext for {struct_and_trait_name} {{ type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView {{ &self.db @@ -1935,6 +2050,9 @@ impl __sdk::DbContext for {struct_and_trait_name} {{ fn procedures(&self) -> &Self::Procedures {{ &self.procedures }} + fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{ + &self.set_reducer_flags + }} fn is_active(&self) -> bool {{ self.imp.is_active() diff --git a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap index a16c3532c81..47cce3c6043 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap @@ -683,6 +683,83 @@ namespace SpacetimeDB } } ''' +"Reducers/EmitEvent.g.cs" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB +{ + public sealed partial class RemoteReducers : RemoteBase + { + public delegate void EmitEventHandler(ReducerEventContext ctx, string name, ulong value); + public event EmitEventHandler? OnEmitEvent; + + public void EmitEvent(string name, ulong value) + { + conn.InternalCallReducer(new Reducer.EmitEvent(name, value)); + } + + public bool InvokeEmitEvent(ReducerEventContext ctx, Reducer.EmitEvent args) + { + if (OnEmitEvent == null) + { + if (InternalOnUnhandledReducerError != null) + { + switch(ctx.Event.Status) + { + case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; + case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; + } + } + return false; + } + OnEmitEvent( + ctx, + args.Name, + args.Value + ); + return true; + } + } + + public abstract partial class Reducer + { + [SpacetimeDB.Type] + [DataContract] + public sealed partial class EmitEvent : Reducer, IReducerArgs + { + [DataMember(Name = "name")] + public string Name; + [DataMember(Name = "value")] + public ulong Value; + + public EmitEvent( + string Name, + ulong Value + ) + { + this.Name = Name; + this.Value = Value; + } + + public EmitEvent() + { + this.Name = ""; + } + + string IReducerArgs.ReducerName => "emit_event"; + } + } + +} +''' "Reducers/ListOverAge.g.cs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. @@ -1090,6 +1167,7 @@ namespace SpacetimeDB public RemoteTables(DbConnection conn) { AddTable(LoggedOutPlayer = new(conn)); + AddTable(MyEvent = new(conn)); AddTable(MyPlayer = new(conn)); AddTable(Person = new(conn)); AddTable(Player = new(conn)); @@ -1590,6 +1668,7 @@ namespace SpacetimeDB public sealed class From { public global::SpacetimeDB.Table LoggedOutPlayer() => new("logged_out_player", new LoggedOutPlayerCols("logged_out_player"), new LoggedOutPlayerIxCols("logged_out_player")); + public global::SpacetimeDB.Table MyEvent() => new("my_event", new MyEventCols("my_event"), new MyEventIxCols("my_event")); public global::SpacetimeDB.Table MyPlayer() => new("my_player", new MyPlayerCols("my_player"), new MyPlayerIxCols("my_player")); public global::SpacetimeDB.Table Person() => new("person", new PersonCols("person"), new PersonIxCols("person")); public global::SpacetimeDB.Table Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player")); @@ -1681,6 +1760,7 @@ namespace SpacetimeDB Reducer.AssertCallerIdentityIsModuleIdentity args => Reducers.InvokeAssertCallerIdentityIsModuleIdentity(eventContext, args), Reducer.DeletePlayer args => Reducers.InvokeDeletePlayer(eventContext, args), Reducer.DeletePlayersByName args => Reducers.InvokeDeletePlayersByName(eventContext, args), + Reducer.EmitEvent args => Reducers.InvokeEmitEvent(eventContext, args), Reducer.ListOverAge args => Reducers.InvokeListOverAge(eventContext, args), Reducer.LogModuleIdentity args => Reducers.InvokeLogModuleIdentity(eventContext, args), Reducer.QueryPrivate args => Reducers.InvokeQueryPrivate(eventContext, args), @@ -1789,6 +1869,55 @@ namespace SpacetimeDB } } ''' +"Tables/MyEvent.g.cs" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.BSATN; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB +{ + public sealed partial class RemoteTables + { + public sealed class MyEventHandle : RemoteTableHandle + { + protected override string RemoteTableName => "my_event"; + + internal MyEventHandle(DbConnection conn) : base(conn) + { + } + } + + public readonly MyEventHandle MyEvent; + } + + public sealed class MyEventCols + { + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col Value { get; } + + public MyEventCols(string tableName) + { + Name = new global::SpacetimeDB.Col(tableName, "name"); + Value = new global::SpacetimeDB.Col(tableName, "value"); + } + } + + public sealed class MyEventIxCols + { + + public MyEventIxCols(string tableName) + { + } + } +} +''' "Tables/MyPlayer.g.cs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. @@ -2185,6 +2314,43 @@ namespace SpacetimeDB } } ''' +"Types/MyEvent.g.cs" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB +{ + [SpacetimeDB.Type] + [DataContract] + public sealed partial class MyEvent + { + [DataMember(Name = "name")] + public string Name; + [DataMember(Name = "value")] + public ulong Value; + + public MyEvent( + string Name, + ulong Value + ) + { + this.Name = Name; + this.Value = Value; + } + + public MyEvent() + { + this.Name = ""; + } + } +} +''' "Types/NamespaceTestC.g.cs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. diff --git a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap index c4889a17bd8..3c286b8b2fa 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap @@ -33,6 +33,8 @@ impl __sdk::InModule for AddPlayerArgs { type Module = super::RemoteModule; } +pub struct AddPlayerCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `add_player`. /// @@ -42,39 +44,70 @@ pub trait add_player { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`add_player:add_player_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_add_player`] callbacks. fn add_player(&self, name: String, -) -> __sdk::Result<()> { - self.add_player_then(name, |_, _| {}) - } - - /// Request that the remote module invoke the reducer `add_player` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `add_player`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn add_player_then( - &self, - name: String, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`AddPlayerCallbackId`] can be passed to [`Self::remove_on_add_player`] + /// to cancel the callback. + fn on_add_player(&self, callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static) -> AddPlayerCallbackId; + /// Cancel a callback previously registered by [`Self::on_add_player`], + /// causing it not to run in the future. + fn remove_on_add_player(&self, callback: AddPlayerCallbackId); } impl add_player for super::RemoteReducers { - fn add_player_then( + fn add_player(&self, name: String, +) -> __sdk::Result<()> { + self.imp.call_reducer("add_player", AddPlayerArgs { name, }) + } + fn on_add_player( &self, - name: String, + mut callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static, + ) -> AddPlayerCallbackId { + AddPlayerCallbackId(self.imp.on_reducer( + "add_player", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::AddPlayer { + name, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, name, ) + }), + )) + } + fn remove_on_add_player(&self, callback: AddPlayerCallbackId) { + self.imp.remove_on_reducer("add_player", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `add_player`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_add_player { + /// Set the call-reducer flags for the reducer `add_player` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn add_player(&self, flags: __ws::CallReducerFlags); +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(AddPlayerArgs { name, }, callback) +impl set_flags_for_add_player for super::SetReducerFlags { + fn add_player(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("add_player", flags); } } @@ -110,6 +143,8 @@ impl __sdk::InModule for AddPrivateArgs { type Module = super::RemoteModule; } +pub struct AddPrivateCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `add_private`. /// @@ -119,39 +154,70 @@ pub trait add_private { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`add_private:add_private_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_add_private`] callbacks. fn add_private(&self, name: String, -) -> __sdk::Result<()> { - self.add_private_then(name, |_, _| {}) - } - - /// Request that the remote module invoke the reducer `add_private` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `add_private`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn add_private_then( - &self, - name: String, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`AddPrivateCallbackId`] can be passed to [`Self::remove_on_add_private`] + /// to cancel the callback. + fn on_add_private(&self, callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static) -> AddPrivateCallbackId; + /// Cancel a callback previously registered by [`Self::on_add_private`], + /// causing it not to run in the future. + fn remove_on_add_private(&self, callback: AddPrivateCallbackId); } impl add_private for super::RemoteReducers { - fn add_private_then( + fn add_private(&self, name: String, +) -> __sdk::Result<()> { + self.imp.call_reducer("add_private", AddPrivateArgs { name, }) + } + fn on_add_private( &self, - name: String, + mut callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static, + ) -> AddPrivateCallbackId { + AddPrivateCallbackId(self.imp.on_reducer( + "add_private", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::AddPrivate { + name, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, name, ) + }), + )) + } + fn remove_on_add_private(&self, callback: AddPrivateCallbackId) { + self.imp.remove_on_reducer("add_private", callback.0) + } +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(AddPrivateArgs { name, }, callback) +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `add_private`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_add_private { + /// Set the call-reducer flags for the reducer `add_private` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn add_private(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_add_private for super::SetReducerFlags { + fn add_private(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("add_private", flags); } } @@ -189,6 +255,8 @@ impl __sdk::InModule for AddArgs { type Module = super::RemoteModule; } +pub struct AddCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `add`. /// @@ -198,42 +266,72 @@ pub trait add { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`add:add_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_add`] callbacks. fn add(&self, name: String, age: u8, -) -> __sdk::Result<()> { - self.add_then(name, age, |_, _| {}) - } - - /// Request that the remote module invoke the reducer `add` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `add`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn add_then( - &self, - name: String, -age: u8, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`AddCallbackId`] can be passed to [`Self::remove_on_add`] + /// to cancel the callback. + fn on_add(&self, callback: impl FnMut(&super::ReducerEventContext, &String, &u8, ) + Send + 'static) -> AddCallbackId; + /// Cancel a callback previously registered by [`Self::on_add`], + /// causing it not to run in the future. + fn remove_on_add(&self, callback: AddCallbackId); } impl add for super::RemoteReducers { - fn add_then( - &self, - name: String, + fn add(&self, name: String, age: u8, +) -> __sdk::Result<()> { + self.imp.call_reducer("add", AddArgs { name, age, }) + } + fn on_add( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &String, &u8, ) + Send + 'static, + ) -> AddCallbackId { + AddCallbackId(self.imp.on_reducer( + "add", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::Add { + name, age, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, name, age, ) + }), + )) + } + fn remove_on_add(&self, callback: AddCallbackId) { + self.imp.remove_on_reducer("add", callback.0) + } +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(AddArgs { name, age, }, callback) +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `add`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_add { + /// Set the call-reducer flags for the reducer `add` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn add(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_add for super::SetReducerFlags { + fn add(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("add", flags); } } @@ -266,6 +364,8 @@ impl __sdk::InModule for AssertCallerIdentityIsModuleIdentityArgs { type Module = super::RemoteModule; } +pub struct AssertCallerIdentityIsModuleIdentityCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `assert_caller_identity_is_module_identity`. /// @@ -275,36 +375,68 @@ pub trait assert_caller_identity_is_module_identity { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`assert_caller_identity_is_module_identity:assert_caller_identity_is_module_identity_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_assert_caller_identity_is_module_identity`] callbacks. + fn assert_caller_identity_is_module_identity(&self, ) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `assert_caller_identity_is_module_identity`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`AssertCallerIdentityIsModuleIdentityCallbackId`] can be passed to [`Self::remove_on_assert_caller_identity_is_module_identity`] + /// to cancel the callback. + fn on_assert_caller_identity_is_module_identity(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> AssertCallerIdentityIsModuleIdentityCallbackId; + /// Cancel a callback previously registered by [`Self::on_assert_caller_identity_is_module_identity`], + /// causing it not to run in the future. + fn remove_on_assert_caller_identity_is_module_identity(&self, callback: AssertCallerIdentityIsModuleIdentityCallbackId); +} + +impl assert_caller_identity_is_module_identity for super::RemoteReducers { fn assert_caller_identity_is_module_identity(&self, ) -> __sdk::Result<()> { - self.assert_caller_identity_is_module_identity_then( |_, _| {}) + self.imp.call_reducer("assert_caller_identity_is_module_identity", AssertCallerIdentityIsModuleIdentityArgs { }) + } + fn on_assert_caller_identity_is_module_identity( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, + ) -> AssertCallerIdentityIsModuleIdentityCallbackId { + AssertCallerIdentityIsModuleIdentityCallbackId(self.imp.on_reducer( + "assert_caller_identity_is_module_identity", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::AssertCallerIdentityIsModuleIdentity { + + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, ) + }), + )) + } + fn remove_on_assert_caller_identity_is_module_identity(&self, callback: AssertCallerIdentityIsModuleIdentityCallbackId) { + self.imp.remove_on_reducer("assert_caller_identity_is_module_identity", callback.0) } +} - /// Request that the remote module invoke the reducer `assert_caller_identity_is_module_identity` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `assert_caller_identity_is_module_identity`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_assert_caller_identity_is_module_identity { + /// Set the call-reducer flags for the reducer `assert_caller_identity_is_module_identity` to `flags`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn assert_caller_identity_is_module_identity_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// This type is currently unstable and may be removed without a major version bump. + fn assert_caller_identity_is_module_identity(&self, flags: __ws::CallReducerFlags); } -impl assert_caller_identity_is_module_identity for super::RemoteReducers { - fn assert_caller_identity_is_module_identity_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(AssertCallerIdentityIsModuleIdentityArgs { }, callback) +impl set_flags_for_assert_caller_identity_is_module_identity for super::SetReducerFlags { + fn assert_caller_identity_is_module_identity(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("assert_caller_identity_is_module_identity", flags); } } @@ -365,6 +497,8 @@ impl __sdk::InModule for DeletePlayerArgs { type Module = super::RemoteModule; } +pub struct DeletePlayerCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `delete_player`. /// @@ -374,39 +508,70 @@ pub trait delete_player { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`delete_player:delete_player_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_delete_player`] callbacks. fn delete_player(&self, id: u64, -) -> __sdk::Result<()> { - self.delete_player_then(id, |_, _| {}) - } - - /// Request that the remote module invoke the reducer `delete_player` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_player`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn delete_player_then( - &self, - id: u64, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`DeletePlayerCallbackId`] can be passed to [`Self::remove_on_delete_player`] + /// to cancel the callback. + fn on_delete_player(&self, callback: impl FnMut(&super::ReducerEventContext, &u64, ) + Send + 'static) -> DeletePlayerCallbackId; + /// Cancel a callback previously registered by [`Self::on_delete_player`], + /// causing it not to run in the future. + fn remove_on_delete_player(&self, callback: DeletePlayerCallbackId); } impl delete_player for super::RemoteReducers { - fn delete_player_then( + fn delete_player(&self, id: u64, +) -> __sdk::Result<()> { + self.imp.call_reducer("delete_player", DeletePlayerArgs { id, }) + } + fn on_delete_player( &self, - id: u64, + mut callback: impl FnMut(&super::ReducerEventContext, &u64, ) + Send + 'static, + ) -> DeletePlayerCallbackId { + DeletePlayerCallbackId(self.imp.on_reducer( + "delete_player", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::DeletePlayer { + id, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, id, ) + }), + )) + } + fn remove_on_delete_player(&self, callback: DeletePlayerCallbackId) { + self.imp.remove_on_reducer("delete_player", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `delete_player`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_delete_player { + /// Set the call-reducer flags for the reducer `delete_player` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn delete_player(&self, flags: __ws::CallReducerFlags); +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(DeletePlayerArgs { id, }, callback) +impl set_flags_for_delete_player for super::SetReducerFlags { + fn delete_player(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("delete_player", flags); } } @@ -442,6 +607,8 @@ impl __sdk::InModule for DeletePlayersByNameArgs { type Module = super::RemoteModule; } +pub struct DeletePlayersByNameCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `delete_players_by_name`. /// @@ -451,39 +618,184 @@ pub trait delete_players_by_name { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`delete_players_by_name:delete_players_by_name_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_delete_players_by_name`] callbacks. + fn delete_players_by_name(&self, name: String, +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_players_by_name`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`DeletePlayersByNameCallbackId`] can be passed to [`Self::remove_on_delete_players_by_name`] + /// to cancel the callback. + fn on_delete_players_by_name(&self, callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static) -> DeletePlayersByNameCallbackId; + /// Cancel a callback previously registered by [`Self::on_delete_players_by_name`], + /// causing it not to run in the future. + fn remove_on_delete_players_by_name(&self, callback: DeletePlayersByNameCallbackId); +} + +impl delete_players_by_name for super::RemoteReducers { fn delete_players_by_name(&self, name: String, ) -> __sdk::Result<()> { - self.delete_players_by_name_then(name, |_, _| {}) + self.imp.call_reducer("delete_players_by_name", DeletePlayersByNameArgs { name, }) + } + fn on_delete_players_by_name( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static, + ) -> DeletePlayersByNameCallbackId { + DeletePlayersByNameCallbackId(self.imp.on_reducer( + "delete_players_by_name", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::DeletePlayersByName { + name, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, name, ) + }), + )) + } + fn remove_on_delete_players_by_name(&self, callback: DeletePlayersByNameCallbackId) { + self.imp.remove_on_reducer("delete_players_by_name", callback.0) } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `delete_players_by_name`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_delete_players_by_name { + /// Set the call-reducer flags for the reducer `delete_players_by_name` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn delete_players_by_name(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_delete_players_by_name for super::SetReducerFlags { + fn delete_players_by_name(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("delete_players_by_name", flags); + } +} + +''' +"emit_event_reducer.rs" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{ + self as __sdk, + __lib, + __sats, + __ws, +}; + + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct EmitEventArgs { + pub name: String, + pub value: u64, +} - /// Request that the remote module invoke the reducer `delete_players_by_name` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +impl From for super::Reducer { + fn from(args: EmitEventArgs) -> Self { + Self::EmitEvent { + name: args.name, + value: args.value, +} +} +} + +impl __sdk::InModule for EmitEventArgs { + type Module = super::RemoteModule; +} + +pub struct EmitEventCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `emit_event`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait emit_event { + /// Request that the remote module invoke the reducer `emit_event` to run as soon as possible. /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn delete_players_by_name_then( - &self, - name: String, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// and its status can be observed by listening for [`Self::on_emit_event`] callbacks. + fn emit_event(&self, name: String, +value: u64, +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `emit_event`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`EmitEventCallbackId`] can be passed to [`Self::remove_on_emit_event`] + /// to cancel the callback. + fn on_emit_event(&self, callback: impl FnMut(&super::ReducerEventContext, &String, &u64, ) + Send + 'static) -> EmitEventCallbackId; + /// Cancel a callback previously registered by [`Self::on_emit_event`], + /// causing it not to run in the future. + fn remove_on_emit_event(&self, callback: EmitEventCallbackId); } -impl delete_players_by_name for super::RemoteReducers { - fn delete_players_by_name_then( +impl emit_event for super::RemoteReducers { + fn emit_event(&self, name: String, +value: u64, +) -> __sdk::Result<()> { + self.imp.call_reducer("emit_event", EmitEventArgs { name, value, }) + } + fn on_emit_event( &self, - name: String, + mut callback: impl FnMut(&super::ReducerEventContext, &String, &u64, ) + Send + 'static, + ) -> EmitEventCallbackId { + EmitEventCallbackId(self.imp.on_reducer( + "emit_event", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::EmitEvent { + name, value, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, name, value, ) + }), + )) + } + fn remove_on_emit_event(&self, callback: EmitEventCallbackId) { + self.imp.remove_on_reducer("emit_event", callback.0) + } +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(DeletePlayersByNameArgs { name, }, callback) +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `emit_event`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_emit_event { + /// Set the call-reducer flags for the reducer `emit_event` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn emit_event(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_emit_event for super::SetReducerFlags { + fn emit_event(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("emit_event", flags); } } @@ -666,6 +978,8 @@ impl __sdk::InModule for ListOverAgeArgs { type Module = super::RemoteModule; } +pub struct ListOverAgeCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `list_over_age`. /// @@ -675,39 +989,70 @@ pub trait list_over_age { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`list_over_age:list_over_age_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_list_over_age`] callbacks. fn list_over_age(&self, age: u8, -) -> __sdk::Result<()> { - self.list_over_age_then(age, |_, _| {}) - } - - /// Request that the remote module invoke the reducer `list_over_age` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `list_over_age`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn list_over_age_then( - &self, - age: u8, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`ListOverAgeCallbackId`] can be passed to [`Self::remove_on_list_over_age`] + /// to cancel the callback. + fn on_list_over_age(&self, callback: impl FnMut(&super::ReducerEventContext, &u8, ) + Send + 'static) -> ListOverAgeCallbackId; + /// Cancel a callback previously registered by [`Self::on_list_over_age`], + /// causing it not to run in the future. + fn remove_on_list_over_age(&self, callback: ListOverAgeCallbackId); } impl list_over_age for super::RemoteReducers { - fn list_over_age_then( + fn list_over_age(&self, age: u8, +) -> __sdk::Result<()> { + self.imp.call_reducer("list_over_age", ListOverAgeArgs { age, }) + } + fn on_list_over_age( &self, - age: u8, + mut callback: impl FnMut(&super::ReducerEventContext, &u8, ) + Send + 'static, + ) -> ListOverAgeCallbackId { + ListOverAgeCallbackId(self.imp.on_reducer( + "list_over_age", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::ListOverAge { + age, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, age, ) + }), + )) + } + fn remove_on_list_over_age(&self, callback: ListOverAgeCallbackId) { + self.imp.remove_on_reducer("list_over_age", callback.0) + } +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(ListOverAgeArgs { age, }, callback) +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `list_over_age`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_list_over_age { + /// Set the call-reducer flags for the reducer `list_over_age` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn list_over_age(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_list_over_age for super::SetReducerFlags { + fn list_over_age(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("list_over_age", flags); } } @@ -740,6 +1085,8 @@ impl __sdk::InModule for LogModuleIdentityArgs { type Module = super::RemoteModule; } +pub struct LogModuleIdentityCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `log_module_identity`. /// @@ -749,36 +1096,68 @@ pub trait log_module_identity { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`log_module_identity:log_module_identity_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_log_module_identity`] callbacks. + fn log_module_identity(&self, ) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `log_module_identity`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`LogModuleIdentityCallbackId`] can be passed to [`Self::remove_on_log_module_identity`] + /// to cancel the callback. + fn on_log_module_identity(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> LogModuleIdentityCallbackId; + /// Cancel a callback previously registered by [`Self::on_log_module_identity`], + /// causing it not to run in the future. + fn remove_on_log_module_identity(&self, callback: LogModuleIdentityCallbackId); +} + +impl log_module_identity for super::RemoteReducers { fn log_module_identity(&self, ) -> __sdk::Result<()> { - self.log_module_identity_then( |_, _| {}) + self.imp.call_reducer("log_module_identity", LogModuleIdentityArgs { }) + } + fn on_log_module_identity( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, + ) -> LogModuleIdentityCallbackId { + LogModuleIdentityCallbackId(self.imp.on_reducer( + "log_module_identity", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::LogModuleIdentity { + + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, ) + }), + )) + } + fn remove_on_log_module_identity(&self, callback: LogModuleIdentityCallbackId) { + self.imp.remove_on_reducer("log_module_identity", callback.0) } +} - /// Request that the remote module invoke the reducer `log_module_identity` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `log_module_identity`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_log_module_identity { + /// Set the call-reducer flags for the reducer `log_module_identity` to `flags`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn log_module_identity_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// This type is currently unstable and may be removed without a major version bump. + fn log_module_identity(&self, flags: __ws::CallReducerFlags); } -impl log_module_identity for super::RemoteReducers { - fn log_module_identity_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(LogModuleIdentityArgs { }, callback) +impl set_flags_for_log_module_identity for super::SetReducerFlags { + fn log_module_identity(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("log_module_identity", flags); } } @@ -893,7 +1272,7 @@ impl<'ctx> __sdk::TableWithPrimaryKey for LoggedOutPlayerTableHandle<'ctx> { #[doc(hidden)] pub(super) fn parse_table_update( - raw_updates: __ws::v2::TableUpdate, + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -1027,6 +1406,7 @@ use spacetimedb_sdk::__codegen::{ pub mod baz_type; pub mod foobar_type; pub mod has_special_stuff_type; +pub mod my_event_type; pub mod person_type; pub mod pk_multi_identity_type; pub mod player_type; @@ -1047,6 +1427,7 @@ pub mod add_private_reducer; pub mod assert_caller_identity_is_module_identity_reducer; pub mod delete_player_reducer; pub mod delete_players_by_name_reducer; +pub mod emit_event_reducer; pub mod list_over_age_reducer; pub mod log_module_identity_reducer; pub mod query_private_reducer; @@ -1054,6 +1435,7 @@ pub mod say_hello_reducer; pub mod test_reducer; pub mod test_btree_index_args_reducer; pub mod logged_out_player_table; +pub mod my_event_table; pub mod person_table; pub mod player_table; pub mod test_d_table; @@ -1067,6 +1449,7 @@ pub mod with_tx_procedure; pub use baz_type::Baz; pub use foobar_type::Foobar; pub use has_special_stuff_type::HasSpecialStuff; +pub use my_event_type::MyEvent; pub use person_type::Person; pub use pk_multi_identity_type::PkMultiIdentity; pub use player_type::Player; @@ -1082,23 +1465,25 @@ pub use test_foobar_type::TestFoobar; pub use namespace_test_c_type::NamespaceTestC; pub use namespace_test_f_type::NamespaceTestF; pub use logged_out_player_table::*; +pub use my_event_table::*; pub use my_player_table::*; pub use person_table::*; pub use player_table::*; pub use test_d_table::*; pub use test_f_table::*; -pub use add_reducer::add; -pub use add_player_reducer::add_player; -pub use add_private_reducer::add_private; -pub use assert_caller_identity_is_module_identity_reducer::assert_caller_identity_is_module_identity; -pub use delete_player_reducer::delete_player; -pub use delete_players_by_name_reducer::delete_players_by_name; -pub use list_over_age_reducer::list_over_age; -pub use log_module_identity_reducer::log_module_identity; -pub use query_private_reducer::query_private; -pub use say_hello_reducer::say_hello; -pub use test_reducer::test; -pub use test_btree_index_args_reducer::test_btree_index_args; +pub use add_reducer::{add, set_flags_for_add, AddCallbackId}; +pub use add_player_reducer::{add_player, set_flags_for_add_player, AddPlayerCallbackId}; +pub use add_private_reducer::{add_private, set_flags_for_add_private, AddPrivateCallbackId}; +pub use assert_caller_identity_is_module_identity_reducer::{assert_caller_identity_is_module_identity, set_flags_for_assert_caller_identity_is_module_identity, AssertCallerIdentityIsModuleIdentityCallbackId}; +pub use delete_player_reducer::{delete_player, set_flags_for_delete_player, DeletePlayerCallbackId}; +pub use delete_players_by_name_reducer::{delete_players_by_name, set_flags_for_delete_players_by_name, DeletePlayersByNameCallbackId}; +pub use emit_event_reducer::{emit_event, set_flags_for_emit_event, EmitEventCallbackId}; +pub use list_over_age_reducer::{list_over_age, set_flags_for_list_over_age, ListOverAgeCallbackId}; +pub use log_module_identity_reducer::{log_module_identity, set_flags_for_log_module_identity, LogModuleIdentityCallbackId}; +pub use query_private_reducer::{query_private, set_flags_for_query_private, QueryPrivateCallbackId}; +pub use say_hello_reducer::{say_hello, set_flags_for_say_hello, SayHelloCallbackId}; +pub use test_reducer::{test, set_flags_for_test, TestCallbackId}; +pub use test_btree_index_args_reducer::{test_btree_index_args, set_flags_for_test_btree_index_args, TestBtreeIndexArgsCallbackId}; pub use get_my_schema_via_http_procedure::get_my_schema_via_http; pub use return_value_procedure::return_value; pub use sleep_one_second_procedure::sleep_one_second; @@ -1128,6 +1513,10 @@ pub enum Reducer { } , DeletePlayersByName { name: String, +} , + EmitEvent { + name: String, + value: u64, } , ListOverAge { age: u8, @@ -1158,6 +1547,7 @@ impl __sdk::Reducer for Reducer { Reducer::AssertCallerIdentityIsModuleIdentity => "assert_caller_identity_is_module_identity", Reducer::DeletePlayer { .. } => "delete_player", Reducer::DeletePlayersByName { .. } => "delete_players_by_name", + Reducer::EmitEvent { .. } => "emit_event", Reducer::ListOverAge { .. } => "list_over_age", Reducer::LogModuleIdentity => "log_module_identity", Reducer::QueryPrivate => "query_private", @@ -1167,63 +1557,33 @@ impl __sdk::Reducer for Reducer { _ => unreachable!(), } } - #[allow(clippy::clone_on_copy)] -fn args_bsatn(&self) -> Result, __sats::bsatn::EncodeError> { - match self { +} +impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { + type Error = __sdk::Error; +fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result { + match &value.reducer_name[..] { Reducer::Add{ name, age, -} => __sats::bsatn::to_vec(&add_reducer::AddArgs { - name: name.clone(), - age: age.clone(), -}), - Reducer::AddPlayer{ +} "add" => Ok(__sdk::parse_reducer_args::("add", &value.args)?.into()),Reducer::AddPlayer{ name, -} => __sats::bsatn::to_vec(&add_player_reducer::AddPlayerArgs { - name: name.clone(), -}), - Reducer::AddPrivate{ +} "add_player" => Ok(__sdk::parse_reducer_args::("add_player", &value.args)?.into()),Reducer::AddPrivate{ name, -} => __sats::bsatn::to_vec(&add_private_reducer::AddPrivateArgs { - name: name.clone(), -}), - Reducer::AssertCallerIdentityIsModuleIdentity => __sats::bsatn::to_vec(&assert_caller_identity_is_module_identity_reducer::AssertCallerIdentityIsModuleIdentityArgs { - }), -Reducer::DeletePlayer{ +} "add_private" => Ok(__sdk::parse_reducer_args::("add_private", &value.args)?.into()),Reducer::AssertCallerIdentityIsModuleIdentity"assert_caller_identity_is_module_identity" => Ok(__sdk::parse_reducer_args::("assert_caller_identity_is_module_identity", &value.args)?.into()),Reducer::DeletePlayer{ id, -} => __sats::bsatn::to_vec(&delete_player_reducer::DeletePlayerArgs { - id: id.clone(), -}), - Reducer::DeletePlayersByName{ +} "delete_player" => Ok(__sdk::parse_reducer_args::("delete_player", &value.args)?.into()),Reducer::DeletePlayersByName{ + name, +} "delete_players_by_name" => Ok(__sdk::parse_reducer_args::("delete_players_by_name", &value.args)?.into()),Reducer::EmitEvent{ name, -} => __sats::bsatn::to_vec(&delete_players_by_name_reducer::DeletePlayersByNameArgs { - name: name.clone(), -}), - Reducer::ListOverAge{ + value, +} "emit_event" => Ok(__sdk::parse_reducer_args::("emit_event", &value.args)?.into()),Reducer::ListOverAge{ age, -} => __sats::bsatn::to_vec(&list_over_age_reducer::ListOverAgeArgs { - age: age.clone(), -}), - Reducer::LogModuleIdentity => __sats::bsatn::to_vec(&log_module_identity_reducer::LogModuleIdentityArgs { - }), -Reducer::QueryPrivate => __sats::bsatn::to_vec(&query_private_reducer::QueryPrivateArgs { - }), -Reducer::SayHello => __sats::bsatn::to_vec(&say_hello_reducer::SayHelloArgs { - }), -Reducer::Test{ +} "list_over_age" => Ok(__sdk::parse_reducer_args::("list_over_age", &value.args)?.into()),Reducer::LogModuleIdentity"log_module_identity" => Ok(__sdk::parse_reducer_args::("log_module_identity", &value.args)?.into()),Reducer::QueryPrivate"query_private" => Ok(__sdk::parse_reducer_args::("query_private", &value.args)?.into()),Reducer::SayHello"say_hello" => Ok(__sdk::parse_reducer_args::("say_hello", &value.args)?.into()),Reducer::Test{ arg, arg_2, arg_3, arg_4, -} => __sats::bsatn::to_vec(&test_reducer::TestArgs { - arg: arg.clone(), - arg_2: arg_2.clone(), - arg_3: arg_3.clone(), - arg_4: arg_4.clone(), -}), - Reducer::TestBtreeIndexArgs => __sats::bsatn::to_vec(&test_btree_index_args_reducer::TestBtreeIndexArgsArgs { - }), -_ => unreachable!(), +} "test" => Ok(__sdk::parse_reducer_args::("test", &value.args)?.into()),Reducer::TestBtreeIndexArgs"test_btree_index_args" => Ok(__sdk::parse_reducer_args::("test_btree_index_args", &value.args)?.into()),unknown => Err(__sdk::InternalError::unknown_name("reducer", unknown, "ReducerCallInfo").into()), } } } @@ -1233,6 +1593,7 @@ _ => unreachable!(), #[doc(hidden)] pub struct DbUpdate { logged_out_player: __sdk::TableUpdate, + my_event: __sdk::TableUpdate, my_player: __sdk::TableUpdate, person: __sdk::TableUpdate, player: __sdk::TableUpdate, @@ -1241,14 +1602,15 @@ pub struct DbUpdate { } -impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { +impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { type Error = __sdk::Error; - fn try_from(raw: __ws::v2::TransactionUpdate) -> Result { + fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result { let mut db_update = DbUpdate::default(); - for table_update in __sdk::transaction_update_iter_table_updates(raw) { + for table_update in raw.tables { match &table_update.table_name[..] { "logged_out_player" => db_update.logged_out_player.append(logged_out_player_table::parse_table_update(table_update)?), + "my_event" => db_update.my_event.append(my_event_table::parse_table_update(table_update)?), "my_player" => db_update.my_player.append(my_player_table::parse_table_update(table_update)?), "person" => db_update.person.append(person_table::parse_table_update(table_update)?), "player" => db_update.player.append(player_table::parse_table_update(table_update)?), @@ -1277,6 +1639,7 @@ impl __sdk::DbUpdate for DbUpdate { let mut diff = AppliedDiff::default(); diff.logged_out_player = cache.apply_diff_to_table::("logged_out_player", &self.logged_out_player).with_updates_by_pk(|row| &row.identity); + diff.my_event = cache.apply_diff_to_table::("my_event", &self.my_event); diff.person = cache.apply_diff_to_table::("person", &self.person).with_updates_by_pk(|row| &row.id); diff.player = cache.apply_diff_to_table::("player", &self.player).with_updates_by_pk(|row| &row.identity); diff.test_d = cache.apply_diff_to_table::("test_d", &self.test_d); @@ -1290,6 +1653,7 @@ fn parse_initial_rows(raw: __ws::v2::QueryRows) -> __sdk::Result { for table_rows in raw.tables { match &table_rows.table[..] { "logged_out_player" => db_update.logged_out_player.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + "my_event" => db_update.my_event.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "my_player" => db_update.my_player.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "person" => db_update.person.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "player" => db_update.player.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), @@ -1303,6 +1667,7 @@ fn parse_unsubscribe_rows(raw: __ws::v2::QueryRows) -> __sdk::Result { for table_rows in raw.tables { match &table_rows.table[..] { "logged_out_player" => db_update.logged_out_player.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + "my_event" => db_update.my_event.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "my_player" => db_update.my_player.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "person" => db_update.person.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "player" => db_update.player.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), @@ -1318,6 +1683,7 @@ for table_rows in raw.tables { #[doc(hidden)] pub struct AppliedDiff<'r> { logged_out_player: __sdk::TableAppliedDiff<'r, Player>, + my_event: __sdk::TableAppliedDiff<'r, MyEvent>, my_player: __sdk::TableAppliedDiff<'r, Player>, person: __sdk::TableAppliedDiff<'r, Person>, player: __sdk::TableAppliedDiff<'r, Player>, @@ -1334,6 +1700,7 @@ impl __sdk::InModule for AppliedDiff<'_> { impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks) { callbacks.invoke_table_row_callbacks::("logged_out_player", &self.logged_out_player, event); + callbacks.invoke_table_row_callbacks::("my_event", &self.my_event, event); callbacks.invoke_table_row_callbacks::("my_player", &self.my_player, event); callbacks.invoke_table_row_callbacks::("person", &self.person, event); callbacks.invoke_table_row_callbacks::("player", &self.player, event); @@ -1370,6 +1737,20 @@ impl __sdk::InModule for RemoteProcedures { type Module = RemoteModule; } +#[doc(hidden)] +/// The `set_reducer_flags` field of [`DbConnection`], +/// with methods provided by extension traits for each reducer defined by the module. +/// Each method sets the flags for the reducer with the same name. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub struct SetReducerFlags { + imp: __sdk::DbContextImpl, +} + +impl __sdk::InModule for SetReducerFlags { + type Module = RemoteModule; +} + /// The `db` field of [`EventContext`] and [`DbConnection`], /// with methods provided by extension traits for each table defined by the module. pub struct RemoteTables { @@ -1402,6 +1783,11 @@ pub struct DbConnection { /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, #[doc(hidden)] + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, @@ -1417,6 +1803,7 @@ impl __sdk::DbContext for DbConnection { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1427,6 +1814,9 @@ impl __sdk::DbContext for DbConnection { fn procedures(&self) -> &Self::Procedures { &self.procedures } + fn set_reducer_flags(&self) -> &Self::SetReducerFlags { + &self.set_reducer_flags + } fn is_active(&self) -> bool { self.imp.is_active() @@ -1530,6 +1920,7 @@ impl __sdk::DbConnection for DbConnection { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, + set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } } @@ -1581,11 +1972,13 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle { pub trait RemoteDbContext: __sdk::DbContext< DbView = RemoteTables, Reducers = RemoteReducers, + SetReducerFlags = SetReducerFlags, SubscriptionBuilder = __sdk::SubscriptionBuilder, > {} impl, >> RemoteDbContext for Ctx {} @@ -1597,6 +1990,11 @@ pub struct EventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -1613,6 +2011,7 @@ impl __sdk::AbstractEventContext for EventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + set_reducer_flags: SetReducerFlags { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, event, imp, @@ -1628,6 +2027,7 @@ impl __sdk::DbContext for EventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1638,6 +2038,9 @@ impl __sdk::DbContext for EventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } + fn set_reducer_flags(&self) -> &Self::SetReducerFlags { + &self.set_reducer_flags + } fn is_active(&self) -> bool { self.imp.is_active() @@ -1673,6 +2076,11 @@ pub struct ReducerEventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -1689,6 +2097,7 @@ impl __sdk::AbstractEventContext for ReducerEventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + set_reducer_flags: SetReducerFlags { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, event, imp, @@ -1704,6 +2113,7 @@ impl __sdk::DbContext for ReducerEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1714,6 +2124,9 @@ impl __sdk::DbContext for ReducerEventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } + fn set_reducer_flags(&self) -> &Self::SetReducerFlags { + &self.set_reducer_flags + } fn is_active(&self) -> bool { self.imp.is_active() @@ -1748,6 +2161,11 @@ pub struct ProcedureEventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, imp: __sdk::DbContextImpl, @@ -1763,6 +2181,7 @@ impl __sdk::AbstractEventContext for ProcedureEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, + set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } } @@ -1776,6 +2195,7 @@ impl __sdk::DbContext for ProcedureEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1786,6 +2206,9 @@ impl __sdk::DbContext for ProcedureEventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } + fn set_reducer_flags(&self) -> &Self::SetReducerFlags { + &self.set_reducer_flags + } fn is_active(&self) -> bool { self.imp.is_active() @@ -1820,6 +2243,11 @@ pub struct SubscriptionEventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, imp: __sdk::DbContextImpl, @@ -1835,6 +2263,7 @@ impl __sdk::AbstractEventContext for SubscriptionEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, + set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } } @@ -1848,6 +2277,7 @@ impl __sdk::DbContext for SubscriptionEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1858,6 +2288,9 @@ impl __sdk::DbContext for SubscriptionEventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } + fn set_reducer_flags(&self) -> &Self::SetReducerFlags { + &self.set_reducer_flags + } fn is_active(&self) -> bool { self.imp.is_active() @@ -1893,6 +2326,11 @@ pub struct ErrorContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, + /// Access to setting the call-flags of each reducer defined for each reducer defined by the module + /// via extension traits implemented for [`SetReducerFlags`]. + /// + /// This type is currently unstable and may be removed without a major version bump. + pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -1909,6 +2347,7 @@ impl __sdk::AbstractEventContext for ErrorContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + set_reducer_flags: SetReducerFlags { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, event, imp, @@ -1924,6 +2363,7 @@ impl __sdk::DbContext for ErrorContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1934,6 +2374,9 @@ impl __sdk::DbContext for ErrorContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } + fn set_reducer_flags(&self) -> &Self::SetReducerFlags { + &self.set_reducer_flags + } fn is_active(&self) -> bool { self.imp.is_active() @@ -1973,7 +2416,7 @@ impl __sdk::SpacetimeModule for RemoteModule { type Reducer = Reducer; type DbView = RemoteTables; type Reducers = RemoteReducers; - type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; type DbUpdate = DbUpdate; type AppliedDiff<'r> = AppliedDiff<'r>; type SubscriptionHandle = SubscriptionHandle; @@ -1981,6 +2424,7 @@ impl __sdk::SpacetimeModule for RemoteModule { fn register_tables(client_cache: &mut __sdk::ClientCache) { logged_out_player_table::register_table(client_cache); + my_event_table::register_table(client_cache); my_player_table::register_table(client_cache); person_table::register_table(client_cache); player_table::register_table(client_cache); @@ -1989,6 +2433,7 @@ fn register_tables(client_cache: &mut __sdk::ClientCache) { } const ALL_TABLE_NAMES: &'static [&'static str] = &[ "logged_out_player", + "my_event", "my_player", "person", "player", @@ -1996,6 +2441,184 @@ const ALL_TABLE_NAMES: &'static [&'static str] = &[ "test_f", ]; } +''' +"my_event_table.rs" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{ + self as __sdk, + __lib, + __sats, + __ws, +}; +use super::my_event_type::MyEvent; + +/// Table handle for the table `my_event`. +/// +/// Obtain a handle from the [`MyEventTableAccess::my_event`] method on [`super::RemoteTables`], +/// like `ctx.db.my_event()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.my_event().on_insert(...)`. +pub struct MyEventTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `my_event`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait MyEventTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`MyEventTableHandle`], which mediates access to the table `my_event`. + fn my_event(&self) -> MyEventTableHandle<'_>; +} + +impl MyEventTableAccess for super::RemoteTables { + fn my_event(&self) -> MyEventTableHandle<'_> { + MyEventTableHandle { + imp: self.imp.get_table::("my_event"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct MyEventInsertCallbackId(__sdk::CallbackId); +pub struct MyEventDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for MyEventTableHandle<'ctx> { + type Row = MyEvent; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { self.imp.count() } + fn iter(&self) -> impl Iterator + '_ { self.imp.iter() } + + type InsertCallbackId = MyEventInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> MyEventInsertCallbackId { + MyEventInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: MyEventInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = MyEventDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> MyEventDeleteCallbackId { + MyEventDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: MyEventDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + + let _table = client_cache.get_or_make_table::("my_event"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse( + "TableUpdate", + "TableUpdate", + ).with_cause(e).into() + }) +} + + #[allow(non_camel_case_types)] + /// Extension trait for query builder access to the table `MyEvent`. + /// + /// Implemented for [`__sdk::QueryTableAccessor`]. + pub trait my_eventQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `MyEvent`. + fn my_event(&self) -> __sdk::__query_builder::Table; + } + + impl my_eventQueryTableAccess for __sdk::QueryTableAccessor { + fn my_event(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("my_event") + } + } + +''' +"my_event_type.rs" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{ + self as __sdk, + __lib, + __sats, + __ws, +}; + + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct MyEvent { + pub name: String, + pub value: u64, +} + + +impl __sdk::InModule for MyEvent { + type Module = super::RemoteModule; +} + + +/// Column accessor struct for the table `MyEvent`. +/// +/// Provides typed access to columns for query building. +pub struct MyEventCols { + pub name: __sdk::__query_builder::Col, + pub value: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for MyEvent { + type Cols = MyEventCols; + fn cols(table_name: &'static str) -> Self::Cols { + MyEventCols { + name: __sdk::__query_builder::Col::new(table_name, "name"), + value: __sdk::__query_builder::Col::new(table_name, "value"), + + } + } +} + +/// Indexed column accessor struct for the table `MyEvent`. +/// +/// Provides typed access to indexed columns for query building. +pub struct MyEventIxCols { +} + +impl __sdk::__query_builder::HasIxCols for MyEvent { + type IxCols = MyEventIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + MyEventIxCols { + + } + } +} + ''' "my_player_table.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -2087,7 +2710,7 @@ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -2282,7 +2905,7 @@ impl<'ctx> __sdk::TableWithPrimaryKey for PersonTableHandle<'ctx> { #[doc(hidden)] pub(super) fn parse_table_update( - raw_updates: __ws::v2::TableUpdate, + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -2582,7 +3205,7 @@ impl<'ctx> __sdk::TableWithPrimaryKey for PlayerTableHandle<'ctx> { #[doc(hidden)] pub(super) fn parse_table_update( - raw_updates: __ws::v2::TableUpdate, + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -2916,6 +3539,8 @@ impl __sdk::InModule for QueryPrivateArgs { type Module = super::RemoteModule; } +pub struct QueryPrivateCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `query_private`. /// @@ -2925,36 +3550,68 @@ pub trait query_private { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`query_private:query_private_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_query_private`] callbacks. + fn query_private(&self, ) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `query_private`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`QueryPrivateCallbackId`] can be passed to [`Self::remove_on_query_private`] + /// to cancel the callback. + fn on_query_private(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> QueryPrivateCallbackId; + /// Cancel a callback previously registered by [`Self::on_query_private`], + /// causing it not to run in the future. + fn remove_on_query_private(&self, callback: QueryPrivateCallbackId); +} + +impl query_private for super::RemoteReducers { fn query_private(&self, ) -> __sdk::Result<()> { - self.query_private_then( |_, _| {}) + self.imp.call_reducer("query_private", QueryPrivateArgs { }) } + fn on_query_private( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, + ) -> QueryPrivateCallbackId { + QueryPrivateCallbackId(self.imp.on_reducer( + "query_private", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::QueryPrivate { + + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, ) + }), + )) + } + fn remove_on_query_private(&self, callback: QueryPrivateCallbackId) { + self.imp.remove_on_reducer("query_private", callback.0) + } +} - /// Request that the remote module invoke the reducer `query_private` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `query_private`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_query_private { + /// Set the call-reducer flags for the reducer `query_private` to `flags`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn query_private_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// This type is currently unstable and may be removed without a major version bump. + fn query_private(&self, flags: __ws::CallReducerFlags); } -impl query_private for super::RemoteReducers { - fn query_private_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(QueryPrivateArgs { }, callback) +impl set_flags_for_query_private for super::SetReducerFlags { + fn query_private(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("query_private", flags); } } @@ -3170,6 +3827,8 @@ impl __sdk::InModule for SayHelloArgs { type Module = super::RemoteModule; } +pub struct SayHelloCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `say_hello`. /// @@ -3179,36 +3838,68 @@ pub trait say_hello { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`say_hello:say_hello_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_say_hello`] callbacks. + fn say_hello(&self, ) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `say_hello`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`SayHelloCallbackId`] can be passed to [`Self::remove_on_say_hello`] + /// to cancel the callback. + fn on_say_hello(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> SayHelloCallbackId; + /// Cancel a callback previously registered by [`Self::on_say_hello`], + /// causing it not to run in the future. + fn remove_on_say_hello(&self, callback: SayHelloCallbackId); +} + +impl say_hello for super::RemoteReducers { fn say_hello(&self, ) -> __sdk::Result<()> { - self.say_hello_then( |_, _| {}) + self.imp.call_reducer("say_hello", SayHelloArgs { }) + } + fn on_say_hello( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, + ) -> SayHelloCallbackId { + SayHelloCallbackId(self.imp.on_reducer( + "say_hello", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::SayHello { + + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, ) + }), + )) + } + fn remove_on_say_hello(&self, callback: SayHelloCallbackId) { + self.imp.remove_on_reducer("say_hello", callback.0) } +} - /// Request that the remote module invoke the reducer `say_hello` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `say_hello`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_say_hello { + /// Set the call-reducer flags for the reducer `say_hello` to `flags`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn say_hello_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// This type is currently unstable and may be removed without a major version bump. + fn say_hello(&self, flags: __ws::CallReducerFlags); } -impl say_hello for super::RemoteReducers { - fn say_hello_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(SayHelloArgs { }, callback) +impl set_flags_for_say_hello for super::SetReducerFlags { + fn say_hello(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("say_hello", flags); } } @@ -3386,6 +4077,8 @@ impl __sdk::InModule for TestBtreeIndexArgsArgs { type Module = super::RemoteModule; } +pub struct TestBtreeIndexArgsCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `test_btree_index_args`. /// @@ -3395,36 +4088,68 @@ pub trait test_btree_index_args { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`test_btree_index_args:test_btree_index_args_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_test_btree_index_args`] callbacks. + fn test_btree_index_args(&self, ) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `test_btree_index_args`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`TestBtreeIndexArgsCallbackId`] can be passed to [`Self::remove_on_test_btree_index_args`] + /// to cancel the callback. + fn on_test_btree_index_args(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> TestBtreeIndexArgsCallbackId; + /// Cancel a callback previously registered by [`Self::on_test_btree_index_args`], + /// causing it not to run in the future. + fn remove_on_test_btree_index_args(&self, callback: TestBtreeIndexArgsCallbackId); +} + +impl test_btree_index_args for super::RemoteReducers { fn test_btree_index_args(&self, ) -> __sdk::Result<()> { - self.test_btree_index_args_then( |_, _| {}) + self.imp.call_reducer("test_btree_index_args", TestBtreeIndexArgsArgs { }) } + fn on_test_btree_index_args( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, + ) -> TestBtreeIndexArgsCallbackId { + TestBtreeIndexArgsCallbackId(self.imp.on_reducer( + "test_btree_index_args", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::TestBtreeIndexArgs { + + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, ) + }), + )) + } + fn remove_on_test_btree_index_args(&self, callback: TestBtreeIndexArgsCallbackId) { + self.imp.remove_on_reducer("test_btree_index_args", callback.0) + } +} - /// Request that the remote module invoke the reducer `test_btree_index_args` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `test_btree_index_args`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_test_btree_index_args { + /// Set the call-reducer flags for the reducer `test_btree_index_args` to `flags`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn test_btree_index_args_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// This type is currently unstable and may be removed without a major version bump. + fn test_btree_index_args(&self, flags: __ws::CallReducerFlags); } -impl test_btree_index_args for super::RemoteReducers { - fn test_btree_index_args_then( - &self, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(TestBtreeIndexArgsArgs { }, callback) +impl set_flags_for_test_btree_index_args for super::SetReducerFlags { + fn test_btree_index_args(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("test_btree_index_args", flags); } } @@ -3520,7 +4245,7 @@ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -3762,7 +4487,7 @@ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -3889,6 +4614,8 @@ impl __sdk::InModule for TestArgs { type Module = super::RemoteModule; } +pub struct TestCallbackId(__sdk::CallbackId); + #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `test`. /// @@ -3898,48 +4625,76 @@ pub trait test { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and this method provides no way to listen for its completion status. - /// /// Use [`test:test_then`] to run a callback after the reducer completes. + /// and its status can be observed by listening for [`Self::on_test`] callbacks. fn test(&self, arg: TestA, arg_2: TestB, arg_3: NamespaceTestC, arg_4: NamespaceTestF, -) -> __sdk::Result<()> { - self.test_then(arg, arg_2, arg_3, arg_4, |_, _| {}) - } - - /// Request that the remote module invoke the reducer `test` to run as soon as possible, - /// registering `callback` to run when we are notified that the reducer completed. +) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `test`. /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed with the `callback`. - fn test_then( - &self, - arg: TestA, -arg_2: TestB, -arg_3: NamespaceTestC, -arg_4: NamespaceTestF, - - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()>; + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`TestCallbackId`] can be passed to [`Self::remove_on_test`] + /// to cancel the callback. + fn on_test(&self, callback: impl FnMut(&super::ReducerEventContext, &TestA, &TestB, &NamespaceTestC, &NamespaceTestF, ) + Send + 'static) -> TestCallbackId; + /// Cancel a callback previously registered by [`Self::on_test`], + /// causing it not to run in the future. + fn remove_on_test(&self, callback: TestCallbackId); } impl test for super::RemoteReducers { - fn test_then( - &self, - arg: TestA, + fn test(&self, arg: TestA, arg_2: TestB, arg_3: NamespaceTestC, arg_4: NamespaceTestF, +) -> __sdk::Result<()> { + self.imp.call_reducer("test", TestArgs { arg, arg_2, arg_3, arg_4, }) + } + fn on_test( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &TestA, &TestB, &NamespaceTestC, &NamespaceTestF, ) + Send + 'static, + ) -> TestCallbackId { + TestCallbackId(self.imp.on_reducer( + "test", + Box::new(move |ctx: &super::ReducerEventContext| { + #[allow(irrefutable_let_patterns)] + let super::ReducerEventContext { + event: __sdk::ReducerEvent { + reducer: super::Reducer::Test { + arg, arg_2, arg_3, arg_4, + }, + .. + }, + .. + } = ctx else { unreachable!() }; + callback(ctx, arg, arg_2, arg_3, arg_4, ) + }), + )) + } + fn remove_on_test(&self, callback: TestCallbackId) { + self.imp.remove_on_reducer("test", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `test`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_test { + /// Set the call-reducer flags for the reducer `test` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn test(&self, flags: __ws::CallReducerFlags); +} - callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) - + Send - + 'static, - ) -> __sdk::Result<()> { - self.imp.invoke_reducer_with_callback(TestArgs { arg, arg_2, arg_3, arg_4, }, callback) +impl set_flags_for_test for super::SetReducerFlags { + fn test(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("test", flags); } } diff --git a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap index 9b4d56d000d..2b4c49e9f77 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -122,6 +122,24 @@ export default { name: __t.string(), }; ''' +"emit_event_reducer.ts" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default { + name: __t.string(), + value: __t.u64(), +}; +''' "foobar_type.ts" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. @@ -228,6 +246,7 @@ import AddPrivateReducer from "./add_private_reducer"; import AssertCallerIdentityIsModuleIdentityReducer from "./assert_caller_identity_is_module_identity_reducer"; import DeletePlayerReducer from "./delete_player_reducer"; import DeletePlayersByNameReducer from "./delete_players_by_name_reducer"; +import EmitEventReducer from "./emit_event_reducer"; import ListOverAgeReducer from "./list_over_age_reducer"; import LogModuleIdentityReducer from "./log_module_identity_reducer"; import QueryPrivateReducer from "./query_private_reducer"; @@ -243,6 +262,7 @@ import * as WithTxProcedure from "./with_tx_procedure"; // Import all table schema definitions import LoggedOutPlayerRow from "./logged_out_player_table"; +import MyEventRow from "./my_event_table"; import MyPlayerRow from "./my_player_table"; import PersonRow from "./person_table"; import PlayerRow from "./player_table"; @@ -336,6 +356,7 @@ const reducersSchema = __reducers( __reducerSchema("assert_caller_identity_is_module_identity", AssertCallerIdentityIsModuleIdentityReducer), __reducerSchema("delete_player", DeletePlayerReducer), __reducerSchema("delete_players_by_name", DeletePlayersByNameReducer), + __reducerSchema("emit_event", EmitEventReducer), __reducerSchema("list_over_age", ListOverAgeReducer), __reducerSchema("log_module_identity", LogModuleIdentityReducer), __reducerSchema("query_private", QueryPrivateReducer), @@ -453,6 +474,44 @@ export default __t.row({ playerId: __t.u64().name("player_id"), name: __t.string(), }); +''' +"my_event_table.ts" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default __t.row({ + name: __t.string(), + value: __t.u64(), +}); +''' +"my_event_type.ts" = ''' +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from "spacetimedb"; + +export default __t.object("MyEvent", { + name: __t.string(), + value: __t.u64(), +}); + + ''' "my_player_table.ts" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -970,6 +1029,7 @@ import { type Infer as __Infer } from "spacetimedb"; import Baz from "../baz_type"; import Foobar from "../foobar_type"; import HasSpecialStuff from "../has_special_stuff_type"; +import MyEvent from "../my_event_type"; import Person from "../person_type"; import PkMultiIdentity from "../pk_multi_identity_type"; import Player from "../player_type"; @@ -988,6 +1048,7 @@ import NamespaceTestF from "../namespace_test_f_type"; export type Baz = __Infer; export type Foobar = __Infer; export type HasSpecialStuff = __Infer; +export type MyEvent = __Infer; export type Person = __Infer; export type PkMultiIdentity = __Infer; export type Player = __Infer; @@ -1043,6 +1104,7 @@ import AddPrivateReducer from "../add_private_reducer"; import AssertCallerIdentityIsModuleIdentityReducer from "../assert_caller_identity_is_module_identity_reducer"; import DeletePlayerReducer from "../delete_player_reducer"; import DeletePlayersByNameReducer from "../delete_players_by_name_reducer"; +import EmitEventReducer from "../emit_event_reducer"; import ListOverAgeReducer from "../list_over_age_reducer"; import LogModuleIdentityReducer from "../log_module_identity_reducer"; import QueryPrivateReducer from "../query_private_reducer"; @@ -1056,6 +1118,7 @@ export type AddPrivateParams = __Infer; export type AssertCallerIdentityIsModuleIdentityParams = __Infer; export type DeletePlayerParams = __Infer; export type DeletePlayersByNameParams = __Infer; +export type EmitEventParams = __Infer; export type ListOverAgeParams = __Infer; export type LogModuleIdentityParams = __Infer; export type QueryPrivateParams = __Infer; diff --git a/crates/physical-plan/src/plan.rs b/crates/physical-plan/src/plan.rs index 92593e293f8..db900c4ac5c 100644 --- a/crates/physical-plan/src/plan.rs +++ b/crates/physical-plan/src/plan.rs @@ -121,6 +121,13 @@ impl ProjectPlan { Self::None(plan) | Self::Name(plan, ..) => plan.reads_from_view(anonymous), } } + + /// Does this plan use an event table as the lookup (rhs) table in a semi-join? + pub fn reads_from_event_table(&self) -> bool { + match self { + Self::None(plan) | Self::Name(plan, ..) => plan.reads_from_event_table(), + } + } } /// Physical plans always terminate with a projection. @@ -228,6 +235,15 @@ impl ProjectListPlan { Self::List(plans, ..) | Self::Agg(plans, ..) => plans.iter().any(|plan| plan.reads_from_view(anonymous)), } } + + /// Does this plan use an event table as the lookup (rhs) table in a semi-join? + pub fn reads_from_event_table(&self) -> bool { + match self { + Self::Limit(plan, _) => plan.reads_from_event_table(), + Self::Name(plans) => plans.iter().any(|plan| plan.reads_from_event_table()), + Self::List(plans, ..) | Self::Agg(plans, ..) => plans.iter().any(|plan| plan.reads_from_event_table()), + } + } } /// Query operators return tuples of rows. @@ -1150,6 +1166,14 @@ impl PhysicalPlan { _ => false, }) } + + /// Does this plan use an event table as the lookup (rhs) table in a semi-join? + pub fn reads_from_event_table(&self) -> bool { + self.any(&|plan| match plan { + Self::IxJoin(join, _) => join.rhs.is_event, + _ => false, + }) + } } /// Scan a table row by row, returning row ids diff --git a/crates/query-builder/src/join.rs b/crates/query-builder/src/join.rs index f72a3942514..e38af5f0248 100644 --- a/crates/query-builder/src/join.rs +++ b/crates/query-builder/src/join.rs @@ -2,7 +2,7 @@ use crate::TableNameStr; use super::{ expr::{format_expr, BoolExpr}, - table::{ColumnRef, HasCols, HasIxCols, Table}, + table::{CanBeLookupTable, ColumnRef, HasCols, HasIxCols, Table}, Query, RawQuery, }; use std::marker::PhantomData; @@ -66,7 +66,7 @@ pub struct RightSemiJoin { } impl Table { - pub fn left_semijoin( + pub fn left_semijoin( self, right: Table, on: impl Fn(&L::IxCols, &R::IxCols) -> IxJoinEq, @@ -80,7 +80,7 @@ impl Table { } } - pub fn right_semijoin( + pub fn right_semijoin( self, right: Table, on: impl Fn(&L::IxCols, &R::IxCols) -> IxJoinEq, @@ -97,7 +97,7 @@ impl Table { } impl super::FromWhere { - pub fn left_semijoin( + pub fn left_semijoin( self, right: Table, on: impl Fn(&L::IxCols, &R::IxCols) -> IxJoinEq, @@ -111,7 +111,7 @@ impl super::FromWhere { } } - pub fn right_semijoin( + pub fn right_semijoin( self, right: Table, on: impl Fn(&L::IxCols, &R::IxCols) -> IxJoinEq, diff --git a/crates/query-builder/src/lib.rs b/crates/query-builder/src/lib.rs index 2621b759487..9b5382967c5 100644 --- a/crates/query-builder/src/lib.rs +++ b/crates/query-builder/src/lib.rs @@ -109,6 +109,8 @@ mod tests { } } } + impl CanBeLookupTable for User {} + impl CanBeLookupTable for Other {} fn norm(s: &str) -> String { s.split_whitespace().collect::>().join(" ") } diff --git a/crates/query-builder/src/table.rs b/crates/query-builder/src/table.rs index e2f126355ac..3d9fb588d93 100644 --- a/crates/query-builder/src/table.rs +++ b/crates/query-builder/src/table.rs @@ -16,6 +16,11 @@ pub trait HasIxCols { fn ix_cols(name: TableNameStr) -> Self::IxCols; } +/// Marker trait for tables that can appear as the right/inner/lookup +/// table in a semi-join. Event tables do NOT implement this trait, +/// preventing them from being used as the lookup side of a join. +pub trait CanBeLookupTable: HasIxCols {} + pub struct Table { pub(super) table_name: TableNameStr, _marker: PhantomData, diff --git a/modules/module-test/src/lib.rs b/modules/module-test/src/lib.rs index 433796e5b60..d8ef5ff62a0 100644 --- a/modules/module-test/src/lib.rs +++ b/modules/module-test/src/lib.rs @@ -165,6 +165,12 @@ pub struct HasSpecialStuff { connection_id: ConnectionId, } +#[table(name = my_event, public, event)] +pub struct MyEvent { + name: String, + value: u64, +} + /// These two tables defined with the same row type /// verify that we can define multiple tables with the same type. /// @@ -373,6 +379,11 @@ pub fn delete_players_by_name(ctx: &ReducerContext, name: String) -> Result<(), } } +#[spacetimedb::reducer] +pub fn emit_event(ctx: &ReducerContext, name: String, value: u64) { + ctx.db.my_event().insert(MyEvent { name, value }); +} + #[spacetimedb::reducer(client_connected)] fn client_connected(_ctx: &ReducerContext) {} From 48fcada6e9fd2abcdac57da188c093aaa290f41c Mon Sep 17 00:00:00 2001 From: = Date: Thu, 12 Feb 2026 14:03:58 -0500 Subject: [PATCH 02/24] Add client SDK support for event tables - Add EventTable trait for transient event table rows (no client cache persistence) - Add from_event_inserts() and into_event_diff() for event table update handling - Parse EventTable rows from v2 TableUpdateRows - Exclude event tables from automatic subscription plans (get_all) - Reject v1 client subscriptions to event tables with upgrade message - Prevent event tables from being used as join lookup tables - Export EventTable in SDK public API --- .../subscription/module_subscription_actor.rs | 23 +++++++++++++ .../module_subscription_manager.rs | 5 +++ crates/core/src/subscription/subscription.rs | 4 +-- crates/subscription/src/lib.rs | 9 +++++ sdks/rust/src/client_cache.rs | 13 +++++++ sdks/rust/src/lib.rs | 4 +-- sdks/rust/src/spacetime_module.rs | 10 +++++- sdks/rust/src/table.rs | 34 +++++++++++++++++++ 8 files changed, 97 insertions(+), 5 deletions(-) diff --git a/crates/core/src/subscription/module_subscription_actor.rs b/crates/core/src/subscription/module_subscription_actor.rs index f9d8a4c3663..c3248ae979f 100644 --- a/crates/core/src/subscription/module_subscription_actor.rs +++ b/crates/core/src/subscription/module_subscription_actor.rs @@ -654,6 +654,17 @@ impl ModuleSubscriptions { send_err_msg ); + // V1 clients must not subscribe to event tables. + // Old codegen doesn't understand event tables and would accumulate rows in the client cache. + if query.returns_event_table() { + let _ = send_err_msg( + "Subscribing to event tables requires WebSocket v2. \ + Please upgrade your client SDK and regenerate your module bindings." + .into(), + ); + return Ok((None, false)); + } + let mut_tx = ScopeGuard::::into_inner(mut_tx); let (tx, tx_offset, trapped) = @@ -1312,6 +1323,18 @@ impl ModuleSubscriptions { send_err_msg, (None, false) ); + + // V1 clients must not subscribe to event tables. + // Old codegen doesn't understand event tables and would accumulate rows in the client cache. + if queries.iter().any(|q| q.returns_event_table()) { + send_err_msg( + "Subscribing to event tables requires WebSocket v2. \ + Please upgrade your client SDK and regenerate your module bindings." + .into(), + ); + return Ok((None, false)); + } + let (mut_tx, _) = self.guard_mut_tx(mut_tx, <_>::default()); // We minimize locking so that other clients can add subscriptions concurrently. diff --git a/crates/core/src/subscription/module_subscription_manager.rs b/crates/core/src/subscription/module_subscription_manager.rs index 1e0ebe86e73..f68e5f2dc5e 100644 --- a/crates/core/src/subscription/module_subscription_manager.rs +++ b/crates/core/src/subscription/module_subscription_manager.rs @@ -142,6 +142,11 @@ impl Plan { pub fn sql(&self) -> &str { &self.sql } + + /// Does this plan return rows from an event table? + pub fn returns_event_table(&self) -> bool { + self.plans.iter().any(|p| p.returns_event_table()) + } } /// For each client, we hold a handle for sending messages, and we track the queries they are subscribed to. diff --git a/crates/core/src/subscription/subscription.rs b/crates/core/src/subscription/subscription.rs index 8d24db68a11..b27dd5e1ce1 100644 --- a/crates/core/src/subscription/subscription.rs +++ b/crates/core/src/subscription/subscription.rs @@ -624,7 +624,7 @@ where I: Iterator>, { Ok(get_all_tables(relational_db, tx)? - .filter(|t| t.table_type == StTableType::User && auth.has_read_access(t.table_access)) + .filter(|t| t.table_type == StTableType::User && auth.has_read_access(t.table_access) && !t.is_event) .map(|schema| { let sql = format!("SELECT * FROM {}", schema.table_name); let tx = SchemaViewer::new(tx, auth); @@ -662,7 +662,7 @@ pub(crate) fn legacy_get_all( .get_all_tables(tx)? .iter() .map(Deref::deref) - .filter(|t| t.table_type == StTableType::User && auth.has_read_access(t.table_access)) + .filter(|t| t.table_type == StTableType::User && auth.has_read_access(t.table_access) && !t.is_event) .map(|src| SupportedQuery { kind: query::Supported::Select, expr: QueryExpr::new(src), diff --git a/crates/subscription/src/lib.rs b/crates/subscription/src/lib.rs index 20708a694c2..ca0ef5b63e3 100644 --- a/crates/subscription/src/lib.rs +++ b/crates/subscription/src/lib.rs @@ -340,6 +340,11 @@ impl SubscriptionPlan { self.plan_opt.returns_view_table() } + /// Does this plan return rows from an event table? + pub fn returns_event_table(&self) -> bool { + self.plan_opt.return_table().is_some_and(|schema| schema.is_event) + } + /// The number of columns returned. /// Only relevant if [`Self::is_view`] is true. pub fn num_cols(&self) -> usize { @@ -514,6 +519,10 @@ impl SubscriptionPlan { bail!("Subscriptions require indexes on join columns") } + if plan_opt.reads_from_event_table() { + bail!("Event tables cannot be used as the lookup table in subscription joins") + } + let (table_ids, table_aliases) = table_ids_for_plan(&plan); let fragments = Fragments::compile_from_plan(&plan, &table_aliases, auth)?; diff --git a/sdks/rust/src/client_cache.rs b/sdks/rust/src/client_cache.rs index 4a5bbfc312c..22d9b4b69b2 100644 --- a/sdks/rust/src/client_cache.rs +++ b/sdks/rust/src/client_cache.rs @@ -105,6 +105,19 @@ impl Default for TableAppliedDiff<'_, Row> { } impl<'r, Row> TableAppliedDiff<'r, Row> { + /// For event tables: construct a `TableAppliedDiff` with just inserts, + /// without touching the client cache. + /// Each insert will fire `on_insert` callbacks. + pub(crate) fn from_event_inserts(inserts: &'r [WithBsatn]) -> Self { + let insert_map = inserts.iter().map(|wb| (wb.bsatn.as_ref(), &wb.row)).collect(); + Self { + inserts: insert_map, + deletes: Default::default(), + update_deletes: Vec::new(), + update_inserts: Vec::new(), + } + } + /// Returns the applied diff restructured /// with row updates where deletes and inserts are found according to `derive_pk`. pub fn with_updates_by_pk(mut self, derive_pk: impl Fn(&Row) -> &Pk) -> Self { diff --git a/sdks/rust/src/lib.rs b/sdks/rust/src/lib.rs index fe9b2d1fc71..d99447d440f 100644 --- a/sdks/rust/src/lib.rs +++ b/sdks/rust/src/lib.rs @@ -29,7 +29,7 @@ pub use db_connection::DbConnectionBuilder; pub use db_context::DbContext; pub use error::{Error, Result}; pub use event::{Event, ReducerEvent, Status}; -pub use table::{Table, TableWithPrimaryKey}; +pub use table::{EventTable, Table, TableWithPrimaryKey}; pub use spacetime_module::SubscriptionHandle; pub use spacetimedb_client_api_messages::websocket::v1::Compression; @@ -61,7 +61,7 @@ pub mod __codegen { }; pub use crate::subscription::{OnEndedCallback, SubscriptionBuilder, SubscriptionHandleImpl}; pub use crate::{ - ConnectionId, DbConnectionBuilder, DbContext, Event, Identity, ReducerEvent, ScheduleAt, Table, + ConnectionId, DbConnectionBuilder, DbContext, Event, EventTable, Identity, ReducerEvent, ScheduleAt, Table, TableWithPrimaryKey, TimeDuration, Timestamp, Uuid, }; } diff --git a/sdks/rust/src/spacetime_module.rs b/sdks/rust/src/spacetime_module.rs index f4f69c7ffae..e50a3a90dfd 100644 --- a/sdks/rust/src/spacetime_module.rs +++ b/sdks/rust/src/spacetime_module.rs @@ -245,6 +245,12 @@ impl TableUpdate { pub(crate) fn is_empty(&self) -> bool { self.inserts.is_empty() && self.deletes.is_empty() } + + /// For event tables: convert inserts directly to a `TableAppliedDiff` + /// without touching the client cache. Each insert fires `on_insert` callbacks. + pub fn into_event_diff(&self) -> crate::client_cache::TableAppliedDiff<'_, Row> { + crate::client_cache::TableAppliedDiff::from_event_inserts(&self.inserts) + } } impl TableUpdate { @@ -254,7 +260,9 @@ impl TableUpdate { let mut deletes = Vec::new(); for update in raw_updates.rows { match update { - ws::v2::TableUpdateRows::EventTable(_) => todo!("Event tables"), + ws::v2::TableUpdateRows::EventTable(update) => { + Self::parse_from_row_list(&mut inserts, &update.events)?; + } ws::v2::TableUpdateRows::PersistentTable(update) => { Self::parse_from_row_list(&mut deletes, &update.deletes)?; Self::parse_from_row_list(&mut inserts, &update.inserts)?; diff --git a/sdks/rust/src/table.rs b/sdks/rust/src/table.rs index 2198201cb6e..fe42c613a4a 100644 --- a/sdks/rust/src/table.rs +++ b/sdks/rust/src/table.rs @@ -10,6 +10,8 @@ /// Trait implemented by table handles, which mediate access to tables in the client cache. /// /// Obtain a table handle by calling a method on `ctx.db`, where `ctx` is a `DbConnection` or `EventContext`. +/// +/// For persistent (non-event) tables only. See [`EventTable`] for transient event tables. pub trait Table { /// The type of rows stored in this table. type Row: 'static; @@ -75,3 +77,35 @@ pub trait TableWithPrimaryKey: Table { /// Cancel a callback previously registered by [`Self::on_update`], causing it not to run in the future. fn remove_on_update(&self, callback: Self::UpdateCallbackId); } + +/// Trait for event tables, whose rows are transient and never persisted in the client cache. +/// +/// Event table rows are delivered as inserts but are not stored; +/// only `on_insert` callbacks fire, and `count`/`iter` always reflect an empty table. +/// +/// Obtain a table handle by calling a method on `ctx.db`, where `ctx` is a `DbConnection` or `EventContext`. +pub trait EventTable { + /// The type of rows in this table. + type Row: 'static; + + /// The `EventContext` type generated for the module which defines this table. + type EventContext; + + /// The number of subscribed rows in the client cache (always 0 for event tables). + fn count(&self) -> u64; + + /// An iterator over all the subscribed rows in the client cache (always empty for event tables). + fn iter(&self) -> impl Iterator + '_; + + type InsertCallbackId; + /// Register a callback to run whenever a row is inserted. + /// + /// The returned [`Self::InsertCallbackId`] can be passed to [`Self::remove_on_insert`] + /// to cancel the callback. + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> Self::InsertCallbackId; + /// Cancel a callback previously registered by [`Self::on_insert`], causing it not to run in the future. + fn remove_on_insert(&self, callback: Self::InsertCallbackId); +} From 46327f2b27e70e201f89c19dd46e43b4452af75c Mon Sep 17 00:00:00 2001 From: = Date: Thu, 12 Feb 2026 14:08:21 -0500 Subject: [PATCH 03/24] Add event table integration tests - Add sdk-test-event-table module with event table and reducers - Add event-table-client test binary with generated bindings - Register event-table-client and sdk-test-event-table in workspace - Update test configuration --- Cargo.toml | 2 + .../sdk-test-event-table/.cargo/config.toml | 2 + modules/sdk-test-event-table/Cargo.toml | 10 + modules/sdk-test-event-table/src/lib.rs | 32 + sdks/rust/tests/event-table-client/Cargo.toml | 13 + .../rust/tests/event-table-client/src/main.rs | 245 ++++++ .../emit_multiple_test_events_reducer.rs | 62 ++ .../emit_test_event_reducer.rs | 72 ++ .../src/module_bindings/mod.rs | 796 ++++++++++++++++++ .../src/module_bindings/noop_reducer.rs | 61 ++ .../src/module_bindings/test_event_table.rs | 95 +++ .../src/module_bindings/test_event_type.rs | 46 + sdks/rust/tests/test-client/src/main.rs | 18 +- sdks/rust/tests/test.rs | 38 + 14 files changed, 1481 insertions(+), 11 deletions(-) create mode 100644 modules/sdk-test-event-table/.cargo/config.toml create mode 100644 modules/sdk-test-event-table/Cargo.toml create mode 100644 modules/sdk-test-event-table/src/lib.rs create mode 100644 sdks/rust/tests/event-table-client/Cargo.toml create mode 100644 sdks/rust/tests/event-table-client/src/main.rs create mode 100644 sdks/rust/tests/event-table-client/src/module_bindings/emit_multiple_test_events_reducer.rs create mode 100644 sdks/rust/tests/event-table-client/src/module_bindings/emit_test_event_reducer.rs create mode 100644 sdks/rust/tests/event-table-client/src/module_bindings/mod.rs create mode 100644 sdks/rust/tests/event-table-client/src/module_bindings/noop_reducer.rs create mode 100644 sdks/rust/tests/event-table-client/src/module_bindings/test_event_table.rs create mode 100644 sdks/rust/tests/event-table-client/src/module_bindings/test_event_type.rs diff --git a/Cargo.toml b/Cargo.toml index 4c94938a7db..3f7e3e5679e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,11 +49,13 @@ members = [ "modules/sdk-test-connect-disconnect", "modules/sdk-test-procedure", "modules/sdk-test-view", + "modules/sdk-test-event-table", "sdks/rust/tests/test-client", "sdks/rust/tests/test-counter", "sdks/rust/tests/connect_disconnect_client", "sdks/rust/tests/procedure-client", "sdks/rust/tests/view-client", + "sdks/rust/tests/event-table-client", "tools/ci", "tools/upgrade-version", "tools/license-check", diff --git a/modules/sdk-test-event-table/.cargo/config.toml b/modules/sdk-test-event-table/.cargo/config.toml new file mode 100644 index 00000000000..f4e8c002fc2 --- /dev/null +++ b/modules/sdk-test-event-table/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/modules/sdk-test-event-table/Cargo.toml b/modules/sdk-test-event-table/Cargo.toml new file mode 100644 index 00000000000..3dd8b22dbe4 --- /dev/null +++ b/modules/sdk-test-event-table/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sdk-test-event-table-module" +version = "0.1.0" +edition.workspace = true + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true diff --git a/modules/sdk-test-event-table/src/lib.rs b/modules/sdk-test-event-table/src/lib.rs new file mode 100644 index 00000000000..05181098b52 --- /dev/null +++ b/modules/sdk-test-event-table/src/lib.rs @@ -0,0 +1,32 @@ +use spacetimedb::{ReducerContext, Table}; + +#[spacetimedb::table(name = test_event, public, event)] +pub struct TestEvent { + pub name: String, + pub value: u64, +} + +#[spacetimedb::reducer] +pub fn emit_test_event(ctx: &ReducerContext, name: String, value: u64) { + ctx.db.test_event().insert(TestEvent { name, value }); +} + +#[spacetimedb::reducer] +pub fn emit_multiple_test_events(ctx: &ReducerContext) { + ctx.db.test_event().insert(TestEvent { + name: "a".to_string(), + value: 1, + }); + ctx.db.test_event().insert(TestEvent { + name: "b".to_string(), + value: 2, + }); + ctx.db.test_event().insert(TestEvent { + name: "c".to_string(), + value: 3, + }); +} + +/// A no-op reducer that lets us observe a subsequent transaction. +#[spacetimedb::reducer] +pub fn noop(_ctx: &ReducerContext) {} diff --git a/sdks/rust/tests/event-table-client/Cargo.toml b/sdks/rust/tests/event-table-client/Cargo.toml new file mode 100644 index 00000000000..f6502644119 --- /dev/null +++ b/sdks/rust/tests/event-table-client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "event-table-client" +version.workspace = true +edition.workspace = true + +[dependencies] +spacetimedb-sdk = { path = "../.." } +test-counter = { path = "../test-counter" } +anyhow.workspace = true +env_logger.workspace = true + +[lints] +workspace = true diff --git a/sdks/rust/tests/event-table-client/src/main.rs b/sdks/rust/tests/event-table-client/src/main.rs new file mode 100644 index 00000000000..8b9620f12d1 --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/main.rs @@ -0,0 +1,245 @@ +#[allow(clippy::too_many_arguments)] +#[allow(clippy::large_enum_variant)] +mod module_bindings; + +use module_bindings::*; + +use spacetimedb_sdk::{DbContext, Event, EventTable}; +use std::sync::atomic::{AtomicU32, Ordering}; +use test_counter::TestCounter; + +const LOCALHOST: &str = "http://localhost:3000"; + +fn db_name_or_panic() -> String { + std::env::var("SPACETIME_SDK_TEST_DB_NAME").expect("Failed to read db name from env") +} + +/// Register a panic hook which will exit the process whenever any thread panics. +/// +/// This allows us to fail tests by panicking in callbacks. +fn exit_on_panic() { + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { + default_hook(panic_info); + std::process::exit(1); + })); +} + +macro_rules! assert_eq_or_bail { + ($expected:expr, $found:expr) => {{ + let expected = &$expected; + let found = &$found; + if expected != found { + anyhow::bail!( + "Expected {} => {:?} but found {} => {:?}", + stringify!($expected), + expected, + stringify!($found), + found + ); + } + }}; +} + +fn main() { + env_logger::init(); + exit_on_panic(); + + let test = std::env::args() + .nth(1) + .expect("Pass a test name as a command-line argument to the test client"); + + match &*test { + "event-table" => exec_event_table(), + "multiple-events" => exec_multiple_events(), + "events-dont-persist" => exec_events_dont_persist(), + "v1-rejects-event-table" => exec_v1_rejects_event_table(), + _ => panic!("Unknown test: {test}"), + } +} + +fn connect_then( + test_counter: &std::sync::Arc, + callback: impl FnOnce(&DbConnection) + Send + 'static, +) -> DbConnection { + let connected_result = test_counter.add_test("on_connect"); + let name = db_name_or_panic(); + let conn = DbConnection::builder() + .with_module_name(name) + .with_uri(LOCALHOST) + .on_connect(|ctx, _, _| { + callback(ctx); + connected_result(Ok(())); + }) + .on_connect_error(|_ctx, error| panic!("Connect errored: {error:?}")) + .build() + .unwrap(); + conn.run_threaded(); + conn +} + +fn subscribe_these_then( + ctx: &impl RemoteDbContext, + queries: &[&str], + callback: impl FnOnce(&SubscriptionEventContext) + Send + 'static, +) { + ctx.subscription_builder() + .on_applied(callback) + .on_error(|_ctx, error| panic!("Subscription errored: {error:?}")) + .subscribe(queries); +} + +fn exec_event_table() { + let test_counter = TestCounter::new(); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + subscribe_these_then(ctx, &["SELECT * FROM test_event;"], move |ctx| { + // Event table should be empty on subscription applied + assert_eq!(0usize, ctx.db.test_event().iter().count()); + + let mut on_insert_result = Some(test_counter.add_test("event-table-on-insert")); + + ctx.db.test_event().on_insert(move |ctx, row| { + if let Some(set_result) = on_insert_result.take() { + let run_checks = || { + assert_eq_or_bail!("hello", row.name); + assert_eq_or_bail!(42u64, row.value); + + let Event::Reducer(reducer_event) = &ctx.event else { + anyhow::bail!("Expected a reducer event"); + }; + anyhow::ensure!( + matches!(reducer_event.reducer, Reducer::EmitTestEvent { .. }), + "Unexpected Reducer variant {:?}", + reducer_event.reducer, + ); + + // Event table rows are not cached + assert_eq_or_bail!(0u64, ctx.db.test_event().count()); + assert_eq_or_bail!(0usize, ctx.db.test_event().iter().count()); + + Ok(()) + }; + set_result(run_checks()); + } + }); + + ctx.reducers.emit_test_event("hello".to_string(), 42).unwrap(); + }); + } + }); + + test_counter.wait_for_all(); +} + +/// Test that multiple events emitted in a single reducer call all arrive as inserts. +fn exec_multiple_events() { + let test_counter = TestCounter::new(); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + subscribe_these_then(ctx, &["SELECT * FROM test_event;"], move |ctx| { + assert_eq!(0usize, ctx.db.test_event().iter().count()); + + let received = std::sync::Arc::new(AtomicU32::new(0)); + let result = test_counter.add_test("multiple-events"); + let result = std::sync::Mutex::new(Some(result)); + + ctx.db.test_event().on_insert({ + let received = received.clone(); + move |_ctx, _row| { + let count = received.fetch_add(1, Ordering::SeqCst) + 1; + if count == 3 { + let set_result = result.lock().unwrap().take().unwrap(); + set_result(Ok(())); + } + } + }); + + ctx.reducers.emit_multiple_test_events().unwrap(); + }); + } + }); + + test_counter.wait_for_all(); +} + +/// Test that event table rows don't persist across transactions. +/// Emit events, then call a no-op reducer. After the no-op completes, +/// verify we didn't receive any additional event inserts. +fn exec_events_dont_persist() { + let test_counter = TestCounter::new(); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + subscribe_these_then(ctx, &["SELECT * FROM test_event;"], move |ctx| { + assert_eq!(0usize, ctx.db.test_event().iter().count()); + + let insert_count = std::sync::Arc::new(AtomicU32::new(0)); + let noop_result = test_counter.add_test("events-dont-persist"); + let noop_result = std::sync::Mutex::new(Some(noop_result)); + + ctx.db.test_event().on_insert({ + let insert_count = insert_count.clone(); + move |_ctx, _row| { + insert_count.fetch_add(1, Ordering::SeqCst); + } + }); + + ctx.reducers.emit_test_event("hello".to_string(), 42).unwrap(); + + // After the noop reducer completes, the insert count should + // still be 1 from the emit_test_event call — no stale events. + ctx.reducers + .noop_then({ + let insert_count = insert_count.clone(); + move |_ctx, _result| { + let set_result = noop_result.lock().unwrap().take().unwrap(); + let count = insert_count.load(Ordering::SeqCst); + if count == 1 { + set_result(Ok(())); + } else { + set_result(Err(anyhow::anyhow!("Expected 1 event insert, but got {count}"))); + } + } + }) + .unwrap(); + }); + } + }); + + test_counter.wait_for_all(); +} + +/// Test that v1 WebSocket clients are rejected when subscribing to event tables. +/// The server should return a subscription error directing the developer to upgrade. +fn exec_v1_rejects_event_table() { + let test_counter = TestCounter::new(); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + let error_result = test_counter.add_test("v1-rejects-event-table"); + + ctx.subscription_builder() + .on_applied(move |_ctx: &SubscriptionEventContext| { + panic!("Subscription to event table should not succeed over v1"); + }) + .on_error(move |_ctx, error| { + let msg = format!("{error:?}"); + if msg.contains("v2") || msg.contains("upgrade") || msg.contains("Upgrade") { + error_result(Ok(())); + } else { + error_result(Err(anyhow::anyhow!("Expected error about v2/upgrade, got: {msg}"))); + } + }) + .subscribe(["SELECT * FROM test_event;"]); + } + }); + + test_counter.wait_for_all(); +} diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/emit_multiple_test_events_reducer.rs b/sdks/rust/tests/event-table-client/src/module_bindings/emit_multiple_test_events_reducer.rs new file mode 100644 index 00000000000..2ac3bcf9e2d --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/module_bindings/emit_multiple_test_events_reducer.rs @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct EmitMultipleTestEventsArgs {} + +impl From for super::Reducer { + fn from(args: EmitMultipleTestEventsArgs) -> Self { + Self::EmitMultipleTestEvents + } +} + +impl __sdk::InModule for EmitMultipleTestEventsArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `emit_multiple_test_events`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait emit_multiple_test_events { + /// Request that the remote module invoke the reducer `emit_multiple_test_events` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and this method provides no way to listen for its completion status. + /// /// Use [`emit_multiple_test_events:emit_multiple_test_events_then`] to run a callback after the reducer completes. + fn emit_multiple_test_events(&self) -> __sdk::Result<()> { + self.emit_multiple_test_events_then(|_, _| {}) + } + + /// Request that the remote module invoke the reducer `emit_multiple_test_events` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn emit_multiple_test_events_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; +} + +impl emit_multiple_test_events for super::RemoteReducers { + fn emit_multiple_test_events_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp + .invoke_reducer_with_callback(EmitMultipleTestEventsArgs {}, callback) + } +} diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/emit_test_event_reducer.rs b/sdks/rust/tests/event-table-client/src/module_bindings/emit_test_event_reducer.rs new file mode 100644 index 00000000000..d0e158e9e38 --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/module_bindings/emit_test_event_reducer.rs @@ -0,0 +1,72 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct EmitTestEventArgs { + pub name: String, + pub value: u64, +} + +impl From for super::Reducer { + fn from(args: EmitTestEventArgs) -> Self { + Self::EmitTestEvent { + name: args.name, + value: args.value, + } + } +} + +impl __sdk::InModule for EmitTestEventArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `emit_test_event`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait emit_test_event { + /// Request that the remote module invoke the reducer `emit_test_event` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and this method provides no way to listen for its completion status. + /// /// Use [`emit_test_event:emit_test_event_then`] to run a callback after the reducer completes. + fn emit_test_event(&self, name: String, value: u64) -> __sdk::Result<()> { + self.emit_test_event_then(name, value, |_, _| {}) + } + + /// Request that the remote module invoke the reducer `emit_test_event` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn emit_test_event_then( + &self, + name: String, + value: u64, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; +} + +impl emit_test_event for super::RemoteReducers { + fn emit_test_event_then( + &self, + name: String, + value: u64, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp + .invoke_reducer_with_callback(EmitTestEventArgs { name, value }, callback) + } +} diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs b/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs new file mode 100644 index 00000000000..7916467866b --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs @@ -0,0 +1,796 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.12.0 (commit c63169838eff3d5c486ea11a3eb1610f80518e0e). + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +pub mod emit_multiple_test_events_reducer; +pub mod emit_test_event_reducer; +pub mod noop_reducer; +pub mod test_event_table; +pub mod test_event_type; + +pub use emit_multiple_test_events_reducer::emit_multiple_test_events; +pub use emit_test_event_reducer::emit_test_event; +pub use noop_reducer::noop; +pub use test_event_table::*; +pub use test_event_type::TestEvent; + +#[derive(Clone, PartialEq, Debug)] + +/// One of the reducers defined by this module. +/// +/// Contained within a [`__sdk::ReducerEvent`] in [`EventContext`]s for reducer events +/// to indicate which reducer caused the event. + +pub enum Reducer { + EmitMultipleTestEvents, + EmitTestEvent { name: String, value: u64 }, + Noop, +} + +impl __sdk::InModule for Reducer { + type Module = RemoteModule; +} + +impl __sdk::Reducer for Reducer { + fn reducer_name(&self) -> &'static str { + match self { + Reducer::EmitMultipleTestEvents => "emit_multiple_test_events", + Reducer::EmitTestEvent { .. } => "emit_test_event", + Reducer::Noop => "noop", + _ => unreachable!(), + } + } + #[allow(clippy::clone_on_copy)] + fn args_bsatn(&self) -> Result, __sats::bsatn::EncodeError> { + match self { + Reducer::EmitMultipleTestEvents => { + __sats::bsatn::to_vec(&emit_multiple_test_events_reducer::EmitMultipleTestEventsArgs {}) + } + Reducer::EmitTestEvent { name, value } => { + __sats::bsatn::to_vec(&emit_test_event_reducer::EmitTestEventArgs { + name: name.clone(), + value: value.clone(), + }) + } + Reducer::Noop => __sats::bsatn::to_vec(&noop_reducer::NoopArgs {}), + _ => unreachable!(), + } + } +} + +#[derive(Default)] +#[allow(non_snake_case)] +#[doc(hidden)] +pub struct DbUpdate { + test_event: __sdk::TableUpdate, +} + +impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { + type Error = __sdk::Error; + fn try_from(raw: __ws::v2::TransactionUpdate) -> Result { + let mut db_update = DbUpdate::default(); + for table_update in __sdk::transaction_update_iter_table_updates(raw) { + match &table_update.table_name[..] { + "test_event" => db_update + .test_event + .append(test_event_table::parse_table_update(table_update)?), + + unknown => { + return Err(__sdk::InternalError::unknown_name("table", unknown, "DatabaseUpdate").into()); + } + } + } + Ok(db_update) + } +} + +impl __sdk::InModule for DbUpdate { + type Module = RemoteModule; +} + +impl __sdk::DbUpdate for DbUpdate { + fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache) -> AppliedDiff<'_> { + let mut diff = AppliedDiff::default(); + + diff.test_event = self.test_event.into_event_diff(); + + diff + } + fn parse_initial_rows(raw: __ws::v2::QueryRows) -> __sdk::Result { + let mut db_update = DbUpdate::default(); + for table_rows in raw.tables { + match &table_rows.table[..] { + "test_event" => db_update + .test_event + .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), + unknown => { + return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); + } + } + } + Ok(db_update) + } + fn parse_unsubscribe_rows(raw: __ws::v2::QueryRows) -> __sdk::Result { + let mut db_update = DbUpdate::default(); + for table_rows in raw.tables { + match &table_rows.table[..] { + "test_event" => db_update + .test_event + .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), + unknown => { + return Err(__sdk::InternalError::unknown_name("table", unknown, "QueryRows").into()); + } + } + } + Ok(db_update) + } +} + +#[derive(Default)] +#[allow(non_snake_case)] +#[doc(hidden)] +pub struct AppliedDiff<'r> { + test_event: __sdk::TableAppliedDiff<'r, TestEvent>, + __unused: std::marker::PhantomData<&'r ()>, +} + +impl __sdk::InModule for AppliedDiff<'_> { + type Module = RemoteModule; +} + +impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { + fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks) { + callbacks.invoke_table_row_callbacks::("test_event", &self.test_event, event); + } +} + +#[doc(hidden)] +pub struct RemoteModule; + +impl __sdk::InModule for RemoteModule { + type Module = Self; +} + +/// The `reducers` field of [`EventContext`] and [`DbConnection`], +/// with methods provided by extension traits for each reducer defined by the module. +pub struct RemoteReducers { + imp: __sdk::DbContextImpl, +} + +impl __sdk::InModule for RemoteReducers { + type Module = RemoteModule; +} + +/// The `procedures` field of [`DbConnection`] and other [`DbContext`] types, +/// with methods provided by extension traits for each procedure defined by the module. +pub struct RemoteProcedures { + imp: __sdk::DbContextImpl, +} + +impl __sdk::InModule for RemoteProcedures { + type Module = RemoteModule; +} + +/// The `db` field of [`EventContext`] and [`DbConnection`], +/// with methods provided by extension traits for each table defined by the module. +pub struct RemoteTables { + imp: __sdk::DbContextImpl, +} + +impl __sdk::InModule for RemoteTables { + type Module = RemoteModule; +} + +/// A connection to a remote module, including a materialized view of a subset of the database. +/// +/// Connect to a remote module by calling [`DbConnection::builder`] +/// and using the [`__sdk::DbConnectionBuilder`] builder-pattern constructor. +/// +/// You must explicitly advance the connection by calling any one of: +/// +/// - [`DbConnection::frame_tick`]. +/// - [`DbConnection::run_threaded`]. +/// - [`DbConnection::run_async`]. +/// - [`DbConnection::advance_one_message`]. +/// - [`DbConnection::advance_one_message_blocking`]. +/// - [`DbConnection::advance_one_message_async`]. +/// +/// Which of these methods you should call depends on the specific needs of your application, +/// but you must call one of them, or else the connection will never progress. +pub struct DbConnection { + /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`]. + pub db: RemoteTables, + /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. + pub reducers: RemoteReducers, + #[doc(hidden)] + + /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. + pub procedures: RemoteProcedures, + + imp: __sdk::DbContextImpl, +} + +impl __sdk::InModule for DbConnection { + type Module = RemoteModule; +} + +impl __sdk::DbContext for DbConnection { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } + + fn is_active(&self) -> bool { + self.imp.is_active() + } + + fn disconnect(&self) -> __sdk::Result<()> { + self.imp.disconnect() + } + + type SubscriptionBuilder = __sdk::SubscriptionBuilder; + + fn subscription_builder(&self) -> Self::SubscriptionBuilder { + __sdk::SubscriptionBuilder::new(&self.imp) + } + + fn try_identity(&self) -> Option<__sdk::Identity> { + self.imp.try_identity() + } + fn connection_id(&self) -> __sdk::ConnectionId { + self.imp.connection_id() + } + fn try_connection_id(&self) -> Option<__sdk::ConnectionId> { + self.imp.try_connection_id() + } +} + +impl DbConnection { + /// Builder-pattern constructor for a connection to a remote module. + /// + /// See [`__sdk::DbConnectionBuilder`] for required and optional configuration for the new connection. + pub fn builder() -> __sdk::DbConnectionBuilder { + __sdk::DbConnectionBuilder::new() + } + + /// If any WebSocket messages are waiting, process one of them. + /// + /// Returns `true` if a message was processed, or `false` if the queue is empty. + /// Callers should invoke this message in a loop until it returns `false` + /// or for as much time is available to process messages. + /// + /// Returns an error if the connection is disconnected. + /// If the disconnection in question was normal, + /// i.e. the result of a call to [`__sdk::DbContext::disconnect`], + /// the returned error will be downcastable to [`__sdk::DisconnectedError`]. + /// + /// This is a low-level primitive exposed for power users who need significant control over scheduling. + /// Most applications should call [`Self::frame_tick`] each frame + /// to fully exhaust the queue whenever time is available. + pub fn advance_one_message(&self) -> __sdk::Result { + self.imp.advance_one_message() + } + + /// Process one WebSocket message, potentially blocking the current thread until one is received. + /// + /// Returns an error if the connection is disconnected. + /// If the disconnection in question was normal, + /// i.e. the result of a call to [`__sdk::DbContext::disconnect`], + /// the returned error will be downcastable to [`__sdk::DisconnectedError`]. + /// + /// This is a low-level primitive exposed for power users who need significant control over scheduling. + /// Most applications should call [`Self::run_threaded`] to spawn a thread + /// which advances the connection automatically. + pub fn advance_one_message_blocking(&self) -> __sdk::Result<()> { + self.imp.advance_one_message_blocking() + } + + /// Process one WebSocket message, `await`ing until one is received. + /// + /// Returns an error if the connection is disconnected. + /// If the disconnection in question was normal, + /// i.e. the result of a call to [`__sdk::DbContext::disconnect`], + /// the returned error will be downcastable to [`__sdk::DisconnectedError`]. + /// + /// This is a low-level primitive exposed for power users who need significant control over scheduling. + /// Most applications should call [`Self::run_async`] to run an `async` loop + /// which advances the connection when polled. + pub async fn advance_one_message_async(&self) -> __sdk::Result<()> { + self.imp.advance_one_message_async().await + } + + /// Process all WebSocket messages waiting in the queue, + /// then return without `await`ing or blocking the current thread. + pub fn frame_tick(&self) -> __sdk::Result<()> { + self.imp.frame_tick() + } + + /// Spawn a thread which processes WebSocket messages as they are received. + pub fn run_threaded(&self) -> std::thread::JoinHandle<()> { + self.imp.run_threaded() + } + + /// Run an `async` loop which processes WebSocket messages when polled. + pub async fn run_async(&self) -> __sdk::Result<()> { + self.imp.run_async().await + } +} + +impl __sdk::DbConnection for DbConnection { + fn new(imp: __sdk::DbContextImpl) -> Self { + Self { + db: RemoteTables { imp: imp.clone() }, + reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, + imp, + } + } +} + +/// A handle on a subscribed query. +// TODO: Document this better after implementing the new subscription API. +#[derive(Clone)] +pub struct SubscriptionHandle { + imp: __sdk::SubscriptionHandleImpl, +} + +impl __sdk::InModule for SubscriptionHandle { + type Module = RemoteModule; +} + +impl __sdk::SubscriptionHandle for SubscriptionHandle { + fn new(imp: __sdk::SubscriptionHandleImpl) -> Self { + Self { imp } + } + + /// Returns true if this subscription has been terminated due to an unsubscribe call or an error. + fn is_ended(&self) -> bool { + self.imp.is_ended() + } + + /// Returns true if this subscription has been applied and has not yet been unsubscribed. + fn is_active(&self) -> bool { + self.imp.is_active() + } + + /// Unsubscribe from the query controlled by this `SubscriptionHandle`, + /// then run `on_end` when its rows are removed from the client cache. + fn unsubscribe_then(self, on_end: __sdk::OnEndedCallback) -> __sdk::Result<()> { + self.imp.unsubscribe_then(Some(on_end)) + } + + fn unsubscribe(self) -> __sdk::Result<()> { + self.imp.unsubscribe_then(None) + } +} + +/// Alias trait for a [`__sdk::DbContext`] connected to this module, +/// with that trait's associated types bounded to this module's concrete types. +/// +/// Users can use this trait as a boundary on definitions which should accept +/// either a [`DbConnection`] or an [`EventContext`] and operate on either. +pub trait RemoteDbContext: + __sdk::DbContext< + DbView = RemoteTables, + Reducers = RemoteReducers, + SubscriptionBuilder = __sdk::SubscriptionBuilder, +> +{ +} +impl< + Ctx: __sdk::DbContext< + DbView = RemoteTables, + Reducers = RemoteReducers, + SubscriptionBuilder = __sdk::SubscriptionBuilder, + >, + > RemoteDbContext for Ctx +{ +} + +/// An [`__sdk::DbContext`] augmented with a [`__sdk::Event`], +/// passed to [`__sdk::Table::on_insert`], [`__sdk::Table::on_delete`] and [`__sdk::TableWithPrimaryKey::on_update`] callbacks. +pub struct EventContext { + /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`]. + pub db: RemoteTables, + /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. + pub reducers: RemoteReducers, + /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. + pub procedures: RemoteProcedures, + /// The event which caused these callbacks to run. + pub event: __sdk::Event, + imp: __sdk::DbContextImpl, +} + +impl __sdk::AbstractEventContext for EventContext { + type Event = __sdk::Event; + fn event(&self) -> &Self::Event { + &self.event + } + fn new(imp: __sdk::DbContextImpl, event: Self::Event) -> Self { + Self { + db: RemoteTables { imp: imp.clone() }, + reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, + event, + imp, + } + } +} + +impl __sdk::InModule for EventContext { + type Module = RemoteModule; +} + +impl __sdk::DbContext for EventContext { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } + + fn is_active(&self) -> bool { + self.imp.is_active() + } + + fn disconnect(&self) -> __sdk::Result<()> { + self.imp.disconnect() + } + + type SubscriptionBuilder = __sdk::SubscriptionBuilder; + + fn subscription_builder(&self) -> Self::SubscriptionBuilder { + __sdk::SubscriptionBuilder::new(&self.imp) + } + + fn try_identity(&self) -> Option<__sdk::Identity> { + self.imp.try_identity() + } + fn connection_id(&self) -> __sdk::ConnectionId { + self.imp.connection_id() + } + fn try_connection_id(&self) -> Option<__sdk::ConnectionId> { + self.imp.try_connection_id() + } +} + +impl __sdk::EventContext for EventContext {} + +/// An [`__sdk::DbContext`] augmented with a [`__sdk::ReducerEvent`], +/// passed to on-reducer callbacks. +pub struct ReducerEventContext { + /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`]. + pub db: RemoteTables, + /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. + pub reducers: RemoteReducers, + /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. + pub procedures: RemoteProcedures, + /// The event which caused these callbacks to run. + pub event: __sdk::ReducerEvent, + imp: __sdk::DbContextImpl, +} + +impl __sdk::AbstractEventContext for ReducerEventContext { + type Event = __sdk::ReducerEvent; + fn event(&self) -> &Self::Event { + &self.event + } + fn new(imp: __sdk::DbContextImpl, event: Self::Event) -> Self { + Self { + db: RemoteTables { imp: imp.clone() }, + reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, + event, + imp, + } + } +} + +impl __sdk::InModule for ReducerEventContext { + type Module = RemoteModule; +} + +impl __sdk::DbContext for ReducerEventContext { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } + + fn is_active(&self) -> bool { + self.imp.is_active() + } + + fn disconnect(&self) -> __sdk::Result<()> { + self.imp.disconnect() + } + + type SubscriptionBuilder = __sdk::SubscriptionBuilder; + + fn subscription_builder(&self) -> Self::SubscriptionBuilder { + __sdk::SubscriptionBuilder::new(&self.imp) + } + + fn try_identity(&self) -> Option<__sdk::Identity> { + self.imp.try_identity() + } + fn connection_id(&self) -> __sdk::ConnectionId { + self.imp.connection_id() + } + fn try_connection_id(&self) -> Option<__sdk::ConnectionId> { + self.imp.try_connection_id() + } +} + +impl __sdk::ReducerEventContext for ReducerEventContext {} + +/// An [`__sdk::DbContext`] passed to procedure callbacks. +pub struct ProcedureEventContext { + /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`]. + pub db: RemoteTables, + /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. + pub reducers: RemoteReducers, + /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. + pub procedures: RemoteProcedures, + imp: __sdk::DbContextImpl, +} + +impl __sdk::AbstractEventContext for ProcedureEventContext { + type Event = (); + fn event(&self) -> &Self::Event { + &() + } + fn new(imp: __sdk::DbContextImpl, _event: Self::Event) -> Self { + Self { + db: RemoteTables { imp: imp.clone() }, + reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, + imp, + } + } +} + +impl __sdk::InModule for ProcedureEventContext { + type Module = RemoteModule; +} + +impl __sdk::DbContext for ProcedureEventContext { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } + + fn is_active(&self) -> bool { + self.imp.is_active() + } + + fn disconnect(&self) -> __sdk::Result<()> { + self.imp.disconnect() + } + + type SubscriptionBuilder = __sdk::SubscriptionBuilder; + + fn subscription_builder(&self) -> Self::SubscriptionBuilder { + __sdk::SubscriptionBuilder::new(&self.imp) + } + + fn try_identity(&self) -> Option<__sdk::Identity> { + self.imp.try_identity() + } + fn connection_id(&self) -> __sdk::ConnectionId { + self.imp.connection_id() + } + fn try_connection_id(&self) -> Option<__sdk::ConnectionId> { + self.imp.try_connection_id() + } +} + +impl __sdk::ProcedureEventContext for ProcedureEventContext {} + +/// An [`__sdk::DbContext`] passed to [`__sdk::SubscriptionBuilder::on_applied`] and [`SubscriptionHandle::unsubscribe_then`] callbacks. +pub struct SubscriptionEventContext { + /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`]. + pub db: RemoteTables, + /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. + pub reducers: RemoteReducers, + /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. + pub procedures: RemoteProcedures, + imp: __sdk::DbContextImpl, +} + +impl __sdk::AbstractEventContext for SubscriptionEventContext { + type Event = (); + fn event(&self) -> &Self::Event { + &() + } + fn new(imp: __sdk::DbContextImpl, _event: Self::Event) -> Self { + Self { + db: RemoteTables { imp: imp.clone() }, + reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, + imp, + } + } +} + +impl __sdk::InModule for SubscriptionEventContext { + type Module = RemoteModule; +} + +impl __sdk::DbContext for SubscriptionEventContext { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } + + fn is_active(&self) -> bool { + self.imp.is_active() + } + + fn disconnect(&self) -> __sdk::Result<()> { + self.imp.disconnect() + } + + type SubscriptionBuilder = __sdk::SubscriptionBuilder; + + fn subscription_builder(&self) -> Self::SubscriptionBuilder { + __sdk::SubscriptionBuilder::new(&self.imp) + } + + fn try_identity(&self) -> Option<__sdk::Identity> { + self.imp.try_identity() + } + fn connection_id(&self) -> __sdk::ConnectionId { + self.imp.connection_id() + } + fn try_connection_id(&self) -> Option<__sdk::ConnectionId> { + self.imp.try_connection_id() + } +} + +impl __sdk::SubscriptionEventContext for SubscriptionEventContext {} + +/// An [`__sdk::DbContext`] augmented with a [`__sdk::Error`], +/// passed to [`__sdk::DbConnectionBuilder::on_disconnect`], [`__sdk::DbConnectionBuilder::on_connect_error`] and [`__sdk::SubscriptionBuilder::on_error`] callbacks. +pub struct ErrorContext { + /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`]. + pub db: RemoteTables, + /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. + pub reducers: RemoteReducers, + /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. + pub procedures: RemoteProcedures, + /// The event which caused these callbacks to run. + pub event: Option<__sdk::Error>, + imp: __sdk::DbContextImpl, +} + +impl __sdk::AbstractEventContext for ErrorContext { + type Event = Option<__sdk::Error>; + fn event(&self) -> &Self::Event { + &self.event + } + fn new(imp: __sdk::DbContextImpl, event: Self::Event) -> Self { + Self { + db: RemoteTables { imp: imp.clone() }, + reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, + event, + imp, + } + } +} + +impl __sdk::InModule for ErrorContext { + type Module = RemoteModule; +} + +impl __sdk::DbContext for ErrorContext { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } + + fn is_active(&self) -> bool { + self.imp.is_active() + } + + fn disconnect(&self) -> __sdk::Result<()> { + self.imp.disconnect() + } + + type SubscriptionBuilder = __sdk::SubscriptionBuilder; + + fn subscription_builder(&self) -> Self::SubscriptionBuilder { + __sdk::SubscriptionBuilder::new(&self.imp) + } + + fn try_identity(&self) -> Option<__sdk::Identity> { + self.imp.try_identity() + } + fn connection_id(&self) -> __sdk::ConnectionId { + self.imp.connection_id() + } + fn try_connection_id(&self) -> Option<__sdk::ConnectionId> { + self.imp.try_connection_id() + } +} + +impl __sdk::ErrorContext for ErrorContext {} + +impl __sdk::SpacetimeModule for RemoteModule { + type DbConnection = DbConnection; + type EventContext = EventContext; + type ReducerEventContext = ReducerEventContext; + type ProcedureEventContext = ProcedureEventContext; + type SubscriptionEventContext = SubscriptionEventContext; + type ErrorContext = ErrorContext; + type Reducer = Reducer; + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + type DbUpdate = DbUpdate; + type AppliedDiff<'r> = AppliedDiff<'r>; + type SubscriptionHandle = SubscriptionHandle; + type QueryBuilder = __sdk::QueryBuilder; + + fn register_tables(client_cache: &mut __sdk::ClientCache) { + test_event_table::register_table(client_cache); + } + + const ALL_TABLE_NAMES: &'static [&'static str] = &["test_event"]; +} diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/noop_reducer.rs b/sdks/rust/tests/event-table-client/src/module_bindings/noop_reducer.rs new file mode 100644 index 00000000000..f999e055148 --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/module_bindings/noop_reducer.rs @@ -0,0 +1,61 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct NoopArgs {} + +impl From for super::Reducer { + fn from(args: NoopArgs) -> Self { + Self::Noop + } +} + +impl __sdk::InModule for NoopArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `noop`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait noop { + /// Request that the remote module invoke the reducer `noop` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and this method provides no way to listen for its completion status. + /// /// Use [`noop:noop_then`] to run a callback after the reducer completes. + fn noop(&self) -> __sdk::Result<()> { + self.noop_then(|_, _| {}) + } + + /// Request that the remote module invoke the reducer `noop` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn noop_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; +} + +impl noop for super::RemoteReducers { + fn noop_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(NoopArgs {}, callback) + } +} diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/test_event_table.rs b/sdks/rust/tests/event-table-client/src/module_bindings/test_event_table.rs new file mode 100644 index 00000000000..b6d14f66674 --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/module_bindings/test_event_table.rs @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::test_event_type::TestEvent; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `test_event`. +/// +/// Obtain a handle from the [`TestEventTableAccess::test_event`] method on [`super::RemoteTables`], +/// like `ctx.db.test_event()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.test_event().on_insert(...)`. +pub struct TestEventTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `test_event`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait TestEventTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`TestEventTableHandle`], which mediates access to the table `test_event`. + fn test_event(&self) -> TestEventTableHandle<'_>; +} + +impl TestEventTableAccess for super::RemoteTables { + fn test_event(&self) -> TestEventTableHandle<'_> { + TestEventTableHandle { + imp: self.imp.get_table::("test_event"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct TestEventInsertCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::EventTable for TestEventTableHandle<'ctx> { + type Row = TestEvent; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = TestEventInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> TestEventInsertCallbackId { + TestEventInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: TestEventInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("test_event"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + +#[allow(non_camel_case_types)] +/// Extension trait for query builder access to the table `TestEvent`. +/// +/// Implemented for [`__sdk::QueryTableAccessor`]. +pub trait test_eventQueryTableAccess { + #[allow(non_snake_case)] + /// Get a query builder for the table `TestEvent`. + fn test_event(&self) -> __sdk::__query_builder::Table; +} + +impl test_eventQueryTableAccess for __sdk::QueryTableAccessor { + fn test_event(&self) -> __sdk::__query_builder::Table { + __sdk::__query_builder::Table::new("test_event") + } +} diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/test_event_type.rs b/sdks/rust/tests/event-table-client/src/module_bindings/test_event_type.rs new file mode 100644 index 00000000000..03025351b0a --- /dev/null +++ b/sdks/rust/tests/event-table-client/src/module_bindings/test_event_type.rs @@ -0,0 +1,46 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct TestEvent { + pub name: String, + pub value: u64, +} + +impl __sdk::InModule for TestEvent { + type Module = super::RemoteModule; +} + +/// Column accessor struct for the table `TestEvent`. +/// +/// Provides typed access to columns for query building. +pub struct TestEventCols { + pub name: __sdk::__query_builder::Col, + pub value: __sdk::__query_builder::Col, +} + +impl __sdk::__query_builder::HasCols for TestEvent { + type Cols = TestEventCols; + fn cols(table_name: &'static str) -> Self::Cols { + TestEventCols { + name: __sdk::__query_builder::Col::new(table_name, "name"), + value: __sdk::__query_builder::Col::new(table_name, "value"), + } + } +} + +/// Indexed column accessor struct for the table `TestEvent`. +/// +/// Provides typed access to indexed columns for query building. +pub struct TestEventIxCols {} + +impl __sdk::__query_builder::HasIxCols for TestEvent { + type IxCols = TestEventIxCols; + fn ix_cols(table_name: &'static str) -> Self::IxCols { + TestEventIxCols {} + } +} diff --git a/sdks/rust/tests/test-client/src/main.rs b/sdks/rust/tests/test-client/src/main.rs index 53b9c5e5606..8429d1fa9a5 100644 --- a/sdks/rust/tests/test-client/src/main.rs +++ b/sdks/rust/tests/test-client/src/main.rs @@ -1010,7 +1010,7 @@ fn exec_fail_reducer() { let test_counter = TestCounter::new(); let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); let reducer_success_result = test_counter.add_test("reducer-callback-success"); - let reducer_fail_result = test_counter.add_test("reducer-callback-failure"); + let reducer_fail_result = test_counter.add_test("reducer-callback-fail"); let connection = connect(&test_counter); @@ -1061,16 +1061,12 @@ fn exec_fail_reducer() { ctx.reducers .insert_pk_u_8_then(key, fail_data, move |ctx, status| { let run_checks = || { - if let Ok(Ok(())) = &status { - anyhow::bail!( - "Expected reducer `insert_pk_u_8` to error or panic, but got a successful return" - ) + match &status { + Ok(Err(_err_msg)) => {} + other => anyhow::bail!("Expected reducer error but got {other:?}"), } - - if matches!(ctx.event.status, Status::Committed) { - anyhow::bail!( - "Expected reducer `insert_pk_u_8` to error or panic, but got a `Status::Committed`" - ); + if !matches!(ctx.event.status, Status::Err(_)) { + anyhow::bail!("Unexpected status. Expected Err but found {:?}", ctx.event.status); } let expected_reducer = Reducer::InsertPkU8 { n: key, @@ -1869,7 +1865,7 @@ fn exec_subscribe_all_select_star() { sub_applied_nothing_result(assert_all_tables_empty(ctx)); } }) - .on_error(|_, e| panic!("Subscription error: {e:?}")) + .on_error(|_, _| panic!("Subscription error")) .subscribe_to_all_tables(); test_counter.wait_for_all(); diff --git a/sdks/rust/tests/test.rs b/sdks/rust/tests/test.rs index afc1e83487b..d6c5cac38ae 100644 --- a/sdks/rust/tests/test.rs +++ b/sdks/rust/tests/test.rs @@ -316,6 +316,44 @@ declare_tests_with_suffix!(typescript, "-ts"); declare_tests_with_suffix!(csharp, "-cs"); declare_tests_with_suffix!(cpp, "-cpp"); +/// Tests of event table functionality, using <./event-table-client> and <../../../modules/sdk-test>. +/// +/// These are separate from the existing client because as of writing (2026-02-07), +/// we do not have event table support in all of the module languages we have tested. +mod event_table_tests { + use spacetimedb_testing::sdk::Test; + + const MODULE: &str = "sdk-test-event-table"; + const CLIENT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/event-table-client"); + + fn make_test(subcommand: &str) -> Test { + Test::builder() + .with_name(subcommand) + .with_module(MODULE) + .with_client(CLIENT) + .with_language("rust") + .with_bindings_dir("src/module_bindings") + .with_compile_command("cargo build") + .with_run_command(format!("cargo run -- {}", subcommand)) + .build() + } + + #[test] + fn event_table() { + make_test("event-table").run(); + } + + #[test] + fn multiple_events() { + make_test("multiple-events").run(); + } + + #[test] + fn events_dont_persist() { + make_test("events-dont-persist").run(); + } +} + macro_rules! procedure_tests { ($mod_name:ident, $suffix:literal) => { mod $mod_name { From 766bfcd6f6243a772c8271dcbdf1bf5de66de27a Mon Sep 17 00:00:00 2001 From: = Date: Thu, 12 Feb 2026 14:10:41 -0500 Subject: [PATCH 04/24] Add event tables documentation - Add event-tables-status.md tracking implementation progress - Add 1.0 to 2.0 migration guide --- .../00100-how-to/00600-migrating-to-2.0.md | 217 ++++++++++++++++++ docs/event-tables-status.md | 176 ++++++++++++++ 2 files changed, 393 insertions(+) create mode 100644 docs/docs/00300-resources/00100-how-to/00600-migrating-to-2.0.md create mode 100644 docs/event-tables-status.md diff --git a/docs/docs/00300-resources/00100-how-to/00600-migrating-to-2.0.md b/docs/docs/00300-resources/00100-how-to/00600-migrating-to-2.0.md new file mode 100644 index 00000000000..089169b5aee --- /dev/null +++ b/docs/docs/00300-resources/00100-how-to/00600-migrating-to-2.0.md @@ -0,0 +1,217 @@ +--- +title: Migrating from 1.0 to 2.0 +slug: /how-to/migrating-to-2-0 +--- + +# Migrating from SpacetimeDB 1.0 to 2.0 + +This guide covers the breaking changes between SpacetimeDB 1.0 and 2.0 and how to update your code. + +## Overview of Changes + +SpacetimeDB 2.0 introduces a new WebSocket protocol (v2) and SDK with several breaking changes aimed at simplifying the programming model and improving security: + +1. **Reducer callbacks removed** -- replaced by event tables and per-call `_then()` callbacks +2. **`light_mode` removed** -- no longer necessary since reducer events are no longer broadcast +3. **`CallReducerFlags` removed** -- `NoSuccessNotify` and `set_reducer_flags()` are gone +4. **Event tables introduced** -- a new table type for publishing transient events to subscribers + +## Reducer Callbacks + +### What changed + +In 1.0, you could register global callbacks on reducers that would fire whenever *any* client called that reducer and you were subscribed to affected rows: + +```rust +// 1.0 -- REMOVED in 2.0 +conn.reducers.on_insert_one_u_8(|ctx, arg| { + println!("Someone called insert_one_u_8 with arg: {}", arg); +}); +``` + +In 2.0, global reducer callbacks no longer exist. The server does not broadcast reducer argument data to other clients. Instead, you have two options: + +### Option A: Per-call result callbacks (`_then()`) + +If you only need to know the result of a reducer *you* called, use the `_then()` variant: + +```rust +// 2.0 -- per-call callback +ctx.reducers.insert_one_u_8_then(42, |ctx, result| { + match result { + Ok(Ok(())) => println!("Reducer succeeded"), + Ok(Err(err)) => println!("Reducer failed: {err}"), + Err(internal) => println!("Internal error: {internal:?}"), + } +}).unwrap(); +``` + +The fire-and-forget form still works: + +```rust +// 2.0 -- fire and forget (unchanged) +ctx.reducers.insert_one_u_8(42).unwrap(); +``` + +### Option B: Event tables (recommended for most use cases) + +If you need *other* clients to observe that something happened (the primary use case for 1.0 reducer callbacks), create an event table and insert into it from your reducer. + +**Server (module) -- before:** +```rust +// 1.0 server -- reducer args were automatically broadcast +#[reducer] +fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) { + // update game state... +} +``` + +**Server (module) -- after:** +```rust +// 2.0 server -- explicitly publish events via an event table +#[spacetimedb::table(name = damage_event, public, event)] +pub struct DamageEvent { + pub target: Identity, + pub amount: u32, +} + +#[reducer] +fn deal_damage(ctx: &ReducerContext, target: Identity, amount: u32) { + // update game state... + ctx.db.damage_event().insert(DamageEvent { target, amount }); +} +``` + +**Client -- before:** +```rust +// 1.0 client -- global reducer callback +conn.reducers.on_deal_damage(|ctx, target, amount| { + play_damage_animation(target, amount); +}); +``` + +**Client -- after:** +```rust +// 2.0 client -- event table callback +conn.db.damage_event().on_insert(|ctx, event| { + play_damage_animation(event.target, event.amount); +}); +``` + +### Why event tables are better + +- **Security**: You control exactly what data is published. In 1.0, reducer arguments were broadcast to any subscriber of affected rows, which could accidentally leak sensitive data. +- **Flexibility**: Multiple reducers can insert the same event type. In 1.0, events were tied 1:1 to a specific reducer. +- **Transactional**: Events are only published if the transaction commits. In 1.0, workarounds using scheduled reducers were not transactional. +- **Row-level security**: RLS rules apply to event tables, so you can control which clients see which events. +- **Queryable**: Event tables are subscribed to with standard SQL, and can be filtered per-client. + +### Event table details + +- Event tables are always empty outside of a transaction. They don't accumulate rows. +- On the client, `count()` always returns 0 and `iter()` is always empty. +- Only `on_insert` callbacks are generated (no `on_delete` or `on_update`). +- The `event` keyword in `#[table(..., event)]` marks the table as transient. +- Event tables must be subscribed to explicitly (they are excluded from `SELECT * FROM *`). + +## Light Mode + +### What changed + +In 1.0, `light_mode` prevented the server from sending reducer event data to a client (unless that client was the caller): + +```rust +// 1.0 -- REMOVED in 2.0 +DbConnection::builder() + .with_light_mode(true) + // ... +``` + +In 2.0, the server never broadcasts reducer argument data to any client, so `light_mode` is no longer necessary. Simply remove the call: + +```rust +// 2.0 +DbConnection::builder() + .with_uri(uri) + .with_module_name(name) + // no with_light_mode needed + .build() +``` + +## CallReducerFlags + +### What changed + +In 1.0, you could suppress success notifications for individual reducer calls: + +```rust +// 1.0 -- REMOVED in 2.0 +ctx.set_reducer_flags(CallReducerFlags::NoSuccessNotify); +ctx.reducers.my_reducer(args).unwrap(); +``` + +In 2.0, the success notification is lightweight (just `request_id` and `timestamp`, no reducer args or full event data), so there is no need to suppress it. Remove any `set_reducer_flags` calls and `CallReducerFlags` imports. + +## Event Type Changes + +### What changed + +In 1.0, table callbacks received `Event::Reducer` with full reducer information when a reducer caused a table change. Non-callers could also receive `Event::UnknownTransaction`. + +In 2.0, the event model is simplified: + +- **The caller** sees `Event::Reducer` with `ReducerEvent { timestamp, status, reducer }` in response to their own reducer calls. +- **Other clients** see `Event::Transaction` (no reducer details). +- `Event::UnknownTransaction` is removed. + +```rust +// 2.0 -- checking who caused a table change +conn.db.my_table().on_insert(|ctx, row| { + match &ctx.event { + Event::Reducer(reducer_event) => { + // This client called the reducer that caused this insert. + println!("Our reducer: {:?}", reducer_event.reducer); + } + Event::Transaction => { + // Another client's action caused this insert. + } + _ => {} + } +}); +``` + +## Subscription API + +The subscription API is largely unchanged: + +```rust +// 2.0 -- same as 1.0 +ctx.subscription_builder() + .on_applied(|ctx| { /* ... */ }) + .on_error(|ctx, error| { /* ... */ }) + .subscribe(["SELECT * FROM my_table"]); +``` + +Note that subscribing to event tables requires an explicit query: + +```rust +// Event tables are excluded from SELECT * FROM *, so subscribe explicitly: +ctx.subscription_builder() + .on_applied(|ctx| { /* ... */ }) + .subscribe(["SELECT * FROM damage_event"]); +``` + +## Quick Migration Checklist + +- [ ] Remove all `ctx.reducers.on_()` calls + - Replace with `_then()` callbacks for your own reducer calls + - Replace with event tables + `on_insert` for cross-client notifications +- [ ] Remove `with_light_mode()` from `DbConnectionBuilder` +- [ ] Remove `set_reducer_flags()` calls and `CallReducerFlags` imports +- [ ] Remove `unstable::CallReducerFlags` from imports +- [ ] Update `Event::UnknownTransaction` matches to `Event::Transaction` +- [ ] For each reducer whose args you were observing from other clients: + 1. Create an `#[table(..., event)]` on the server + 2. Insert into it from the reducer + 3. Subscribe to it on the client + 4. Use `on_insert` instead of the old reducer callback diff --git a/docs/event-tables-status.md b/docs/event-tables-status.md new file mode 100644 index 00000000000..7339aae3c48 --- /dev/null +++ b/docs/event-tables-status.md @@ -0,0 +1,176 @@ +# Event Tables Implementation Status + +Tracking progress against the [event tables proposal](../SpacetimeDBPrivate/proposals/00XX-event-tables.md). + +**Branches:** +- `tyler/impl-event-tables` -- full implementation (rebased on `phoebe/rust-sdk-ws-v2`) +- `tyler/event-tables-datastore` -- datastore-only subset (off `master`, PR [#4251](https://github.com/clockworklabs/SpacetimeDB/pull/4251)) + +## Implemented + +### Server: Module-side (`#[table(..., event)]`) + +- [x] `event` attribute on `#[table]` macro (`crates/bindings-macro/src/table.rs`) +- [x] `is_event` field on `TableSchema` propagated through schema validation (`crates/schema/`) +- [x] `RawModuleDefV10` includes `is_event` in table definitions (`crates/lib/src/db/raw_def/v10.rs`). Note: V9 does not support event tables. +- [x] Event table rows are recorded as inserts in `tx_data` at commit time but NOT merged into committed state (`crates/datastore/src/locking_tx_datastore/committed_state.rs`) +- [x] Commitlog replay treats event table inserts as noops -- `replay_insert()` returns early when `schema.is_event` (`committed_state.rs`) +- [x] Event tables function as normal tables during reducer execution (insert/delete/update within a transaction) + +### Server: Datastore Unit Tests (PR #4251) + +- [x] Insert + delete in same tx cancels out -- no TxData entry, no committed state (`test_event_table_insert_delete_noop`) +- [x] Update (delete + re-insert) leaves only the final row in TxData, nothing in committed state (`test_event_table_update_only_final_row`) +- [x] Bare insert records in TxData but not committed state (`test_event_table_insert_records_txdata_not_committed_state`) +- [x] `replay_insert()` is a no-op for event tables (`test_event_table_replay_ignores_inserts`) + +### Server: Migration Validation (PR #4251) + +- [x] `ChangeTableEventFlag` error variant in `auto_migrate.rs` prevents changing `is_event` between module versions +- [x] Tests: non-event -> event and event -> non-event both rejected; same flag accepted (`test_change_event_flag_rejected`, `test_same_event_flag_accepted`) + +### Server: Subscriptions & Query Engine + +- [x] `SELECT * FROM *` excludes event tables (`crates/core/src/subscription/subscription.rs`) +- [x] `CanBeLookupTable` trait: event tables do NOT implement it, preventing use as the right/lookup table in view semijoins (`crates/bindings-macro/src/table.rs`, `crates/query-builder/src/table.rs`) +- [x] `reads_from_event_table()` check in `SubscriptionPlan::compile()` rejects event tables as lookup tables in subscription joins (`crates/subscription/src/lib.rs`). The proposal notes that joining on event tables is well-defined (noops), but it is restricted for now for ease of implementation. +- [x] View definitions cannot select from event tables at runtime (`crates/core/src/host/wasm_common/module_host_actor.rs`). The proposal says to disallow event table access in the query builder entirely for now. + +### Server: V1 Protocol Compatibility + +- [x] V1 WebSocket subscriptions to event tables are rejected with a clear error message directing developers to upgrade (`crates/core/src/subscription/module_subscription_actor.rs`) + - Enforced in all three V1 paths: `SubscribeSingle`, `SubscribeMulti`, and legacy `Subscribe` +- [x] `returns_event_table()` methods on `SubscriptionPlan` and `Plan` for detecting event table subscriptions + +### Server: V2 Subscription Path + +- [x] Event tables work correctly through the v2 subscription evaluation path -- verified end-to-end with v2 client subscribing, calling reducer, receiving `on_insert` callback +- [x] Merged `jsdt/ws-v2` (server-side v2 protocol) and `phoebe/rust-sdk-ws-v2` (client-side v2 SDK) into `tyler/impl-event-tables` + +### Client: Rust SDK + +- [x] `EventTable` trait and `TableHandle` implementation that skips client cache storage (`sdks/rust/src/table.rs`) +- [x] `is_event` flag on `TableMetadata` in client cache (`sdks/rust/src/client_cache.rs`) +- [x] Codegen generates `EventTable` impl (insert-only, no delete callbacks) for event tables (`crates/codegen/src/rust.rs`). The proposal says to generate both `on_insert` and `on_delete` (see Deferred section below). +- [x] `on_insert` callbacks fire for event table rows; `count()` and `iter()` always return empty +- [x] `spacetime_module.rs` exposes `is_event` in generated `SpacetimeModule` trait +- [x] SDK uses v2 WebSocket protocol by default (`ws::v2::BIN_PROTOCOL` in `sdks/rust/src/websocket.rs`) + +### Reducer Callback Deprecation (2.0 -- primary goal of proposal) + +The proposal's primary motivation is deprecating reducer event callbacks and replacing them with event tables. This is implemented in the v2 SDK: + +- [x] V2 server does not publish `ReducerEvent` messages to clients (commit `fd3ef210f`) +- [x] V2 codegen does not generate `ctx.reducers.on_()` callbacks; replaced with `_then()` pattern for per-call result callbacks +- [x] `CallReducerFlags` / `NoSuccessNotify` removed from v2 SDK (proposal recommends deprecation) + +### Compile-time Checks (trybuild) + +- [x] Event tables cannot be the lookup (right) table in `left_semijoin` (`crates/bindings/tests/ui/views.rs`) +- [x] Event tables cannot be the lookup (right) table in `right_semijoin` +- [x] Event tables CAN be the left/outer table in a semijoin (positive compile test -- would still be blocked at runtime by the view validation check) + +### Integration Tests + +All event table integration tests are active and passing on the v2 SDK: + +- [x] Basic event table test: `on_insert` fires, row values match, cache stays empty (`event-table`) +- [x] Multiple events in one reducer test: 3 inserts in one reducer all arrive as callbacks (`multiple-events`) +- [x] Events don't persist across transactions test: event count doesn't grow after a subsequent noop reducer (`events-dont-persist`) + +The V1 rejection test (`exec_v1_rejects_event_table`) exists in the test client source but is not wired up in `test.rs`, since the SDK now exclusively uses v2. The V1 rejection server logic remains in place for any V1 clients that may connect. + +Test module: `modules/sdk-test-event-table/src/lib.rs` +Test client: `sdks/rust/tests/event-table-client/src/main.rs` +Test harness: `sdks/rust/tests/test.rs` (`event_table_tests` module) + +### Proposal Document + +- [x] WebSocket Protocol Compatibility section added to proposal (`SpacetimeDBPrivate/proposals/00XX-event-tables.md`) + +## Deferred + +Items that are well-defined and implementable but deliberately deferred per the proposal. + +### `on_delete` Codegen + +The proposal says to generate both `on_insert` and `on_delete` for event tables. Since the server only sends inserts (the optimization described in the proposal), the client would need to synthesize `on_delete` from `on_insert` since every event table row is a noop. This is deferred for now and can be added later without breaking changes. See proposal section "Client-side API". + +### Event Tables in Subscription Joins + +The proposal notes that using event tables as the right/inner/lookup table in subscription joins is well-defined (noops make it so), and that event-table-ness is "infectious" -- joined results behave as event tables too. Currently blocked by the `reads_from_event_table()` check in `SubscriptionPlan::compile()` and the `CanBeLookupTable` compile-time trait. This restriction exists for ease of implementation and can be relaxed later. + +### Event Tables in Views + +The proposal says event tables MAY be accessible in view functions but cannot return rows. Currently disallowed both at compile time (via `CanBeLookupTable`) and runtime (view validation check). The proposal suggests this will be allowed in the future, potentially with implicit "infectious" event-table-ness for views that join on event tables. + +## Not Yet Implemented + +### Server: Untested Behaviors + +These are described in the proposal as expected to work but have no dedicated tests: + +- [ ] RLS (Row-Level Security) on event tables -- proposal says RLS should apply with same semantics as non-event tables +- [ ] Primary key, unique constraints, indexes on event tables -- proposal says these should work within a single transaction +- [ ] Sequences and `auto_inc` on event tables -- proposal says these should work + +### Server: Module-side for Other Languages + +- [ ] TypeScript module bindings: `event` attribute on table declarations +- [ ] C# module bindings: `event` attribute on table declarations + +### Client: Other SDKs + +- [ ] TypeScript SDK: `EventTable` support and client codegen +- [ ] C# SDK: `EventTable` support and client codegen +- [ ] C++ SDK: `EventTable` support and client codegen + +### Documentation + +- [ ] Migration guide from reducer callbacks to event tables (proposal includes examples in "Reducer Callback Compatibility" section but no standalone guide exists) + +### Proposal: Future Work Items (not blocking 2.0) + +- [ ] `#[table]` attribute on reducer functions (auto-generate event table from reducer args) -- proposal "Reducer Event Table" section +- [ ] `ctx.on.()` convenience syntax on client -- proposal ".on Syntax" section +- [ ] Event tables in views / infectious event-table-ness for joined views -- proposal "Views" section +- [ ] TTL tables as generalization of event tables (`ttl = Duration::ZERO`) -- proposal "TTLs, Temporal Filters..." section +- [ ] Temporal filters -- proposal "TTLs, Temporal Filters..." section +- [ ] Light mode deprecation (2.0) -- proposal "Light mode deprecation" section + +## Known Issues + +### V2 SDK Test Stability + +The broader (non-event-table) SDK test suite has intermittent failures when running on the v2 protocol branches. These manifest as: +- `subscribe_all_select_star` and `fail_reducer` intermittent failures in Rust SDK tests +- Test timeouts under parallel execution + +These are pre-existing issues in the v2 WebSocket implementation, not caused by event tables. The event table tests themselves pass reliably. + +### `request_id` Bug (Fixed) + +`sdks/rust/src/db_connection.rs:383` had `request_id: 0` hardcoded instead of using the generated `request_id` variable. This caused "Reducer result for unknown request_id 0" errors for all reducer calls. Fixed in commit `43d84a277` on this branch and `f2f385a28` on `phoebe/rust-sdk-ws-v2`. + +## Key Files + +| Area | File | +|------|------| +| Table macro | `crates/bindings-macro/src/table.rs` | +| Schema | `crates/schema/src/schema.rs` (`is_event` field) | +| Migration validation | `crates/schema/src/auto_migrate.rs` (`ChangeTableEventFlag`) | +| Committed state | `crates/datastore/src/locking_tx_datastore/committed_state.rs` | +| Datastore unit tests | `crates/datastore/src/locking_tx_datastore/datastore.rs` | +| Subscription filtering | `crates/core/src/subscription/subscription.rs` | +| V1 rejection | `crates/core/src/subscription/module_subscription_actor.rs` | +| Plan helpers | `crates/core/src/subscription/module_subscription_manager.rs`, `crates/subscription/src/lib.rs` | +| Query builder | `crates/query-builder/src/table.rs` (`CanBeLookupTable`) | +| Physical plan | `crates/physical-plan/src/plan.rs` (`reads_from_event_table`) | +| View validation | `crates/core/src/host/wasm_common/module_host_actor.rs` | +| Rust codegen | `crates/codegen/src/rust.rs` | +| Rust SDK | `sdks/rust/src/table.rs`, `sdks/rust/src/client_cache.rs` | +| Test module | `modules/sdk-test-event-table/src/lib.rs` | +| Test client | `sdks/rust/tests/event-table-client/src/main.rs` | +| Test harness | `sdks/rust/tests/test.rs` (`event_table_tests` module) | +| Compile tests | `crates/bindings/tests/ui/views.rs` | +| Proposal | `../SpacetimeDBPrivate/proposals/00XX-event-tables.md` | From 681351366f4ef9077f05f25a50325e2ad4b850e7 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 12 Feb 2026 14:31:20 -0500 Subject: [PATCH 05/24] Remove event table from module-test to fix ensure_same_schema tests The MyEvent table and emit_event reducer belong in sdk-test-event-table, not in module-test which must stay in sync with C# and TypeScript modules. --- .../snapshots/codegen__codegen_csharp.snap | 166 --------- .../snapshots/codegen__codegen_rust.snap | 317 +----------------- .../codegen__codegen_typescript.snap | 63 ---- modules/module-test/src/lib.rs | 11 - 4 files changed, 1 insertion(+), 556 deletions(-) diff --git a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap index 47cce3c6043..a16c3532c81 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap @@ -683,83 +683,6 @@ namespace SpacetimeDB } } ''' -"Reducers/EmitEvent.g.cs" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void EmitEventHandler(ReducerEventContext ctx, string name, ulong value); - public event EmitEventHandler? OnEmitEvent; - - public void EmitEvent(string name, ulong value) - { - conn.InternalCallReducer(new Reducer.EmitEvent(name, value)); - } - - public bool InvokeEmitEvent(ReducerEventContext ctx, Reducer.EmitEvent args) - { - if (OnEmitEvent == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch(ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnEmitEvent( - ctx, - args.Name, - args.Value - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class EmitEvent : Reducer, IReducerArgs - { - [DataMember(Name = "name")] - public string Name; - [DataMember(Name = "value")] - public ulong Value; - - public EmitEvent( - string Name, - ulong Value - ) - { - this.Name = Name; - this.Value = Value; - } - - public EmitEvent() - { - this.Name = ""; - } - - string IReducerArgs.ReducerName => "emit_event"; - } - } - -} -''' "Reducers/ListOverAge.g.cs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. @@ -1167,7 +1090,6 @@ namespace SpacetimeDB public RemoteTables(DbConnection conn) { AddTable(LoggedOutPlayer = new(conn)); - AddTable(MyEvent = new(conn)); AddTable(MyPlayer = new(conn)); AddTable(Person = new(conn)); AddTable(Player = new(conn)); @@ -1668,7 +1590,6 @@ namespace SpacetimeDB public sealed class From { public global::SpacetimeDB.Table LoggedOutPlayer() => new("logged_out_player", new LoggedOutPlayerCols("logged_out_player"), new LoggedOutPlayerIxCols("logged_out_player")); - public global::SpacetimeDB.Table MyEvent() => new("my_event", new MyEventCols("my_event"), new MyEventIxCols("my_event")); public global::SpacetimeDB.Table MyPlayer() => new("my_player", new MyPlayerCols("my_player"), new MyPlayerIxCols("my_player")); public global::SpacetimeDB.Table Person() => new("person", new PersonCols("person"), new PersonIxCols("person")); public global::SpacetimeDB.Table Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player")); @@ -1760,7 +1681,6 @@ namespace SpacetimeDB Reducer.AssertCallerIdentityIsModuleIdentity args => Reducers.InvokeAssertCallerIdentityIsModuleIdentity(eventContext, args), Reducer.DeletePlayer args => Reducers.InvokeDeletePlayer(eventContext, args), Reducer.DeletePlayersByName args => Reducers.InvokeDeletePlayersByName(eventContext, args), - Reducer.EmitEvent args => Reducers.InvokeEmitEvent(eventContext, args), Reducer.ListOverAge args => Reducers.InvokeListOverAge(eventContext, args), Reducer.LogModuleIdentity args => Reducers.InvokeLogModuleIdentity(eventContext, args), Reducer.QueryPrivate args => Reducers.InvokeQueryPrivate(eventContext, args), @@ -1869,55 +1789,6 @@ namespace SpacetimeDB } } ''' -"Tables/MyEvent.g.cs" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.BSATN; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB -{ - public sealed partial class RemoteTables - { - public sealed class MyEventHandle : RemoteTableHandle - { - protected override string RemoteTableName => "my_event"; - - internal MyEventHandle(DbConnection conn) : base(conn) - { - } - } - - public readonly MyEventHandle MyEvent; - } - - public sealed class MyEventCols - { - public global::SpacetimeDB.Col Name { get; } - public global::SpacetimeDB.Col Value { get; } - - public MyEventCols(string tableName) - { - Name = new global::SpacetimeDB.Col(tableName, "name"); - Value = new global::SpacetimeDB.Col(tableName, "value"); - } - } - - public sealed class MyEventIxCols - { - - public MyEventIxCols(string tableName) - { - } - } -} -''' "Tables/MyPlayer.g.cs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. @@ -2314,43 +2185,6 @@ namespace SpacetimeDB } } ''' -"Types/MyEvent.g.cs" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB -{ - [SpacetimeDB.Type] - [DataContract] - public sealed partial class MyEvent - { - [DataMember(Name = "name")] - public string Name; - [DataMember(Name = "value")] - public ulong Value; - - public MyEvent( - string Name, - ulong Value - ) - { - this.Name = Name; - this.Value = Value; - } - - public MyEvent() - { - this.Name = ""; - } - } -} -''' "Types/NamespaceTestC.g.cs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. diff --git a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap index 3c286b8b2fa..e27bad56492 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap @@ -685,120 +685,6 @@ impl set_flags_for_delete_players_by_name for super::SetReducerFlags { } } -''' -"emit_event_reducer.rs" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#![allow(unused, clippy::all)] -use spacetimedb_sdk::__codegen::{ - self as __sdk, - __lib, - __sats, - __ws, -}; - - -#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] -#[sats(crate = __lib)] -pub(super) struct EmitEventArgs { - pub name: String, - pub value: u64, -} - -impl From for super::Reducer { - fn from(args: EmitEventArgs) -> Self { - Self::EmitEvent { - name: args.name, - value: args.value, -} -} -} - -impl __sdk::InModule for EmitEventArgs { - type Module = super::RemoteModule; -} - -pub struct EmitEventCallbackId(__sdk::CallbackId); - -#[allow(non_camel_case_types)] -/// Extension trait for access to the reducer `emit_event`. -/// -/// Implemented for [`super::RemoteReducers`]. -pub trait emit_event { - /// Request that the remote module invoke the reducer `emit_event` to run as soon as possible. - /// - /// This method returns immediately, and errors only if we are unable to send the request. - /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_emit_event`] callbacks. - fn emit_event(&self, name: String, -value: u64, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `emit_event`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`EmitEventCallbackId`] can be passed to [`Self::remove_on_emit_event`] - /// to cancel the callback. - fn on_emit_event(&self, callback: impl FnMut(&super::ReducerEventContext, &String, &u64, ) + Send + 'static) -> EmitEventCallbackId; - /// Cancel a callback previously registered by [`Self::on_emit_event`], - /// causing it not to run in the future. - fn remove_on_emit_event(&self, callback: EmitEventCallbackId); -} - -impl emit_event for super::RemoteReducers { - fn emit_event(&self, name: String, -value: u64, -) -> __sdk::Result<()> { - self.imp.call_reducer("emit_event", EmitEventArgs { name, value, }) - } - fn on_emit_event( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &String, &u64, ) + Send + 'static, - ) -> EmitEventCallbackId { - EmitEventCallbackId(self.imp.on_reducer( - "emit_event", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::EmitEvent { - name, value, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, name, value, ) - }), - )) - } - fn remove_on_emit_event(&self, callback: EmitEventCallbackId) { - self.imp.remove_on_reducer("emit_event", callback.0) - } -} - -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `emit_event`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_emit_event { - /// Set the call-reducer flags for the reducer `emit_event` to `flags`. - /// - /// This type is currently unstable and may be removed without a major version bump. - fn emit_event(&self, flags: __ws::CallReducerFlags); -} - -impl set_flags_for_emit_event for super::SetReducerFlags { - fn emit_event(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("emit_event", flags); - } -} - ''' "foobar_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -1406,7 +1292,6 @@ use spacetimedb_sdk::__codegen::{ pub mod baz_type; pub mod foobar_type; pub mod has_special_stuff_type; -pub mod my_event_type; pub mod person_type; pub mod pk_multi_identity_type; pub mod player_type; @@ -1427,7 +1312,6 @@ pub mod add_private_reducer; pub mod assert_caller_identity_is_module_identity_reducer; pub mod delete_player_reducer; pub mod delete_players_by_name_reducer; -pub mod emit_event_reducer; pub mod list_over_age_reducer; pub mod log_module_identity_reducer; pub mod query_private_reducer; @@ -1435,7 +1319,6 @@ pub mod say_hello_reducer; pub mod test_reducer; pub mod test_btree_index_args_reducer; pub mod logged_out_player_table; -pub mod my_event_table; pub mod person_table; pub mod player_table; pub mod test_d_table; @@ -1449,7 +1332,6 @@ pub mod with_tx_procedure; pub use baz_type::Baz; pub use foobar_type::Foobar; pub use has_special_stuff_type::HasSpecialStuff; -pub use my_event_type::MyEvent; pub use person_type::Person; pub use pk_multi_identity_type::PkMultiIdentity; pub use player_type::Player; @@ -1465,7 +1347,6 @@ pub use test_foobar_type::TestFoobar; pub use namespace_test_c_type::NamespaceTestC; pub use namespace_test_f_type::NamespaceTestF; pub use logged_out_player_table::*; -pub use my_event_table::*; pub use my_player_table::*; pub use person_table::*; pub use player_table::*; @@ -1477,7 +1358,6 @@ pub use add_private_reducer::{add_private, set_flags_for_add_private, AddPrivate pub use assert_caller_identity_is_module_identity_reducer::{assert_caller_identity_is_module_identity, set_flags_for_assert_caller_identity_is_module_identity, AssertCallerIdentityIsModuleIdentityCallbackId}; pub use delete_player_reducer::{delete_player, set_flags_for_delete_player, DeletePlayerCallbackId}; pub use delete_players_by_name_reducer::{delete_players_by_name, set_flags_for_delete_players_by_name, DeletePlayersByNameCallbackId}; -pub use emit_event_reducer::{emit_event, set_flags_for_emit_event, EmitEventCallbackId}; pub use list_over_age_reducer::{list_over_age, set_flags_for_list_over_age, ListOverAgeCallbackId}; pub use log_module_identity_reducer::{log_module_identity, set_flags_for_log_module_identity, LogModuleIdentityCallbackId}; pub use query_private_reducer::{query_private, set_flags_for_query_private, QueryPrivateCallbackId}; @@ -1513,10 +1393,6 @@ pub enum Reducer { } , DeletePlayersByName { name: String, -} , - EmitEvent { - name: String, - value: u64, } , ListOverAge { age: u8, @@ -1547,7 +1423,6 @@ impl __sdk::Reducer for Reducer { Reducer::AssertCallerIdentityIsModuleIdentity => "assert_caller_identity_is_module_identity", Reducer::DeletePlayer { .. } => "delete_player", Reducer::DeletePlayersByName { .. } => "delete_players_by_name", - Reducer::EmitEvent { .. } => "emit_event", Reducer::ListOverAge { .. } => "list_over_age", Reducer::LogModuleIdentity => "log_module_identity", Reducer::QueryPrivate => "query_private", @@ -1573,10 +1448,7 @@ fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result Ok(__sdk::parse_reducer_args::("delete_player", &value.args)?.into()),Reducer::DeletePlayersByName{ name, -} "delete_players_by_name" => Ok(__sdk::parse_reducer_args::("delete_players_by_name", &value.args)?.into()),Reducer::EmitEvent{ - name, - value, -} "emit_event" => Ok(__sdk::parse_reducer_args::("emit_event", &value.args)?.into()),Reducer::ListOverAge{ +} "delete_players_by_name" => Ok(__sdk::parse_reducer_args::("delete_players_by_name", &value.args)?.into()),Reducer::ListOverAge{ age, } "list_over_age" => Ok(__sdk::parse_reducer_args::("list_over_age", &value.args)?.into()),Reducer::LogModuleIdentity"log_module_identity" => Ok(__sdk::parse_reducer_args::("log_module_identity", &value.args)?.into()),Reducer::QueryPrivate"query_private" => Ok(__sdk::parse_reducer_args::("query_private", &value.args)?.into()),Reducer::SayHello"say_hello" => Ok(__sdk::parse_reducer_args::("say_hello", &value.args)?.into()),Reducer::Test{ arg, @@ -1593,7 +1465,6 @@ fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result, - my_event: __sdk::TableUpdate, my_player: __sdk::TableUpdate, person: __sdk::TableUpdate, player: __sdk::TableUpdate, @@ -1610,7 +1481,6 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { match &table_update.table_name[..] { "logged_out_player" => db_update.logged_out_player.append(logged_out_player_table::parse_table_update(table_update)?), - "my_event" => db_update.my_event.append(my_event_table::parse_table_update(table_update)?), "my_player" => db_update.my_player.append(my_player_table::parse_table_update(table_update)?), "person" => db_update.person.append(person_table::parse_table_update(table_update)?), "player" => db_update.player.append(player_table::parse_table_update(table_update)?), @@ -1639,7 +1509,6 @@ impl __sdk::DbUpdate for DbUpdate { let mut diff = AppliedDiff::default(); diff.logged_out_player = cache.apply_diff_to_table::("logged_out_player", &self.logged_out_player).with_updates_by_pk(|row| &row.identity); - diff.my_event = cache.apply_diff_to_table::("my_event", &self.my_event); diff.person = cache.apply_diff_to_table::("person", &self.person).with_updates_by_pk(|row| &row.id); diff.player = cache.apply_diff_to_table::("player", &self.player).with_updates_by_pk(|row| &row.identity); diff.test_d = cache.apply_diff_to_table::("test_d", &self.test_d); @@ -1653,7 +1522,6 @@ fn parse_initial_rows(raw: __ws::v2::QueryRows) -> __sdk::Result { for table_rows in raw.tables { match &table_rows.table[..] { "logged_out_player" => db_update.logged_out_player.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), - "my_event" => db_update.my_event.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "my_player" => db_update.my_player.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "person" => db_update.person.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "player" => db_update.player.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), @@ -1667,7 +1535,6 @@ fn parse_unsubscribe_rows(raw: __ws::v2::QueryRows) -> __sdk::Result { for table_rows in raw.tables { match &table_rows.table[..] { "logged_out_player" => db_update.logged_out_player.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), - "my_event" => db_update.my_event.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "my_player" => db_update.my_player.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "person" => db_update.person.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "player" => db_update.player.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), @@ -1683,7 +1550,6 @@ for table_rows in raw.tables { #[doc(hidden)] pub struct AppliedDiff<'r> { logged_out_player: __sdk::TableAppliedDiff<'r, Player>, - my_event: __sdk::TableAppliedDiff<'r, MyEvent>, my_player: __sdk::TableAppliedDiff<'r, Player>, person: __sdk::TableAppliedDiff<'r, Person>, player: __sdk::TableAppliedDiff<'r, Player>, @@ -1700,7 +1566,6 @@ impl __sdk::InModule for AppliedDiff<'_> { impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks) { callbacks.invoke_table_row_callbacks::("logged_out_player", &self.logged_out_player, event); - callbacks.invoke_table_row_callbacks::("my_event", &self.my_event, event); callbacks.invoke_table_row_callbacks::("my_player", &self.my_player, event); callbacks.invoke_table_row_callbacks::("person", &self.person, event); callbacks.invoke_table_row_callbacks::("player", &self.player, event); @@ -2424,7 +2289,6 @@ impl __sdk::SpacetimeModule for RemoteModule { fn register_tables(client_cache: &mut __sdk::ClientCache) { logged_out_player_table::register_table(client_cache); - my_event_table::register_table(client_cache); my_player_table::register_table(client_cache); person_table::register_table(client_cache); player_table::register_table(client_cache); @@ -2433,7 +2297,6 @@ fn register_tables(client_cache: &mut __sdk::ClientCache) { } const ALL_TABLE_NAMES: &'static [&'static str] = &[ "logged_out_player", - "my_event", "my_player", "person", "player", @@ -2441,184 +2304,6 @@ const ALL_TABLE_NAMES: &'static [&'static str] = &[ "test_f", ]; } -''' -"my_event_table.rs" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#![allow(unused, clippy::all)] -use spacetimedb_sdk::__codegen::{ - self as __sdk, - __lib, - __sats, - __ws, -}; -use super::my_event_type::MyEvent; - -/// Table handle for the table `my_event`. -/// -/// Obtain a handle from the [`MyEventTableAccess::my_event`] method on [`super::RemoteTables`], -/// like `ctx.db.my_event()`. -/// -/// Users are encouraged not to explicitly reference this type, -/// but to directly chain method calls, -/// like `ctx.db.my_event().on_insert(...)`. -pub struct MyEventTableHandle<'ctx> { - imp: __sdk::TableHandle, - ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, -} - -#[allow(non_camel_case_types)] -/// Extension trait for access to the table `my_event`. -/// -/// Implemented for [`super::RemoteTables`]. -pub trait MyEventTableAccess { - #[allow(non_snake_case)] - /// Obtain a [`MyEventTableHandle`], which mediates access to the table `my_event`. - fn my_event(&self) -> MyEventTableHandle<'_>; -} - -impl MyEventTableAccess for super::RemoteTables { - fn my_event(&self) -> MyEventTableHandle<'_> { - MyEventTableHandle { - imp: self.imp.get_table::("my_event"), - ctx: std::marker::PhantomData, - } - } -} - -pub struct MyEventInsertCallbackId(__sdk::CallbackId); -pub struct MyEventDeleteCallbackId(__sdk::CallbackId); - -impl<'ctx> __sdk::Table for MyEventTableHandle<'ctx> { - type Row = MyEvent; - type EventContext = super::EventContext; - - fn count(&self) -> u64 { self.imp.count() } - fn iter(&self) -> impl Iterator + '_ { self.imp.iter() } - - type InsertCallbackId = MyEventInsertCallbackId; - - fn on_insert( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, - ) -> MyEventInsertCallbackId { - MyEventInsertCallbackId(self.imp.on_insert(Box::new(callback))) - } - - fn remove_on_insert(&self, callback: MyEventInsertCallbackId) { - self.imp.remove_on_insert(callback.0) - } - - type DeleteCallbackId = MyEventDeleteCallbackId; - - fn on_delete( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, - ) -> MyEventDeleteCallbackId { - MyEventDeleteCallbackId(self.imp.on_delete(Box::new(callback))) - } - - fn remove_on_delete(&self, callback: MyEventDeleteCallbackId) { - self.imp.remove_on_delete(callback.0) - } -} - -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - - let _table = client_cache.get_or_make_table::("my_event"); -} - -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, -) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse( - "TableUpdate", - "TableUpdate", - ).with_cause(e).into() - }) -} - - #[allow(non_camel_case_types)] - /// Extension trait for query builder access to the table `MyEvent`. - /// - /// Implemented for [`__sdk::QueryTableAccessor`]. - pub trait my_eventQueryTableAccess { - #[allow(non_snake_case)] - /// Get a query builder for the table `MyEvent`. - fn my_event(&self) -> __sdk::__query_builder::Table; - } - - impl my_eventQueryTableAccess for __sdk::QueryTableAccessor { - fn my_event(&self) -> __sdk::__query_builder::Table { - __sdk::__query_builder::Table::new("my_event") - } - } - -''' -"my_event_type.rs" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#![allow(unused, clippy::all)] -use spacetimedb_sdk::__codegen::{ - self as __sdk, - __lib, - __sats, - __ws, -}; - - -#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] -#[sats(crate = __lib)] -pub struct MyEvent { - pub name: String, - pub value: u64, -} - - -impl __sdk::InModule for MyEvent { - type Module = super::RemoteModule; -} - - -/// Column accessor struct for the table `MyEvent`. -/// -/// Provides typed access to columns for query building. -pub struct MyEventCols { - pub name: __sdk::__query_builder::Col, - pub value: __sdk::__query_builder::Col, -} - -impl __sdk::__query_builder::HasCols for MyEvent { - type Cols = MyEventCols; - fn cols(table_name: &'static str) -> Self::Cols { - MyEventCols { - name: __sdk::__query_builder::Col::new(table_name, "name"), - value: __sdk::__query_builder::Col::new(table_name, "value"), - - } - } -} - -/// Indexed column accessor struct for the table `MyEvent`. -/// -/// Provides typed access to indexed columns for query building. -pub struct MyEventIxCols { -} - -impl __sdk::__query_builder::HasIxCols for MyEvent { - type IxCols = MyEventIxCols; - fn ix_cols(table_name: &'static str) -> Self::IxCols { - MyEventIxCols { - - } - } -} - ''' "my_player_table.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE diff --git a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap index 2b4c49e9f77..9b4d56d000d 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -122,24 +122,6 @@ export default { name: __t.string(), }; ''' -"emit_event_reducer.ts" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default { - name: __t.string(), - value: __t.u64(), -}; -''' "foobar_type.ts" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. @@ -246,7 +228,6 @@ import AddPrivateReducer from "./add_private_reducer"; import AssertCallerIdentityIsModuleIdentityReducer from "./assert_caller_identity_is_module_identity_reducer"; import DeletePlayerReducer from "./delete_player_reducer"; import DeletePlayersByNameReducer from "./delete_players_by_name_reducer"; -import EmitEventReducer from "./emit_event_reducer"; import ListOverAgeReducer from "./list_over_age_reducer"; import LogModuleIdentityReducer from "./log_module_identity_reducer"; import QueryPrivateReducer from "./query_private_reducer"; @@ -262,7 +243,6 @@ import * as WithTxProcedure from "./with_tx_procedure"; // Import all table schema definitions import LoggedOutPlayerRow from "./logged_out_player_table"; -import MyEventRow from "./my_event_table"; import MyPlayerRow from "./my_player_table"; import PersonRow from "./person_table"; import PlayerRow from "./player_table"; @@ -356,7 +336,6 @@ const reducersSchema = __reducers( __reducerSchema("assert_caller_identity_is_module_identity", AssertCallerIdentityIsModuleIdentityReducer), __reducerSchema("delete_player", DeletePlayerReducer), __reducerSchema("delete_players_by_name", DeletePlayersByNameReducer), - __reducerSchema("emit_event", EmitEventReducer), __reducerSchema("list_over_age", ListOverAgeReducer), __reducerSchema("log_module_identity", LogModuleIdentityReducer), __reducerSchema("query_private", QueryPrivateReducer), @@ -474,44 +453,6 @@ export default __t.row({ playerId: __t.u64().name("player_id"), name: __t.string(), }); -''' -"my_event_table.ts" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.row({ - name: __t.string(), - value: __t.u64(), -}); -''' -"my_event_type.ts" = ''' -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from "spacetimedb"; - -export default __t.object("MyEvent", { - name: __t.string(), - value: __t.u64(), -}); - - ''' "my_player_table.ts" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -1029,7 +970,6 @@ import { type Infer as __Infer } from "spacetimedb"; import Baz from "../baz_type"; import Foobar from "../foobar_type"; import HasSpecialStuff from "../has_special_stuff_type"; -import MyEvent from "../my_event_type"; import Person from "../person_type"; import PkMultiIdentity from "../pk_multi_identity_type"; import Player from "../player_type"; @@ -1048,7 +988,6 @@ import NamespaceTestF from "../namespace_test_f_type"; export type Baz = __Infer; export type Foobar = __Infer; export type HasSpecialStuff = __Infer; -export type MyEvent = __Infer; export type Person = __Infer; export type PkMultiIdentity = __Infer; export type Player = __Infer; @@ -1104,7 +1043,6 @@ import AddPrivateReducer from "../add_private_reducer"; import AssertCallerIdentityIsModuleIdentityReducer from "../assert_caller_identity_is_module_identity_reducer"; import DeletePlayerReducer from "../delete_player_reducer"; import DeletePlayersByNameReducer from "../delete_players_by_name_reducer"; -import EmitEventReducer from "../emit_event_reducer"; import ListOverAgeReducer from "../list_over_age_reducer"; import LogModuleIdentityReducer from "../log_module_identity_reducer"; import QueryPrivateReducer from "../query_private_reducer"; @@ -1118,7 +1056,6 @@ export type AddPrivateParams = __Infer; export type AssertCallerIdentityIsModuleIdentityParams = __Infer; export type DeletePlayerParams = __Infer; export type DeletePlayersByNameParams = __Infer; -export type EmitEventParams = __Infer; export type ListOverAgeParams = __Infer; export type LogModuleIdentityParams = __Infer; export type QueryPrivateParams = __Infer; diff --git a/modules/module-test/src/lib.rs b/modules/module-test/src/lib.rs index d8ef5ff62a0..433796e5b60 100644 --- a/modules/module-test/src/lib.rs +++ b/modules/module-test/src/lib.rs @@ -165,12 +165,6 @@ pub struct HasSpecialStuff { connection_id: ConnectionId, } -#[table(name = my_event, public, event)] -pub struct MyEvent { - name: String, - value: u64, -} - /// These two tables defined with the same row type /// verify that we can define multiple tables with the same type. /// @@ -379,11 +373,6 @@ pub fn delete_players_by_name(ctx: &ReducerContext, name: String) -> Result<(), } } -#[spacetimedb::reducer] -pub fn emit_event(ctx: &ReducerContext, name: String, value: u64) { - ctx.db.my_event().insert(MyEvent { name, value }); -} - #[spacetimedb::reducer(client_connected)] fn client_connected(_ctx: &ReducerContext) {} From 6d1b5aeb8a9b8aa6f2f44612c0a5015dcd56726b Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 12:34:44 -0500 Subject: [PATCH 06/24] Add event table support to codegen (Rust/TS/C#) and client SDKs (TS/C#) - Rust codegen: branch on is_event to emit EventTable trait, skip delete/update callbacks - TS module bindings: add event option to table(), pass isEvent through schema - TS codegen: emit event: true for event tables - TS client SDK: event tables fire on_insert but skip cache storage - C# SDK: IsEventTable flag, parse EventTableRows, skip cache/indices, fire OnInsert only - C# codegen: pass isEventTable: true in constructor for event tables - Datastore: 5 new tests for constraints/indexes/auto_inc on event tables - Docs: update event-tables-status.md with completed items --- Cargo.lock | 17 ++ crates/bindings-typescript/src/lib/schema.ts | 1 + crates/bindings-typescript/src/lib/table.ts | 26 ++++ .../src/sdk/db_connection_impl.ts | 3 +- .../src/sdk/table_cache.ts | 17 ++ crates/codegen/src/csharp.rs | 15 +- crates/codegen/src/rust.rs | 146 +++++++++++++----- crates/codegen/src/typescript.rs | 7 +- .../codegen__codegen_typescript.snap | 2 +- .../src/locking_tx_datastore/datastore.rs | 104 +++++++++++++ docs/event-tables-status.md | 41 +++-- sdks/csharp/src/Table.cs | 68 ++++++-- 12 files changed, 371 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ed83e61eb0..fa76aebc3ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2078,6 +2078,16 @@ dependencies = [ "serde", ] +[[package]] +name = "event-table-client" +version = "2.0.0" +dependencies = [ + "anyhow", + "env_logger 0.10.2", + "spacetimedb-sdk", + "test-counter", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -6870,6 +6880,13 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdk-test-event-table-module" +version = "0.1.0" +dependencies = [ + "spacetimedb 2.0.0", +] + [[package]] name = "sdk-test-module" version = "0.1.0" diff --git a/crates/bindings-typescript/src/lib/schema.ts b/crates/bindings-typescript/src/lib/schema.ts index e8bc6521322..98ef235646c 100644 --- a/crates/bindings-typescript/src/lib/schema.ts +++ b/crates/bindings-typescript/src/lib/schema.ts @@ -117,6 +117,7 @@ export function tableToSchema< }; }) as T['idxs'], tableDef, + ...(tableDef.isEvent ? { isEvent: true } : {}), }; } diff --git a/crates/bindings-typescript/src/lib/table.ts b/crates/bindings-typescript/src/lib/table.ts index 2ab05b9ea8f..12ca9a603e1 100644 --- a/crates/bindings-typescript/src/lib/table.ts +++ b/crates/bindings-typescript/src/lib/table.ts @@ -117,6 +117,7 @@ export type UntypedTableDef = { indexes: readonly IndexOpts[]; constraints: readonly ConstraintOpts[]; tableDef: Infer; + isEvent?: boolean; }; /** @@ -172,6 +173,7 @@ export type TableOpts = { public?: boolean; indexes?: IndexOpts[]; // declarative multi‑column indexes constraints?: ConstraintOpts[]; +<<<<<<< HEAD scheduled?: () => | ReducerExport }> | ProcedureExport< @@ -179,6 +181,10 @@ export type TableOpts = { { [k: string]: RowBuilder }, ReturnType >; +======= + scheduled?: string; + event?: boolean; +>>>>>>> 1d669cbd1 (Add event table support to codegen (Rust/TS/C#) and client SDKs (TS/C#)) }; /** @@ -301,6 +307,7 @@ export function table>( public: isPublic = false, indexes: userIndexes = [], scheduled, + event: isEvent = false, } = opts; // 1. column catalogue + helpers @@ -448,6 +455,25 @@ export function table>( index.sourceName = `${name}_${colS}_idx_${index.algorithm.tag.toLowerCase()}`; } +<<<<<<< HEAD +======= + // Temporarily set the type ref to 0. We will set this later + // in the schema function. + + const tableDef = (ctx: ModuleContext): Infer => ({ + sourceName: name, + productTypeRef: ctx.registerTypesRecursively(row).ref, + primaryKey: pk, + indexes, + constraints, + sequences, + tableType: { tag: 'User' }, + tableAccess: { tag: isPublic ? 'Public' : 'Private' }, + defaultValues, + isEvent, + }); + +>>>>>>> 1d669cbd1 (Add event table support to codegen (Rust/TS/C#) and client SDKs (TS/C#)) const productType = row.algebraicType.value as RowBuilder< CoerceRow >['algebraicType']['value']; diff --git a/crates/bindings-typescript/src/sdk/db_connection_impl.ts b/crates/bindings-typescript/src/sdk/db_connection_impl.ts index 96f959f14a8..344a9dbc4a9 100644 --- a/crates/bindings-typescript/src/sdk/db_connection_impl.ts +++ b/crates/bindings-typescript/src/sdk/db_connection_impl.ts @@ -510,7 +510,8 @@ export class DbConnectionImpl return inserts.concat(deletes); } if (rows.tag === 'EventTable') { - // TODO: Decide how event tables should be merged into the cache. + // Event table rows are insert-only. The table cache handles skipping + // storage for event tables and only firing on_insert callbacks. return this.#parseRowList('insert', tableName, rows.value.events); } return []; diff --git a/crates/bindings-typescript/src/sdk/table_cache.ts b/crates/bindings-typescript/src/sdk/table_cache.ts index 947ce304c32..0b14e801d75 100644 --- a/crates/bindings-typescript/src/sdk/table_cache.ts +++ b/crates/bindings-typescript/src/sdk/table_cache.ts @@ -261,6 +261,23 @@ export class TableCacheImpl< ctx: EventContextInterface ): PendingCallback[] => { const pendingCallbacks: PendingCallback[] = []; + + // Event tables: fire on_insert callbacks but don't store rows in the cache. + if (this.tableDef.isEvent) { + for (const op of operations) { + if (op.type === 'insert') { + pendingCallbacks.push({ + type: 'insert', + table: this.tableDef.name, + cb: () => { + this.emitter.emit('insert', ctx, op.row); + }, + }); + } + } + return pendingCallbacks; + } + // TODO: performance const hasPrimaryKey = Object.values(this.tableDef.columns).some( col => col.columnMetadata.isPrimaryKey === true diff --git a/crates/codegen/src/csharp.rs b/crates/codegen/src/csharp.rs index 7873b794848..633a06b74fd 100644 --- a/crates/codegen/src/csharp.rs +++ b/crates/codegen/src/csharp.rs @@ -608,10 +608,17 @@ impl Lang for Csharp<'_> { index_names.push(csharp_index_name); } - writeln!( - output, - "internal {csharp_table_class_name}(DbConnection conn) : base(conn)" - ); + if table.is_event { + writeln!( + output, + "internal {csharp_table_class_name}(DbConnection conn) : base(conn, isEventTable: true)" + ); + } else { + writeln!( + output, + "internal {csharp_table_class_name}(DbConnection conn) : base(conn)" + ); + } indented_block(output, |output| { for csharp_index_name in &index_names { writeln!(output, "{csharp_index_name} = new(this);"); diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index cef3a821a14..e0d89095603 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -129,7 +129,6 @@ impl __sdk::InModule for {type_name} {{ let table_name_pascalcase = table.name.deref().to_case(Case::Pascal); let table_handle = table_name_pascalcase.clone() + "TableHandle"; let insert_callback_id = table_name_pascalcase.clone() + "InsertCallbackId"; - let delete_callback_id = table_name_pascalcase.clone() + "DeleteCallbackId"; let accessor_trait = table_access_trait_name(&table.name); let accessor_method = table_method_name(&table.name); @@ -169,7 +168,71 @@ impl {accessor_trait} for super::RemoteTables {{ }} pub struct {insert_callback_id}(__sdk::CallbackId); -pub struct {delete_callback_id}(__sdk::CallbackId); +" + ); + + if table.is_event { + // Event tables: implement EventTable (insert-only, no delete/update callbacks) + write!( + out, + " +impl<'ctx> __sdk::EventTable for {table_handle}<'ctx> {{ + type Row = {row_type}; + type EventContext = super::EventContext; + + fn count(&self) -> u64 {{ self.imp.count() }} + fn iter(&self) -> impl Iterator + '_ {{ self.imp.iter() }} + + type InsertCallbackId = {insert_callback_id}; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> {insert_callback_id} {{ + {insert_callback_id}(self.imp.on_insert(Box::new(callback))) + }} + + fn remove_on_insert(&self, callback: {insert_callback_id}) {{ + self.imp.remove_on_insert(callback.0) + }} +}} +" + ); + + // Event tables: no unique constraints in register_table + out.delimited_block( + " +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { +", + |out| { + writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});"); + }, + "}", + ); + + // Event tables: parse_table_update takes __ws::v2::TableUpdate + out.newline(); + write!( + out, + " +#[doc(hidden)] +pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate<{row_type}>> {{ + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {{ + __sdk::InternalError::failed_parse( + \"TableUpdate<{row_type}>\", + \"TableUpdate\", + ).with_cause(e).into() + }}) +}} +" + ); + } else { + // Normal tables: implement Table with delete callbacks + let delete_callback_id = table_name_pascalcase.clone() + "DeleteCallbackId"; + write!( + out, + "pub struct {delete_callback_id}(__sdk::CallbackId); impl<'ctx> __sdk::Table for {table_handle}<'ctx> {{ type Row = {row_type}; @@ -205,32 +268,32 @@ impl<'ctx> __sdk::Table for {table_handle}<'ctx> {{ }} }} " - ); + ); - out.delimited_block( - " + out.delimited_block( + " #[doc(hidden)] pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { ", - |out| { - writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});"); - for (unique_field_ident, unique_field_type_use) in iter_unique_cols(module.typespace_for_generate(), &schema, product_def) { - let unique_field_name = unique_field_ident.deref().to_case(Case::Snake); - let unique_field_type = type_name(module, unique_field_type_use); - writeln!( - out, - "_table.add_unique_constraint::<{unique_field_type}>({unique_field_name:?}, |row| &row.{unique_field_name});", - ); - } - }, - "}", - ); + |out| { + writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});"); + for (unique_field_ident, unique_field_type_use) in iter_unique_cols(module.typespace_for_generate(), &schema, product_def) { + let unique_field_name = unique_field_ident.deref().to_case(Case::Snake); + let unique_field_type = type_name(module, unique_field_type_use); + writeln!( + out, + "_table.add_unique_constraint::<{unique_field_type}>({unique_field_name:?}, |row| &row.{unique_field_name});", + ); + } + }, + "}", + ); - if table.primary_key.is_some() { - let update_callback_id = table_name_pascalcase.clone() + "UpdateCallbackId"; - write!( - out, - " + if table.primary_key.is_some() { + let update_callback_id = table_name_pascalcase.clone() + "UpdateCallbackId"; + write!( + out, + " pub struct {update_callback_id}(__sdk::CallbackId); impl<'ctx> __sdk::TableWithPrimaryKey for {table_handle}<'ctx> {{ @@ -248,14 +311,14 @@ impl<'ctx> __sdk::TableWithPrimaryKey for {table_handle}<'ctx> {{ }} }} " - ); - } + ); + } - out.newline(); + out.newline(); - write!( - out, - " + write!( + out, + " #[doc(hidden)] pub(super) fn parse_table_update( raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, @@ -268,20 +331,20 @@ pub(super) fn parse_table_update( }}) }} " - ); + ); - for (unique_field_ident, unique_field_type_use) in - iter_unique_cols(module.typespace_for_generate(), &schema, product_def) - { - let unique_field_name = unique_field_ident.deref().to_case(Case::Snake); - let unique_field_name_pascalcase = unique_field_name.to_case(Case::Pascal); + for (unique_field_ident, unique_field_type_use) in + iter_unique_cols(module.typespace_for_generate(), &schema, product_def) + { + let unique_field_name = unique_field_ident.deref().to_case(Case::Snake); + let unique_field_name_pascalcase = unique_field_name.to_case(Case::Pascal); - let unique_constraint = table_name_pascalcase.clone() + &unique_field_name_pascalcase + "Unique"; - let unique_field_type = type_name(module, unique_field_type_use); + let unique_constraint = table_name_pascalcase.clone() + &unique_field_name_pascalcase + "Unique"; + let unique_field_type = type_name(module, unique_field_type_use); - write!( - out, - " + write!( + out, + " /// Access to the `{unique_field_name}` unique index on the table `{table_name}`, /// which allows point queries on the field of the same name /// via the [`{unique_constraint}::find`] method. @@ -312,7 +375,8 @@ pub(super) fn parse_table_update( }} }} " - ); + ); + } } implement_query_table_accessor(table, out, &row_type).expect("failed to implement query table accessor"); diff --git a/crates/codegen/src/typescript.rs b/crates/codegen/src/typescript.rs index 59c80dfd8ff..32444a82ab6 100644 --- a/crates/codegen/src/typescript.rs +++ b/crates/codegen/src/typescript.rs @@ -260,6 +260,7 @@ impl Lang for TypeScript { &table.name, iter_indexes(table), iter_constraints(table), + table.is_event, ); out.dedent(1); writeln!(out, "}}, {}Row),", table_name_pascalcase); @@ -269,7 +270,7 @@ impl Lang for TypeScript { let view_name_pascalcase = view.name.deref().to_case(Case::Pascal); writeln!(out, "{}: __table({{", view.name); out.indent(1); - write_table_opts(module, out, type_ref, &view.name, iter::empty(), iter::empty()); + write_table_opts(module, out, type_ref, &view.name, iter::empty(), iter::empty(), false); out.dedent(1); writeln!(out, "}}, {}Row),", view_name_pascalcase); } @@ -688,6 +689,7 @@ fn write_table_opts<'a>( name: &Identifier, indexes: impl Iterator, constraints: impl Iterator, + is_event: bool, ) { let product_def = module.typespace_for_generate()[type_ref].as_product().unwrap(); writeln!(out, "name: '{}',", name.deref()); @@ -754,6 +756,9 @@ fn write_table_opts<'a>( } out.dedent(1); writeln!(out, "],"); + if is_event { + writeln!(out, "event: true,"); + } } /// e.g. diff --git a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap index 9b4d56d000d..593bb8809d5 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -272,7 +272,7 @@ const tablesSchema = __schema({ { name: 'logged_out_player_player_id_key', constraint: 'unique', columns: ['playerId'] }, ], }, LoggedOutPlayerRow), - person: __table({ + __table({ name: 'person', indexes: [ { name: 'age', algorithm: 'btree', columns: [ diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index ec63dbcce0e..f4eb91e9c4c 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -3832,4 +3832,108 @@ mod tests { ); Ok(()) } + + /// Inserting a duplicate primary key within the same transaction should fail for event tables. + #[test] + fn test_event_table_primary_key_enforced_within_tx() -> ResultTest<()> { + let datastore = get_datastore()?; + let mut tx = begin_mut_tx(&datastore); + let mut schema = basic_table_schema_with_indices(basic_indices(), basic_constraints()); + schema.is_event = true; + schema.primary_key = Some(0.into()); // PK on column 0 (id) + let table_id = datastore.create_table_mut_tx(&mut tx, schema)?; + commit(&datastore, tx)?; + + let mut tx = begin_mut_tx(&datastore); + let row1 = u32_str_u32(1, "Alice", 30); + insert(&datastore, &mut tx, table_id, &row1)?; + + // Duplicate PK in same TX should error (unique constraint on col 0). + let row2 = u32_str_u32(1, "Bob", 25); + let result = insert(&datastore, &mut tx, table_id, &row2); + assert!(result.is_err(), "duplicate PK in same TX should be rejected"); + Ok(()) + } + + /// Inserting a duplicate unique column value within the same transaction should fail for event tables. + #[test] + fn test_event_table_unique_constraint_within_tx() -> ResultTest<()> { + let (datastore, tx, table_id) = setup_event_table()?; + commit(&datastore, tx)?; + + let mut tx = begin_mut_tx(&datastore); + let row1 = u32_str_u32(1, "Alice", 30); + insert(&datastore, &mut tx, table_id, &row1)?; + + // Duplicate unique name in same TX should error (unique constraint on col 1). + let row2 = u32_str_u32(2, "Alice", 25); + let result = insert(&datastore, &mut tx, table_id, &row2); + assert!(result.is_err(), "duplicate unique column in same TX should be rejected"); + Ok(()) + } + + /// Btree index lookups should work within a transaction for event tables. + /// We verify this indirectly: if the index works, an update via that index succeeds. + #[test] + fn test_event_table_index_lookup_within_tx() -> ResultTest<()> { + let (datastore, tx, table_id) = setup_event_table()?; + commit(&datastore, tx)?; + + let mut tx = begin_mut_tx(&datastore); + let row1 = u32_str_u32(1, "Alice", 30); + let row2 = u32_str_u32(2, "Bob", 25); + insert(&datastore, &mut tx, table_id, &row1)?; + insert(&datastore, &mut tx, table_id, &row2)?; + + // Update via the btree index on column 0 (id) — this exercises index lookup. + let idx = extract_index_id(&datastore, &tx, &basic_indices()[0])?; + let row1_updated = u32_str_u32(1, "Alice", 31); + update(&datastore, &mut tx, table_id, idx, &row1_updated)?; + + // Verify both rows are visible in the tx. + let rows = all_rows(&datastore, &tx, table_id); + assert_eq!(rows.len(), 2, "should have 2 rows after insert+update"); + assert!(rows.contains(&row1_updated), "updated row should be present"); + assert!(rows.contains(&row2), "other row should be present"); + Ok(()) + } + + /// Auto-increment should generate distinct values within a single transaction for event tables. + #[test] + fn test_event_table_auto_inc_within_tx() -> ResultTest<()> { + let (datastore, tx, table_id) = setup_event_table()?; + commit(&datastore, tx)?; + + let mut tx = begin_mut_tx(&datastore); + // Insert with id=0 to trigger auto_inc on column 0. + let row1 = u32_str_u32(0, "Alice", 30); + let (gen1, _) = insert(&datastore, &mut tx, table_id, &row1)?; + let row2 = u32_str_u32(0, "Bob", 25); + let (gen2, _) = insert(&datastore, &mut tx, table_id, &row2)?; + + // Both auto-incremented ids should be distinct. + assert_ne!(gen1, gen2, "auto_inc should produce distinct values within the same TX"); + Ok(()) + } + + /// Constraints on event tables should reset across transactions (no committed state carryover). + #[test] + fn test_event_table_constraints_reset_across_txs() -> ResultTest<()> { + let (datastore, tx, table_id) = setup_event_table()?; + commit(&datastore, tx)?; + + // TX1: insert row with id=1. + let mut tx1 = begin_mut_tx(&datastore); + let row = u32_str_u32(1, "Alice", 30); + insert(&datastore, &mut tx1, table_id, &row)?; + commit(&datastore, tx1)?; + + // TX2: insert row with same id=1 — should succeed because event tables + // don't carry committed state. + let mut tx2 = begin_mut_tx(&datastore); + let row = u32_str_u32(1, "Bob", 25); + let result = insert(&datastore, &mut tx2, table_id, &row); + assert!(result.is_ok(), "same PK in a new TX should succeed for event tables (no committed state)"); + Ok(()) + } } diff --git a/docs/event-tables-status.md b/docs/event-tables-status.md index 7339aae3c48..6928918679c 100644 --- a/docs/event-tables-status.md +++ b/docs/event-tables-status.md @@ -3,7 +3,7 @@ Tracking progress against the [event tables proposal](../SpacetimeDBPrivate/proposals/00XX-event-tables.md). **Branches:** -- `tyler/impl-event-tables` -- full implementation (rebased on `phoebe/rust-sdk-ws-v2`) +- `tyler/impl-event-tables` -- full implementation (rebased on `jlarabie/csharp-sdk-ws-v2-rebase2`) - `tyler/event-tables-datastore` -- datastore-only subset (off `master`, PR [#4251](https://github.com/clockworklabs/SpacetimeDB/pull/4251)) ## Implemented @@ -106,23 +106,35 @@ The proposal says event tables MAY be accessible in view functions but cannot re ## Not Yet Implemented -### Server: Untested Behaviors +### Server: Constraint/Index Tests on Event Tables -These are described in the proposal as expected to work but have no dedicated tests: - -- [ ] RLS (Row-Level Security) on event tables -- proposal says RLS should apply with same semantics as non-event tables -- [ ] Primary key, unique constraints, indexes on event tables -- proposal says these should work within a single transaction -- [ ] Sequences and `auto_inc` on event tables -- proposal says these should work +- [x] Primary key enforced within single transaction (`test_event_table_primary_key_enforced_within_tx`) +- [x] Unique constraint enforced within single transaction (`test_event_table_unique_constraint_within_tx`) +- [x] Btree index lookup works within transaction (`test_event_table_index_lookup_within_tx`) +- [x] Auto-increment generates distinct values within transaction (`test_event_table_auto_inc_within_tx`) +- [x] Constraints reset across transactions -- no committed state carryover (`test_event_table_constraints_reset_across_txs`) ### Server: Module-side for Other Languages -- [ ] TypeScript module bindings: `event` attribute on table declarations -- [ ] C# module bindings: `event` attribute on table declarations +- [x] TypeScript module bindings: `event` option on `table()` function (`crates/bindings-typescript/src/lib/table.ts`) +- [x] C# module bindings: `[Table(Event = true)]` attribute + `RawModuleDefV10` support (already implemented) + +### Client: TypeScript SDK + +- [x] `isEvent` property on `UntypedTableDef` (`crates/bindings-typescript/src/lib/table.ts`) +- [x] Event table cache bypass: `on_insert` callbacks fire, rows not stored (`crates/bindings-typescript/src/sdk/table_cache.ts`) +- [x] TypeScript codegen emits `event: true` for event tables (`crates/codegen/src/typescript.rs`) + +### Client: C# SDK + +- [x] `IsEventTable` flag on `RemoteTableHandle` (`sdks/csharp/src/Table.cs`) +- [x] Event table rows parsed from `EventTableRows` wire format (Parse/ParseInsertOnly/ParseDeleteOnly) +- [x] Event tables skip cache storage in `Apply()`, fire only `OnInsert` in `PostApply()` +- [x] C# codegen passes `isEventTable: true` to constructor (`crates/codegen/src/csharp.rs`) -### Client: Other SDKs +### Not Yet Implemented -- [ ] TypeScript SDK: `EventTable` support and client codegen -- [ ] C# SDK: `EventTable` support and client codegen +- [ ] RLS (Row-Level Security) on event tables -- proposal says RLS should apply with same semantics; needs integration test - [ ] C++ SDK: `EventTable` support and client codegen ### Documentation @@ -168,7 +180,12 @@ These are pre-existing issues in the v2 WebSocket implementation, not caused by | Physical plan | `crates/physical-plan/src/plan.rs` (`reads_from_event_table`) | | View validation | `crates/core/src/host/wasm_common/module_host_actor.rs` | | Rust codegen | `crates/codegen/src/rust.rs` | +| TypeScript codegen | `crates/codegen/src/typescript.rs` | +| C# codegen | `crates/codegen/src/csharp.rs` | | Rust SDK | `sdks/rust/src/table.rs`, `sdks/rust/src/client_cache.rs` | +| TypeScript SDK | `crates/bindings-typescript/src/sdk/table_cache.ts`, `crates/bindings-typescript/src/sdk/db_connection_impl.ts` | +| TypeScript bindings | `crates/bindings-typescript/src/lib/table.ts` | +| C# SDK | `sdks/csharp/src/Table.cs` | | Test module | `modules/sdk-test-event-table/src/lib.rs` | | Test client | `sdks/rust/tests/event-table-client/src/main.rs` | | Test harness | `sdks/rust/tests/test.rs` (`event_table_tests` module) | diff --git a/sdks/csharp/src/Table.cs b/sdks/csharp/src/Table.cs index d23a917b8dd..524abe05bb7 100644 --- a/sdks/csharp/src/Table.cs +++ b/sdks/csharp/src/Table.cs @@ -164,12 +164,26 @@ internal class ParsedTableUpdate : IParsedTableUpdate /// Stores the set of changes for the table, mapping primary keys to updated rows. /// internal MultiDictionaryDelta Delta = new(EqualityComparer.Default, EqualityComparer.Default); + + /// + /// For event tables: stores the event rows directly (bypassing delta merge). + /// + internal List? EventRows; } protected abstract string RemoteTableName { get; } string IRemoteTableHandle.RemoteTableName => RemoteTableName; - public RemoteTableHandle(IDbConnection conn) : base(conn) { } + /// + /// Whether this table is an event table. + /// Event tables don't persist rows in the client cache — they only fire insert callbacks. + /// + internal bool IsEventTable { get; } + + public RemoteTableHandle(IDbConnection conn, bool isEventTable = false) : base(conn) + { + IsEventTable = isEventTable; + } // This method needs to be overridden by autogen. protected virtual object? GetPrimaryKey(Row row) => null; @@ -288,9 +302,12 @@ void IRemoteTableHandle.ParseInsertOnly(TableUpdate update, ParsedDatabaseUpdate } else if (rowSet is TableUpdateRows.EventTable(var events)) { - if (events.Events.RowsData.Count > 0) + var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); + delta.EventRows ??= new(); + for (var i = 0; i < eventRowCount; i++) { - Log.Warn($"Event-table rows are not yet supported by the C# SDK; ignoring insert-only event rows for table `{RemoteTableName}`."); + var obj = DecodeValue(eventReader); + delta.EventRows.Add(obj); } } } @@ -321,10 +338,7 @@ void IRemoteTableHandle.ParseDeleteOnly(TableUpdate update, ParsedDatabaseUpdate } else if (rowSet is TableUpdateRows.EventTable(var events)) { - if (events.Events.RowsData.Count > 0) - { - Log.Warn($"Event-table rows are not yet supported by the C# SDK; ignoring delete-only event rows for table `{RemoteTableName}`."); - } + // Event tables never have deletes; ignore event rows in delete-only context. } } } @@ -359,9 +373,12 @@ void IRemoteTableHandle.Parse(TableUpdate update, ParsedDatabaseUpdate dbOps) } else if (rowSet is TableUpdateRows.EventTable(var events)) { - if (events.Events.RowsData.Count > 0) + var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); + delta.EventRows ??= new(); + for (var i = 0; i < eventRowCount; i++) { - Log.Warn($"Event-table rows are not yet supported by the C# SDK; ignoring event rows for table `{RemoteTableName}`."); + var obj = DecodeValue(eventReader); + delta.EventRows.Add(obj); } } } @@ -464,6 +481,7 @@ void InvokeUpdate(IEventContext context, IStructuralReadWrite oldRow, IStructura void IRemoteTableHandle.PreApply(IEventContext context, IParsedTableUpdate parsedTableUpdate) { Debug.Assert(wasInserted.Count == 0 && wasUpdated.Count == 0 && wasRemoved.Count == 0, "Call Apply and PostApply before calling PreApply again"); + if (IsEventTable) return; // Event tables have no deletes. var delta = (ParsedTableUpdate)parsedTableUpdate; foreach (var (_, value) in Entries.WillRemove(delta.Delta)) { @@ -478,9 +496,24 @@ void IRemoteTableHandle.PreApply(IEventContext context, IParsedTableUpdate parse /// void IRemoteTableHandle.Apply(IEventContext context, IParsedTableUpdate parsedTableUpdate) { + var delta = (ParsedTableUpdate)parsedTableUpdate; + + if (IsEventTable) + { + // Event tables: don't store rows in the cache or update indices. + // Just collect the event rows so PostApply can fire OnInsert callbacks. + if (delta.EventRows != null) + { + foreach (var row in delta.EventRows) + { + wasInserted.Add(new KeyValuePair(row, row)); + } + } + return; + } + try { - var delta = (ParsedTableUpdate)parsedTableUpdate; Entries.Apply(delta.Delta, wasInserted, wasUpdated, wasRemoved); } catch (Exception e) @@ -552,13 +585,16 @@ void IRemoteTableHandle.PostApply(IEventContext context) { InvokeInsert(context, value); } - foreach (var (_, oldValue, newValue) in wasUpdated) + if (!IsEventTable) { - InvokeUpdate(context, oldValue, newValue); - } - foreach (var (_, value) in wasRemoved) - { - InvokeDelete(context, value); + foreach (var (_, oldValue, newValue) in wasUpdated) + { + InvokeUpdate(context, oldValue, newValue); + } + foreach (var (_, value) in wasRemoved) + { + InvokeDelete(context, value); + } } wasInserted.Clear(); wasUpdated.Clear(); From 65230704b18faf216cbdee2257f3ecbd2d420594 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 12:45:05 -0500 Subject: [PATCH 07/24] Exclude onDelete/onUpdate from event table types in TypeScript SDK Event tables only expose onInsert/removeOnInsert at the type level, matching the Rust SDK's EventTable trait. Split ClientTableMethods into ClientTableInsertMethods and ClientTableDeleteMethods, and use an IsEventTable conditional type in ClientTableCore to exclude delete and update methods for event tables. --- .../src/sdk/client_table.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/bindings-typescript/src/sdk/client_table.ts b/crates/bindings-typescript/src/sdk/client_table.ts index 2e4e102301b..4e388172209 100644 --- a/crates/bindings-typescript/src/sdk/client_table.ts +++ b/crates/bindings-typescript/src/sdk/client_table.ts @@ -42,7 +42,7 @@ export type ClientTablePrimaryKeyMethods< ): void; }; -export type ClientTableMethods< +export type ClientTableInsertMethods< RemoteModule extends UntypedRemoteModule, TableName extends TableNamesOf, > = { @@ -66,7 +66,12 @@ export type ClientTableMethods< row: Prettify>> ) => void ): void; +}; +export type ClientTableDeleteMethods< + RemoteModule extends UntypedRemoteModule, + TableName extends TableNamesOf, +> = { /** * Registers a callback to be invoked when a row is deleted from the table. */ @@ -89,6 +94,12 @@ export type ClientTableMethods< ): void; }; +export type ClientTableMethods< + RemoteModule extends UntypedRemoteModule, + TableName extends TableNamesOf, +> = ClientTableInsertMethods & + ClientTableDeleteMethods; + /** * Table * @@ -107,6 +118,9 @@ export type ClientTable< > >; +type IsEventTable = + TableDef extends { isEvent: true } ? true : false; + type HasPrimaryKey = ColumnsHavePrimaryKey< TableDef['columns'] >; @@ -142,13 +156,21 @@ export type ClientTableCoreImplementable< /** * Core methods of ClientTable, without the indexes mixed in. - * Includes only staticly known methods. + * Includes only statically known methods. + * + * Event tables only expose insert callbacks (no delete or update), + * matching the Rust SDK's `EventTable` trait. */ export type ClientTableCore< RemoteModule extends UntypedRemoteModule, TableName extends TableNamesOf, > = ReadonlyTableMethods> & - ClientTableMethods & - (HasPrimaryKey> extends true - ? ClientTablePrimaryKeyMethods - : {}); + ClientTableInsertMethods & + (IsEventTable> extends true + ? {} + : ClientTableDeleteMethods & + (HasPrimaryKey< + TableDefForTableName + > extends true + ? ClientTablePrimaryKeyMethods + : {})); From bb666c80b762020ed34630f88e299fe0a6641d5a Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 12:48:24 -0500 Subject: [PATCH 08/24] Run cargo fmt and pnpm format --- crates/bindings-typescript/src/sdk/client_table.ts | 7 +++++-- crates/codegen/src/rust.rs | 5 ++++- crates/datastore/src/locking_tx_datastore/datastore.rs | 5 ++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/bindings-typescript/src/sdk/client_table.ts b/crates/bindings-typescript/src/sdk/client_table.ts index 4e388172209..b6cd2c1a56d 100644 --- a/crates/bindings-typescript/src/sdk/client_table.ts +++ b/crates/bindings-typescript/src/sdk/client_table.ts @@ -118,8 +118,11 @@ export type ClientTable< > >; -type IsEventTable = - TableDef extends { isEvent: true } ? true : false; +type IsEventTable = TableDef extends { + isEvent: true; +} + ? true + : false; type HasPrimaryKey = ColumnsHavePrimaryKey< TableDef['columns'] diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index e0d89095603..d9534c27fab 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -206,7 +206,10 @@ impl<'ctx> __sdk::EventTable for {table_handle}<'ctx> {{ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { ", |out| { - writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});"); + writeln!( + out, + "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});" + ); }, "}", ); diff --git a/crates/datastore/src/locking_tx_datastore/datastore.rs b/crates/datastore/src/locking_tx_datastore/datastore.rs index f4eb91e9c4c..7741b6e319f 100644 --- a/crates/datastore/src/locking_tx_datastore/datastore.rs +++ b/crates/datastore/src/locking_tx_datastore/datastore.rs @@ -3933,7 +3933,10 @@ mod tests { let mut tx2 = begin_mut_tx(&datastore); let row = u32_str_u32(1, "Bob", 25); let result = insert(&datastore, &mut tx2, table_id, &row); - assert!(result.is_ok(), "same PK in a new TX should succeed for event tables (no committed state)"); + assert!( + result.is_ok(), + "same PK in a new TX should succeed for event tables (no committed state)" + ); Ok(()) } } From f0f22d17e82183a13e468887c8b8ef76a0fa7425 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 13:04:29 -0500 Subject: [PATCH 09/24] Add RemoteEventTableHandle to hide OnDelete/OnUpdate from event tables in C# SDK Event tables only expose OnInsert at the type level, matching Rust's EventTable trait and the TypeScript conditional types. RemoteEventTableHandle inherits from RemoteTableHandle and uses private new to shadow the OnDelete, OnBeforeDelete, and OnUpdate events. Codegen generates event table classes inheriting from RemoteEventTableHandle instead. --- crates/codegen/src/csharp.rs | 22 ++++++++++------------ sdks/csharp/src/Table.cs | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/codegen/src/csharp.rs b/crates/codegen/src/csharp.rs index 633a06b74fd..a2fd23af34c 100644 --- a/crates/codegen/src/csharp.rs +++ b/crates/codegen/src/csharp.rs @@ -521,9 +521,14 @@ impl Lang for Csharp<'_> { let csharp_table_class_name = csharp_table_name.clone() + "Handle"; let table_type = type_ref_name(module, table.product_type_ref); + let base_class = if table.is_event { + "RemoteEventTableHandle" + } else { + "RemoteTableHandle" + }; writeln!( output, - "public sealed class {csharp_table_class_name} : RemoteTableHandle" + "public sealed class {csharp_table_class_name} : {base_class}" ); indented_block(output, |output| { writeln!( @@ -608,17 +613,10 @@ impl Lang for Csharp<'_> { index_names.push(csharp_index_name); } - if table.is_event { - writeln!( - output, - "internal {csharp_table_class_name}(DbConnection conn) : base(conn, isEventTable: true)" - ); - } else { - writeln!( - output, - "internal {csharp_table_class_name}(DbConnection conn) : base(conn)" - ); - } + writeln!( + output, + "internal {csharp_table_class_name}(DbConnection conn) : base(conn)" + ); indented_block(output, |output| { for csharp_index_name in &index_names { writeln!(output, "{csharp_index_name} = new(this);"); diff --git a/sdks/csharp/src/Table.cs b/sdks/csharp/src/Table.cs index 524abe05bb7..5a8cac6da64 100644 --- a/sdks/csharp/src/Table.cs +++ b/sdks/csharp/src/Table.cs @@ -633,5 +633,24 @@ public void Invoke(EventContext ctx, Row oldRow, Row newRow) public void RemoveListener(UpdateEventHandler listener) => Listeners.Remove(listener); } } + + /// + /// A table handle for event tables, which only expose OnInsert callbacks. + /// Event tables do not persist rows in the client cache and do not support + /// OnDelete, OnBeforeDelete, or OnUpdate callbacks. + /// + public abstract class RemoteEventTableHandle : RemoteTableHandle + where EventContext : class, IEventContext + where Row : class, IStructuralReadWrite, new() + { + protected RemoteEventTableHandle(IDbConnection conn) : base(conn, isEventTable: true) { } + + // Hide delete/update events from the public API. + // The base class handlers still exist but will never have listeners registered, + // so invoking them in PostApply is a harmless no-op. + private new event RowEventHandler OnDelete { add { } remove { } } + private new event RowEventHandler OnBeforeDelete { add { } remove { } } + private new event UpdateEventHandler OnUpdate { add { } remove { } } + } } #nullable disable From 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 13:44:47 -0500 Subject: [PATCH 10/24] Fix remaining merge conflict in table.ts from rebase --- crates/bindings-typescript/src/lib/table.ts | 25 +-------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/crates/bindings-typescript/src/lib/table.ts b/crates/bindings-typescript/src/lib/table.ts index 12ca9a603e1..8ade4e9b94a 100644 --- a/crates/bindings-typescript/src/lib/table.ts +++ b/crates/bindings-typescript/src/lib/table.ts @@ -173,7 +173,6 @@ export type TableOpts = { public?: boolean; indexes?: IndexOpts[]; // declarative multi‑column indexes constraints?: ConstraintOpts[]; -<<<<<<< HEAD scheduled?: () => | ReducerExport }> | ProcedureExport< @@ -181,10 +180,7 @@ export type TableOpts = { { [k: string]: RowBuilder }, ReturnType >; -======= - scheduled?: string; event?: boolean; ->>>>>>> 1d669cbd1 (Add event table support to codegen (Rust/TS/C#) and client SDKs (TS/C#)) }; /** @@ -455,25 +451,6 @@ export function table>( index.sourceName = `${name}_${colS}_idx_${index.algorithm.tag.toLowerCase()}`; } -<<<<<<< HEAD -======= - // Temporarily set the type ref to 0. We will set this later - // in the schema function. - - const tableDef = (ctx: ModuleContext): Infer => ({ - sourceName: name, - productTypeRef: ctx.registerTypesRecursively(row).ref, - primaryKey: pk, - indexes, - constraints, - sequences, - tableType: { tag: 'User' }, - tableAccess: { tag: isPublic ? 'Public' : 'Private' }, - defaultValues, - isEvent, - }); - ->>>>>>> 1d669cbd1 (Add event table support to codegen (Rust/TS/C#) and client SDKs (TS/C#)) const productType = row.algebraicType.value as RowBuilder< CoerceRow >['algebraicType']['value']; @@ -502,7 +479,7 @@ export function table>( tableType: { tag: 'User' }, tableAccess: { tag: isPublic ? 'Public' : 'Private' }, defaultValues, - isEvent: false, + isEvent, }; }, idxs: {} as OptsIndices, From 4f35a392a983e6874536781f2c485c5d1cf989db Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Fri, 13 Feb 2026 14:31:10 -0500 Subject: [PATCH 11/24] Fix Rust codegen with apparent borked rebase At some point, a rebase on this branch appears to have accidentally overwritten the Rust codegen changes in https://github.com/clockworklabs/SpacetimeDB/pull/4257 . This commit fixes that, to integrate the event tables codegen changes with the prior WS V2 codegen changes. I've also taken the minor liberty of re-ordering the emitted top-level forms in a table definition file, in order to allow merging the definitions of `register_table` and `parse_table_update`, which this branch previously had duplicated into the `if` and `else` cases of the conditional on whether the table was an event table or not. Plus I added a few comments. --- crates/codegen/src/rust.rs | 355 ++++++++++++------------------------- 1 file changed, 110 insertions(+), 245 deletions(-) diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index d9534c27fab..061215a9e20 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -129,6 +129,7 @@ impl __sdk::InModule for {type_name} {{ let table_name_pascalcase = table.name.deref().to_case(Case::Pascal); let table_handle = table_name_pascalcase.clone() + "TableHandle"; let insert_callback_id = table_name_pascalcase.clone() + "InsertCallbackId"; + let delete_callback_id = table_name_pascalcase.clone() + "DeleteCallbackId"; let accessor_trait = table_access_trait_name(&table.name); let accessor_method = table_method_name(&table.name); @@ -168,11 +169,19 @@ impl {accessor_trait} for super::RemoteTables {{ }} pub struct {insert_callback_id}(__sdk::CallbackId); + " ); if table.is_event { - // Event tables: implement EventTable (insert-only, no delete/update callbacks) + // Event tables: implement the `EventTable` trait, which exposes only on-insert callbacks, + // not on-delete or on-update. + // on-update callbacks aren't meaningful for event tables, + // as they never have resident rows, so they can never update an existing row. + // on-delete callbacks are meaningful, but exactly equivalent to the on-insert callbacks, + // so not particularly useful. + // Also, don't emit unique index accessors: no resident rows means these would always be empty, + // so no reason to have them. write!( out, " @@ -196,46 +205,16 @@ impl<'ctx> __sdk::EventTable for {table_handle}<'ctx> {{ self.imp.remove_on_insert(callback.0) }} }} -" - ); - - // Event tables: no unique constraints in register_table - out.delimited_block( - " -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { -", - |out| { - writeln!( - out, - "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});" - ); - }, - "}", - ); - - // Event tables: parse_table_update takes __ws::v2::TableUpdate - out.newline(); - write!( - out, - " -#[doc(hidden)] -pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate<{row_type}>> {{ - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {{ - __sdk::InternalError::failed_parse( - \"TableUpdate<{row_type}>\", - \"TableUpdate\", - ).with_cause(e).into() - }}) -}} " ); } else { - // Normal tables: implement Table with delete callbacks - let delete_callback_id = table_name_pascalcase.clone() + "DeleteCallbackId"; + // Non-event tables: implement the `Table` trait, which exposes on-insert and on-delete callbacks. + // Also possibly implement `TableWithPrimrayKey`, which exposes on-update callbacks, + // and emit accessors for unique columns. write!( out, - "pub struct {delete_callback_id}(__sdk::CallbackId); + " +pub struct {delete_callback_id}(__sdk::CallbackId); impl<'ctx> __sdk::Table for {table_handle}<'ctx> {{ type Row = {row_type}; @@ -273,26 +252,9 @@ impl<'ctx> __sdk::Table for {table_handle}<'ctx> {{ " ); - out.delimited_block( - " -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { -", - |out| { - writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});"); - for (unique_field_ident, unique_field_type_use) in iter_unique_cols(module.typespace_for_generate(), &schema, product_def) { - let unique_field_name = unique_field_ident.deref().to_case(Case::Snake); - let unique_field_type = type_name(module, unique_field_type_use); - writeln!( - out, - "_table.add_unique_constraint::<{unique_field_type}>({unique_field_name:?}, |row| &row.{unique_field_name});", - ); - } - }, - "}", - ); - if table.primary_key.is_some() { + // If the table has a primary key, implement the `TableWithPrimaryKey` trait + // to expose on-update callbacks. let update_callback_id = table_name_pascalcase.clone() + "UpdateCallbackId"; write!( out, @@ -317,25 +279,7 @@ impl<'ctx> __sdk::TableWithPrimaryKey for {table_handle}<'ctx> {{ ); } - out.newline(); - - write!( - out, - " -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, -) -> __sdk::Result<__sdk::TableUpdate<{row_type}>> {{ - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {{ - __sdk::InternalError::failed_parse( - \"TableUpdate<{row_type}>\", - \"TableUpdate\", - ).with_cause(e).into() - }}) -}} -" - ); - + // Emit unique index accessors for all of the table's unique fields. for (unique_field_ident, unique_field_type_use) in iter_unique_cols(module.typespace_for_generate(), &schema, product_def) { @@ -380,11 +324,50 @@ pub(super) fn parse_table_update( " ); } + + // TODO: expose non-unique indices. } - implement_query_table_accessor(table, out, &row_type).expect("failed to implement query table accessor"); + // Regardless of event-ness, emit `register_table` and `parse_table_update`. + out.delimited_block( + " +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { +", + |out| { + writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});"); + for (unique_field_ident, unique_field_type_use) in iter_unique_cols(module.typespace_for_generate(), &schema, product_def) { + let unique_field_name = unique_field_ident.deref().to_case(Case::Snake); + let unique_field_type = type_name(module, unique_field_type_use); + writeln!( + out, + "_table.add_unique_constraint::<{unique_field_type}>({unique_field_name:?}, |row| &row.{unique_field_name});", + ); + } + }, + "}", + ); - // TODO: expose non-unique indices. + out.newline(); + + write!( + out, + " +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate<{row_type}>> {{ + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {{ + __sdk::InternalError::failed_parse( + \"TableUpdate<{row_type}>\", + \"TableUpdate\", + ).with_cause(e).into() + }}) +}} +" + ); + + implement_query_table_accessor(table, out, &row_type).expect("failed to implement query table accessor"); OutputFile { filename: table_module_name(&table.name) + ".rs", @@ -411,7 +394,6 @@ pub(super) fn parse_table_update( let reducer_name = reducer.name.deref(); let func_name = reducer_function_name(reducer); - let set_reducer_flags_trait = reducer_flags_trait_name(reducer); let args_type = function_args_type_name(&reducer.name); let enum_variant_name = reducer_variant_name(&reducer.name); @@ -435,11 +417,8 @@ pub(super) fn parse_table_update( out.newline(); - let callback_id = reducer_callback_id_name(&reducer.name); - let FormattedArglist { arglist_no_delimiters, - arg_type_refs, arg_names, } = FormattedArglist::for_arguments(module, &reducer.params_for_generate.elements); @@ -484,8 +463,6 @@ impl __sdk::InModule for {args_type} {{ type Module = super::RemoteModule; }} -pub struct {callback_id}(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `{reducer_name}`. /// @@ -495,68 +472,36 @@ pub trait {func_name} {{ /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_{func_name}`] callbacks. - fn {func_name}(&self, {arglist_no_delimiters}) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `{reducer_name}`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`{callback_id}`] can be passed to [`Self::remove_on_{func_name}`] - /// to cancel the callback. - fn on_{func_name}(&self, callback: impl FnMut(&super::ReducerEventContext, {arg_type_refs}) + Send + 'static) -> {callback_id}; - /// Cancel a callback previously registered by [`Self::on_{func_name}`], - /// causing it not to run in the future. - fn remove_on_{func_name}(&self, callback: {callback_id}); -}} - -impl {func_name} for super::RemoteReducers {{ + /// and this method provides no way to listen for its completion status. + /// /// Use [`{func_name}:{func_name}_then`] to run a callback after the reducer completes. fn {func_name}(&self, {arglist_no_delimiters}) -> __sdk::Result<()> {{ - self.imp.call_reducer({reducer_name:?}, {args_type} {{ {arg_names} }}) - }} - fn on_{func_name}( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, {arg_type_refs}) + Send + 'static, - ) -> {callback_id} {{ - {callback_id}(self.imp.on_reducer( - {reducer_name:?}, - Box::new(move |ctx: &super::ReducerEventContext| {{ - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext {{ - event: __sdk::ReducerEvent {{ - reducer: super::Reducer::{enum_variant_name} {{ - {arg_names} - }}, - .. - }}, - .. - }} = ctx else {{ unreachable!() }}; - callback(ctx, {arg_names}) - }}), - )) - }} - fn remove_on_{func_name}(&self, callback: {callback_id}) {{ - self.imp.remove_on_reducer({reducer_name:?}, callback.0) + self.{func_name}_then({arg_names} |_, _| {{}}) }} -}} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `{reducer_name}`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait {set_reducer_flags_trait} {{ - /// Set the call-reducer flags for the reducer `{reducer_name}` to `flags`. + /// Request that the remote module invoke the reducer `{reducer_name}` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn {func_name}(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn {func_name}_then( + &self, + {arglist_no_delimiters} + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; }} -impl {set_reducer_flags_trait} for super::SetReducerFlags {{ - fn {func_name}(&self, flags: __ws::CallReducerFlags) {{ - self.imp.set_call_reducer_flags({reducer_name:?}, flags); +impl {func_name} for super::RemoteReducers {{ + fn {func_name}_then( + &self, + {arglist_no_delimiters} + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> {{ + self.imp.invoke_reducer_with_callback({args_type} {{ {arg_names} }}, callback) }} }} " @@ -928,11 +873,6 @@ struct FormattedArglist { /// /// Always carries a trailing comma, unless it's zero elements. arglist_no_delimiters: String, - /// The argument types as `&ty, &ty, &ty,`, - /// for use as the params in a function/closure type. - /// - /// Always carries a trailing comma, unless it's zero elements. - arg_type_refs: String, /// The argument names as `ident, ident, ident,`, /// for passing to function call and struct literal expressions. /// @@ -946,13 +886,8 @@ impl FormattedArglist { write_arglist_no_delimiters(module, &mut arglist_no_delimiters, params, None) .expect("Writing to a String failed... huh?"); - let mut arg_type_refs = String::new(); let mut arg_names = String::new(); - for (arg_ident, arg_ty) in params { - arg_type_refs += "&"; - write_type(module, &mut arg_type_refs, arg_ty).expect("Writing to a String failed... huh?"); - arg_type_refs += ", "; - + for (arg_ident, _) in params { let arg_name = arg_ident.deref().to_case(Case::Snake); arg_names += &arg_name; arg_names += ", "; @@ -960,7 +895,6 @@ impl FormattedArglist { Self { arglist_no_delimiters, - arg_type_refs, arg_names, } } @@ -1166,10 +1100,6 @@ fn reducer_variant_name(reducer_name: &ReducerName) -> String { reducer_name.deref().to_case(Case::Pascal) } -fn reducer_callback_id_name(reducer_name: &ReducerName) -> String { - reducer_name.deref().to_case(Case::Pascal) + "CallbackId" -} - fn reducer_module_name(reducer_name: &ReducerName) -> String { reducer_name.deref().to_case(Case::Snake) + "_reducer" } @@ -1190,10 +1120,6 @@ fn procedure_function_with_callback_name(procedure: &ProcedureDef) -> String { procedure_function_name(procedure) + "_then" } -fn reducer_flags_trait_name(reducer: &ReducerDef) -> String { - format!("set_flags_for_{}", reducer_function_name(reducer)) -} - /// Iterate over all of the Rust `mod`s for types, reducers, views, and tables in the `module`. fn iter_module_names(module: &ModuleDef, visibility: CodegenVisibility) -> impl Iterator + '_ { itertools::chain!( @@ -1231,12 +1157,7 @@ fn print_module_reexports(module: &ModuleDef, visibility: CodegenVisibility, out for reducer in iter_reducers(module, visibility) { let mod_name = reducer_module_name(&reducer.name); let reducer_trait_name = reducer_function_name(reducer); - let flags_trait_name = reducer_flags_trait_name(reducer); - let callback_id_name = reducer_callback_id_name(&reducer.name); - writeln!( - out, - "pub use {mod_name}::{{{reducer_trait_name}, {flags_trait_name}, {callback_id_name}}};" - ); + writeln!(out, "pub use {mod_name}::{reducer_trait_name};"); } for procedure in iter_procedures(module, visibility) { let mod_name = procedure_module_name(&procedure.name); @@ -1317,34 +1238,12 @@ impl __sdk::InModule for Reducer {{ }, "}\n", ); - }, - "}\n", - ); - - out.delimited_block( - "impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer {", - |out| { - writeln!(out, "type Error = __sdk::Error;"); - // We define an "args struct" for each reducer in `generate_reducer`. - // This is not user-facing, and is not exported past the "root" `mod.rs`; - // it is an internal helper for serialization and deserialization. - // We actually want to ser/de instances of `enum Reducer`, but: - // - // - `Reducer` will have struct-like variants, which SATS ser/de does not support. - // - The WS format does not contain a BSATN-serialized `Reducer` instance; - // it holds the reducer name or ID separately from the argument bytes. - // We could work up some magic with `DeserializeSeed` - // and/or custom `Serializer` and `Deserializer` types - // to account for this, but it's much easier to just use an intermediate struct per reducer. - // - // As such, we deserialize from the `value.args` bytes into that "args struct," - // then convert it into a `Reducer` variant via `Into::into`, - // which we also implement in `generate_reducer`. + writeln!(out, "#[allow(clippy::clone_on_copy)]"); out.delimited_block( - "fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result {", + "fn args_bsatn(&self) -> Result, __sats::bsatn::EncodeError> {", |out| { out.delimited_block( - "match &value.reducer_name[..] {", + "match self {", |out| { for reducer in iter_reducers(module, visibility) { write!(out, "Reducer::{}", reducer_variant_name(&reducer.name)); @@ -1369,26 +1268,34 @@ impl __sdk::InModule for Reducer {{ write!( out, - "{:?} => Ok(__sdk::parse_reducer_args::<{}::{}>({:?}, &value.args)?.into()),", - reducer.name.deref(), + " => __sats::bsatn::to_vec(&{}::{}", reducer_module_name(&reducer.name), - function_args_type_name(&reducer.name), - reducer.name.deref(), + function_args_type_name(&reducer.name) + ); + out.delimited_block( + " {", + |out| { + for (ident, _) in &reducer.params_for_generate.elements { + let field = ident.deref().to_case(Case::Snake); + writeln!(out, "{field}: {field}.clone(),"); + } + }, + "}),\n", ); } - writeln!( - out, - "unknown => Err(__sdk::InternalError::unknown_name(\"reducer\", unknown, \"ReducerCallInfo\").into()),", - ); + // Write a catch-all pattern to handle the case where the module defines zero reducers, + // 'cause references are always considered inhabited, + // even references to uninhabited types. + writeln!(out, "_ => unreachable!(),"); }, "}\n", - ) + ); }, "}\n", ); }, "}\n", - ) + ); } fn print_db_update_defn(module: &ModuleDef, visibility: CodegenVisibility, out: &mut Indenter) { @@ -1414,11 +1321,11 @@ fn print_db_update_defn(module: &ModuleDef, visibility: CodegenVisibility, out: out.delimited_block( " -impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { +impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { type Error = __sdk::Error; - fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result { + fn try_from(raw: __ws::v2::TransactionUpdate) -> Result { let mut db_update = DbUpdate::default(); - for table_update in raw.tables { + for table_update in __sdk::transaction_update_iter_table_updates(raw) { match &table_update.table_name[..] { ", |out| { @@ -1641,7 +1548,7 @@ type ErrorContext = ErrorContext; type Reducer = Reducer; type DbView = RemoteTables; type Reducers = RemoteReducers; -type SetReducerFlags = SetReducerFlags; +type Procedures = RemoteProcedures; type DbUpdate = DbUpdate; type AppliedDiff<'r> = AppliedDiff<'r>; type SubscriptionHandle = SubscriptionHandle; @@ -1702,20 +1609,6 @@ impl __sdk::InModule for RemoteProcedures {{ type Module = RemoteModule; }} -#[doc(hidden)] -/// The `set_reducer_flags` field of [`DbConnection`], -/// with methods provided by extension traits for each reducer defined by the module. -/// Each method sets the flags for the reducer with the same name. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub struct SetReducerFlags {{ - imp: __sdk::DbContextImpl, -}} - -impl __sdk::InModule for SetReducerFlags {{ - type Module = RemoteModule; -}} - /// The `db` field of [`EventContext`] and [`DbConnection`], /// with methods provided by extension traits for each table defined by the module. pub struct RemoteTables {{ @@ -1748,11 +1641,6 @@ pub struct DbConnection {{ /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, #[doc(hidden)] - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, @@ -1768,7 +1656,6 @@ impl __sdk::DbContext for DbConnection {{ type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView {{ &self.db @@ -1779,9 +1666,6 @@ impl __sdk::DbContext for DbConnection {{ fn procedures(&self) -> &Self::Procedures {{ &self.procedures }} - fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{ - &self.set_reducer_flags - }} fn is_active(&self) -> bool {{ self.imp.is_active() @@ -1885,7 +1769,6 @@ impl __sdk::DbConnection for DbConnection {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, procedures: RemoteProcedures {{ imp: imp.clone() }}, - set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, imp, }} }} @@ -1937,13 +1820,11 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle {{ pub trait RemoteDbContext: __sdk::DbContext< DbView = RemoteTables, Reducers = RemoteReducers, - SetReducerFlags = SetReducerFlags, SubscriptionBuilder = __sdk::SubscriptionBuilder, > {{}} impl, >> RemoteDbContext for Ctx {{}} ", @@ -2025,11 +1906,6 @@ pub struct {struct_and_trait_name} {{ pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -2046,7 +1922,6 @@ impl __sdk::AbstractEventContext for {struct_and_trait_name} {{ Self {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, - set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, procedures: RemoteProcedures {{ imp: imp.clone() }}, event, imp, @@ -2066,11 +1941,6 @@ pub struct {struct_and_trait_name} {{ pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, imp: __sdk::DbContextImpl, @@ -2086,7 +1956,6 @@ impl __sdk::AbstractEventContext for {struct_and_trait_name} {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, procedures: RemoteProcedures {{ imp: imp.clone() }}, - set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, imp, }} }} @@ -2106,7 +1975,6 @@ impl __sdk::DbContext for {struct_and_trait_name} {{ type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView {{ &self.db @@ -2117,9 +1985,6 @@ impl __sdk::DbContext for {struct_and_trait_name} {{ fn procedures(&self) -> &Self::Procedures {{ &self.procedures }} - fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{ - &self.set_reducer_flags - }} fn is_active(&self) -> bool {{ self.imp.is_active() From 31cd756665563188efcd17c177a8bae3ddd8b046 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 16:03:01 -0500 Subject: [PATCH 12/24] Regenerate view-client bindings and fix with_database_name in event-table-client --- .../rust/tests/event-table-client/src/main.rs | 2 +- .../view-client/src/module_bindings/mod.rs | 19 +-- .../my_player_and_level_table.rs | 1 + .../src/module_bindings/my_player_table.rs | 1 + .../module_bindings/nearby_players_table.rs | 1 + .../src/module_bindings/player_level_table.rs | 31 ++-- .../module_bindings/player_location_table.rs | 142 ------------------ .../src/module_bindings/player_table.rs | 32 ++-- .../players_at_level_0_table.rs | 1 + 9 files changed, 39 insertions(+), 191 deletions(-) delete mode 100644 sdks/rust/tests/view-client/src/module_bindings/player_location_table.rs diff --git a/sdks/rust/tests/event-table-client/src/main.rs b/sdks/rust/tests/event-table-client/src/main.rs index 8b9620f12d1..33303f923f2 100644 --- a/sdks/rust/tests/event-table-client/src/main.rs +++ b/sdks/rust/tests/event-table-client/src/main.rs @@ -65,7 +65,7 @@ fn connect_then( let connected_result = test_counter.add_test("on_connect"); let name = db_name_or_panic(); let conn = DbConnection::builder() - .with_module_name(name) + .with_database_name(name) .with_uri(LOCALHOST) .on_connect(|ctx, _, _| { callback(ctx); diff --git a/sdks/rust/tests/view-client/src/module_bindings/mod.rs b/sdks/rust/tests/view-client/src/module_bindings/mod.rs index c5a296bc9b3..b2c244afef7 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/mod.rs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.12.0 (commit 729b96fecd3ee7ab4f69443b80cafa6d39c2782e). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; @@ -15,7 +15,6 @@ pub mod nearby_players_table; pub mod player_and_level_type; pub mod player_level_table; pub mod player_level_type; -pub mod player_location_table; pub mod player_location_type; pub mod player_table; pub mod player_type; @@ -30,7 +29,6 @@ pub use nearby_players_table::*; pub use player_and_level_type::PlayerAndLevel; pub use player_level_table::*; pub use player_level_type::PlayerLevel; -pub use player_location_table::*; pub use player_location_type::PlayerLocation; pub use player_table::*; pub use player_type::Player; @@ -92,7 +90,6 @@ pub struct DbUpdate { nearby_players: __sdk::TableUpdate, player: __sdk::TableUpdate, player_level: __sdk::TableUpdate, - player_location: __sdk::TableUpdate, players_at_level_0: __sdk::TableUpdate, } @@ -115,9 +112,6 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { "player_level" => db_update .player_level .append(player_level_table::parse_table_update(table_update)?), - "player_location" => db_update - .player_location - .append(player_location_table::parse_table_update(table_update)?), "players_at_level_0" => db_update .players_at_level_0 .append(players_at_level_0_table::parse_table_update(table_update)?), @@ -143,7 +137,6 @@ impl __sdk::DbUpdate for DbUpdate { .apply_diff_to_table::("player", &self.player) .with_updates_by_pk(|row| &row.entity_id); diff.player_level = cache.apply_diff_to_table::("player_level", &self.player_level); - diff.player_location = cache.apply_diff_to_table::("player_location", &self.player_location); diff.my_player = cache.apply_diff_to_table::("my_player", &self.my_player); diff.my_player_and_level = cache.apply_diff_to_table::("my_player_and_level", &self.my_player_and_level); @@ -171,9 +164,6 @@ impl __sdk::DbUpdate for DbUpdate { "player_level" => db_update .player_level .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), - "player_location" => db_update - .player_location - .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), "players_at_level_0" => db_update .players_at_level_0 .append(__sdk::parse_row_list_as_inserts(table_rows.rows)?), @@ -203,9 +193,6 @@ impl __sdk::DbUpdate for DbUpdate { "player_level" => db_update .player_level .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), - "player_location" => db_update - .player_location - .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), "players_at_level_0" => db_update .players_at_level_0 .append(__sdk::parse_row_list_as_deletes(table_rows.rows)?), @@ -227,7 +214,6 @@ pub struct AppliedDiff<'r> { nearby_players: __sdk::TableAppliedDiff<'r, PlayerLocation>, player: __sdk::TableAppliedDiff<'r, Player>, player_level: __sdk::TableAppliedDiff<'r, PlayerLevel>, - player_location: __sdk::TableAppliedDiff<'r, PlayerLocation>, players_at_level_0: __sdk::TableAppliedDiff<'r, Player>, __unused: std::marker::PhantomData<&'r ()>, } @@ -243,7 +229,6 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks.invoke_table_row_callbacks::("nearby_players", &self.nearby_players, event); callbacks.invoke_table_row_callbacks::("player", &self.player, event); callbacks.invoke_table_row_callbacks::("player_level", &self.player_level, event); - callbacks.invoke_table_row_callbacks::("player_location", &self.player_location, event); callbacks.invoke_table_row_callbacks::("players_at_level_0", &self.players_at_level_0, event); } } @@ -894,7 +879,6 @@ impl __sdk::SpacetimeModule for RemoteModule { nearby_players_table::register_table(client_cache); player_table::register_table(client_cache); player_level_table::register_table(client_cache); - player_location_table::register_table(client_cache); players_at_level_0_table::register_table(client_cache); } const ALL_TABLE_NAMES: &'static [&'static str] = &[ @@ -903,7 +887,6 @@ impl __sdk::SpacetimeModule for RemoteModule { "nearby_players", "player", "player_level", - "player_location", "players_at_level_0", ]; } diff --git a/sdks/rust/tests/view-client/src/module_bindings/my_player_and_level_table.rs b/sdks/rust/tests/view-client/src/module_bindings/my_player_and_level_table.rs index ac0ac773bdc..d6455ec7763 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/my_player_and_level_table.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/my_player_and_level_table.rs @@ -38,6 +38,7 @@ impl MyPlayerAndLevelTableAccess for super::RemoteTables { } pub struct MyPlayerAndLevelInsertCallbackId(__sdk::CallbackId); + pub struct MyPlayerAndLevelDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for MyPlayerAndLevelTableHandle<'ctx> { diff --git a/sdks/rust/tests/view-client/src/module_bindings/my_player_table.rs b/sdks/rust/tests/view-client/src/module_bindings/my_player_table.rs index ddb0d624e15..5b1cf86fa7e 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/my_player_table.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/my_player_table.rs @@ -38,6 +38,7 @@ impl MyPlayerTableAccess for super::RemoteTables { } pub struct MyPlayerInsertCallbackId(__sdk::CallbackId); + pub struct MyPlayerDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for MyPlayerTableHandle<'ctx> { diff --git a/sdks/rust/tests/view-client/src/module_bindings/nearby_players_table.rs b/sdks/rust/tests/view-client/src/module_bindings/nearby_players_table.rs index 9ecc2f275fe..cf35955108c 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/nearby_players_table.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/nearby_players_table.rs @@ -38,6 +38,7 @@ impl NearbyPlayersTableAccess for super::RemoteTables { } pub struct NearbyPlayersInsertCallbackId(__sdk::CallbackId); + pub struct NearbyPlayersDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for NearbyPlayersTableHandle<'ctx> { diff --git a/sdks/rust/tests/view-client/src/module_bindings/player_level_table.rs b/sdks/rust/tests/view-client/src/module_bindings/player_level_table.rs index 7cd3eec5a85..e61a83840f5 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/player_level_table.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/player_level_table.rs @@ -38,6 +38,7 @@ impl PlayerLevelTableAccess for super::RemoteTables { } pub struct PlayerLevelInsertCallbackId(__sdk::CallbackId); + pub struct PlayerLevelDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for PlayerLevelTableHandle<'ctx> { @@ -78,21 +79,6 @@ impl<'ctx> __sdk::Table for PlayerLevelTableHandle<'ctx> { } } -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - let _table = client_cache.get_or_make_table::("player_level"); - _table.add_unique_constraint::("entity_id", |row| &row.entity_id); -} - -#[doc(hidden)] -pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") - .with_cause(e) - .into() - }) -} - /// Access to the `entity_id` unique index on the table `player_level`, /// which allows point queries on the field of the same name /// via the [`PlayerLevelEntityIdUnique::find`] method. @@ -123,6 +109,21 @@ impl<'ctx> PlayerLevelEntityIdUnique<'ctx> { } } +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("player_level"); + _table.add_unique_constraint::("entity_id", |row| &row.entity_id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + #[allow(non_camel_case_types)] /// Extension trait for query builder access to the table `PlayerLevel`. /// diff --git a/sdks/rust/tests/view-client/src/module_bindings/player_location_table.rs b/sdks/rust/tests/view-client/src/module_bindings/player_location_table.rs deleted file mode 100644 index efe15384b4d..00000000000 --- a/sdks/rust/tests/view-client/src/module_bindings/player_location_table.rs +++ /dev/null @@ -1,142 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#![allow(unused, clippy::all)] -use super::player_location_type::PlayerLocation; -use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; - -/// Table handle for the table `player_location`. -/// -/// Obtain a handle from the [`PlayerLocationTableAccess::player_location`] method on [`super::RemoteTables`], -/// like `ctx.db.player_location()`. -/// -/// Users are encouraged not to explicitly reference this type, -/// but to directly chain method calls, -/// like `ctx.db.player_location().on_insert(...)`. -pub struct PlayerLocationTableHandle<'ctx> { - imp: __sdk::TableHandle, - ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, -} - -#[allow(non_camel_case_types)] -/// Extension trait for access to the table `player_location`. -/// -/// Implemented for [`super::RemoteTables`]. -pub trait PlayerLocationTableAccess { - #[allow(non_snake_case)] - /// Obtain a [`PlayerLocationTableHandle`], which mediates access to the table `player_location`. - fn player_location(&self) -> PlayerLocationTableHandle<'_>; -} - -impl PlayerLocationTableAccess for super::RemoteTables { - fn player_location(&self) -> PlayerLocationTableHandle<'_> { - PlayerLocationTableHandle { - imp: self.imp.get_table::("player_location"), - ctx: std::marker::PhantomData, - } - } -} - -pub struct PlayerLocationInsertCallbackId(__sdk::CallbackId); -pub struct PlayerLocationDeleteCallbackId(__sdk::CallbackId); - -impl<'ctx> __sdk::Table for PlayerLocationTableHandle<'ctx> { - type Row = PlayerLocation; - type EventContext = super::EventContext; - - fn count(&self) -> u64 { - self.imp.count() - } - fn iter(&self) -> impl Iterator + '_ { - self.imp.iter() - } - - type InsertCallbackId = PlayerLocationInsertCallbackId; - - fn on_insert( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, - ) -> PlayerLocationInsertCallbackId { - PlayerLocationInsertCallbackId(self.imp.on_insert(Box::new(callback))) - } - - fn remove_on_insert(&self, callback: PlayerLocationInsertCallbackId) { - self.imp.remove_on_insert(callback.0) - } - - type DeleteCallbackId = PlayerLocationDeleteCallbackId; - - fn on_delete( - &self, - callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, - ) -> PlayerLocationDeleteCallbackId { - PlayerLocationDeleteCallbackId(self.imp.on_delete(Box::new(callback))) - } - - fn remove_on_delete(&self, callback: PlayerLocationDeleteCallbackId) { - self.imp.remove_on_delete(callback.0) - } -} - -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - let _table = client_cache.get_or_make_table::("player_location"); - _table.add_unique_constraint::("entity_id", |row| &row.entity_id); -} - -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::v2::TableUpdate, -) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") - .with_cause(e) - .into() - }) -} - -/// Access to the `entity_id` unique index on the table `player_location`, -/// which allows point queries on the field of the same name -/// via the [`PlayerLocationEntityIdUnique::find`] method. -/// -/// Users are encouraged not to explicitly reference this type, -/// but to directly chain method calls, -/// like `ctx.db.player_location().entity_id().find(...)`. -pub struct PlayerLocationEntityIdUnique<'ctx> { - imp: __sdk::UniqueConstraintHandle, - phantom: std::marker::PhantomData<&'ctx super::RemoteTables>, -} - -impl<'ctx> PlayerLocationTableHandle<'ctx> { - /// Get a handle on the `entity_id` unique index on the table `player_location`. - pub fn entity_id(&self) -> PlayerLocationEntityIdUnique<'ctx> { - PlayerLocationEntityIdUnique { - imp: self.imp.get_unique_constraint::("entity_id"), - phantom: std::marker::PhantomData, - } - } -} - -impl<'ctx> PlayerLocationEntityIdUnique<'ctx> { - /// Find the subscribed row whose `entity_id` column value is equal to `col_val`, - /// if such a row is present in the client cache. - pub fn find(&self, col_val: &u64) -> Option { - self.imp.find(col_val) - } -} - -#[allow(non_camel_case_types)] -/// Extension trait for query builder access to the table `PlayerLocation`. -/// -/// Implemented for [`__sdk::QueryTableAccessor`]. -pub trait player_locationQueryTableAccess { - #[allow(non_snake_case)] - /// Get a query builder for the table `PlayerLocation`. - fn player_location(&self) -> __sdk::__query_builder::Table; -} - -impl player_locationQueryTableAccess for __sdk::QueryTableAccessor { - fn player_location(&self) -> __sdk::__query_builder::Table { - __sdk::__query_builder::Table::new("player_location") - } -} diff --git a/sdks/rust/tests/view-client/src/module_bindings/player_table.rs b/sdks/rust/tests/view-client/src/module_bindings/player_table.rs index 916d5b5db4d..a18246e13dc 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/player_table.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/player_table.rs @@ -38,6 +38,7 @@ impl PlayerTableAccess for super::RemoteTables { } pub struct PlayerInsertCallbackId(__sdk::CallbackId); + pub struct PlayerDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for PlayerTableHandle<'ctx> { @@ -78,12 +79,6 @@ impl<'ctx> __sdk::Table for PlayerTableHandle<'ctx> { } } -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - let _table = client_cache.get_or_make_table::("player"); - _table.add_unique_constraint::("entity_id", |row| &row.entity_id); - _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); -} pub struct PlayerUpdateCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::TableWithPrimaryKey for PlayerTableHandle<'ctx> { @@ -101,15 +96,6 @@ impl<'ctx> __sdk::TableWithPrimaryKey for PlayerTableHandle<'ctx> { } } -#[doc(hidden)] -pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") - .with_cause(e) - .into() - }) -} - /// Access to the `entity_id` unique index on the table `player`, /// which allows point queries on the field of the same name /// via the [`PlayerEntityIdUnique::find`] method. @@ -170,6 +156,22 @@ impl<'ctx> PlayerIdentityUnique<'ctx> { } } +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("player"); + _table.add_unique_constraint::("entity_id", |row| &row.entity_id); + _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); +} + +#[doc(hidden)] +pub(super) fn parse_table_update(raw_updates: __ws::v2::TableUpdate) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} + #[allow(non_camel_case_types)] /// Extension trait for query builder access to the table `Player`. /// diff --git a/sdks/rust/tests/view-client/src/module_bindings/players_at_level_0_table.rs b/sdks/rust/tests/view-client/src/module_bindings/players_at_level_0_table.rs index 57c3ae723eb..12b43390843 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/players_at_level_0_table.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/players_at_level_0_table.rs @@ -38,6 +38,7 @@ impl PlayersAtLevel0TableAccess for super::RemoteTables { } pub struct PlayersAtLevel0InsertCallbackId(__sdk::CallbackId); + pub struct PlayersAtLevel0DeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for PlayersAtLevel0TableHandle<'ctx> { From ef45624c4154e20cd2c90819cd81c330790e7b1f Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 16:24:00 -0500 Subject: [PATCH 13/24] Add CanBeLookupTable to client codegen for non-event tables and regenerate bindings --- crates/codegen/src/rust.rs | 12 +- .../snapshots/codegen__codegen_rust.snap | 1342 ++++++----------- .../codegen__codegen_typescript.snap | 2 +- .../src/module_bindings/mod.rs | 5 +- .../src/module_bindings/player_level_type.rs | 2 + .../module_bindings/player_location_type.rs | 2 + .../src/module_bindings/player_type.rs | 2 + 7 files changed, 489 insertions(+), 878 deletions(-) diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index 061215a9e20..411401c7251 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -778,7 +778,17 @@ impl __sdk::__query_builder::HasIxCols for {struct_name} {{ }} }} }}"# - ) + )?; + + // Event tables cannot be used as lookup tables in semijoins. + if !table.is_event { + writeln!( + out, + "\nimpl __sdk::__query_builder::CanBeLookupTable for {struct_name} {{}}" + )?; + } + + Ok(()) } pub fn implement_query_table_accessor(table: &TableDef, out: &mut impl Write, struct_name: &String) -> fmt::Result { diff --git a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap index e27bad56492..d17548e8238 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap @@ -33,8 +33,6 @@ impl __sdk::InModule for AddPlayerArgs { type Module = super::RemoteModule; } -pub struct AddPlayerCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `add_player`. /// @@ -44,70 +42,39 @@ pub trait add_player { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_add_player`] callbacks. - fn add_player(&self, name: String, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `add_player`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`AddPlayerCallbackId`] can be passed to [`Self::remove_on_add_player`] - /// to cancel the callback. - fn on_add_player(&self, callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static) -> AddPlayerCallbackId; - /// Cancel a callback previously registered by [`Self::on_add_player`], - /// causing it not to run in the future. - fn remove_on_add_player(&self, callback: AddPlayerCallbackId); -} - -impl add_player for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`add_player:add_player_then`] to run a callback after the reducer completes. fn add_player(&self, name: String, ) -> __sdk::Result<()> { - self.imp.call_reducer("add_player", AddPlayerArgs { name, }) + self.add_player_then(name, |_, _| {}) } - fn on_add_player( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static, - ) -> AddPlayerCallbackId { - AddPlayerCallbackId(self.imp.on_reducer( - "add_player", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::AddPlayer { - name, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, name, ) - }), - )) - } - fn remove_on_add_player(&self, callback: AddPlayerCallbackId) { - self.imp.remove_on_reducer("add_player", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `add_player`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_add_player { - /// Set the call-reducer flags for the reducer `add_player` to `flags`. + /// Request that the remote module invoke the reducer `add_player` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn add_player(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn add_player_then( + &self, + name: String, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_add_player for super::SetReducerFlags { - fn add_player(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("add_player", flags); +impl add_player for super::RemoteReducers { + fn add_player_then( + &self, + name: String, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(AddPlayerArgs { name, }, callback) } } @@ -143,8 +110,6 @@ impl __sdk::InModule for AddPrivateArgs { type Module = super::RemoteModule; } -pub struct AddPrivateCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `add_private`. /// @@ -154,70 +119,39 @@ pub trait add_private { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_add_private`] callbacks. - fn add_private(&self, name: String, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `add_private`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`AddPrivateCallbackId`] can be passed to [`Self::remove_on_add_private`] - /// to cancel the callback. - fn on_add_private(&self, callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static) -> AddPrivateCallbackId; - /// Cancel a callback previously registered by [`Self::on_add_private`], - /// causing it not to run in the future. - fn remove_on_add_private(&self, callback: AddPrivateCallbackId); -} - -impl add_private for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`add_private:add_private_then`] to run a callback after the reducer completes. fn add_private(&self, name: String, ) -> __sdk::Result<()> { - self.imp.call_reducer("add_private", AddPrivateArgs { name, }) + self.add_private_then(name, |_, _| {}) } - fn on_add_private( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static, - ) -> AddPrivateCallbackId { - AddPrivateCallbackId(self.imp.on_reducer( - "add_private", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::AddPrivate { - name, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, name, ) - }), - )) - } - fn remove_on_add_private(&self, callback: AddPrivateCallbackId) { - self.imp.remove_on_reducer("add_private", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `add_private`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_add_private { - /// Set the call-reducer flags for the reducer `add_private` to `flags`. + /// Request that the remote module invoke the reducer `add_private` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn add_private(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn add_private_then( + &self, + name: String, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_add_private for super::SetReducerFlags { - fn add_private(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("add_private", flags); +impl add_private for super::RemoteReducers { + fn add_private_then( + &self, + name: String, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(AddPrivateArgs { name, }, callback) } } @@ -255,8 +189,6 @@ impl __sdk::InModule for AddArgs { type Module = super::RemoteModule; } -pub struct AddCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `add`. /// @@ -266,72 +198,42 @@ pub trait add { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_add`] callbacks. - fn add(&self, name: String, -age: u8, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `add`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`AddCallbackId`] can be passed to [`Self::remove_on_add`] - /// to cancel the callback. - fn on_add(&self, callback: impl FnMut(&super::ReducerEventContext, &String, &u8, ) + Send + 'static) -> AddCallbackId; - /// Cancel a callback previously registered by [`Self::on_add`], - /// causing it not to run in the future. - fn remove_on_add(&self, callback: AddCallbackId); -} - -impl add for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`add:add_then`] to run a callback after the reducer completes. fn add(&self, name: String, age: u8, ) -> __sdk::Result<()> { - self.imp.call_reducer("add", AddArgs { name, age, }) + self.add_then(name, age, |_, _| {}) } - fn on_add( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &String, &u8, ) + Send + 'static, - ) -> AddCallbackId { - AddCallbackId(self.imp.on_reducer( - "add", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::Add { - name, age, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, name, age, ) - }), - )) - } - fn remove_on_add(&self, callback: AddCallbackId) { - self.imp.remove_on_reducer("add", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `add`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_add { - /// Set the call-reducer flags for the reducer `add` to `flags`. + /// Request that the remote module invoke the reducer `add` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn add(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn add_then( + &self, + name: String, +age: u8, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_add for super::SetReducerFlags { - fn add(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("add", flags); +impl add for super::RemoteReducers { + fn add_then( + &self, + name: String, +age: u8, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(AddArgs { name, age, }, callback) } } @@ -364,8 +266,6 @@ impl __sdk::InModule for AssertCallerIdentityIsModuleIdentityArgs { type Module = super::RemoteModule; } -pub struct AssertCallerIdentityIsModuleIdentityCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `assert_caller_identity_is_module_identity`. /// @@ -375,68 +275,36 @@ pub trait assert_caller_identity_is_module_identity { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_assert_caller_identity_is_module_identity`] callbacks. - fn assert_caller_identity_is_module_identity(&self, ) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `assert_caller_identity_is_module_identity`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`AssertCallerIdentityIsModuleIdentityCallbackId`] can be passed to [`Self::remove_on_assert_caller_identity_is_module_identity`] - /// to cancel the callback. - fn on_assert_caller_identity_is_module_identity(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> AssertCallerIdentityIsModuleIdentityCallbackId; - /// Cancel a callback previously registered by [`Self::on_assert_caller_identity_is_module_identity`], - /// causing it not to run in the future. - fn remove_on_assert_caller_identity_is_module_identity(&self, callback: AssertCallerIdentityIsModuleIdentityCallbackId); -} - -impl assert_caller_identity_is_module_identity for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`assert_caller_identity_is_module_identity:assert_caller_identity_is_module_identity_then`] to run a callback after the reducer completes. fn assert_caller_identity_is_module_identity(&self, ) -> __sdk::Result<()> { - self.imp.call_reducer("assert_caller_identity_is_module_identity", AssertCallerIdentityIsModuleIdentityArgs { }) + self.assert_caller_identity_is_module_identity_then( |_, _| {}) } - fn on_assert_caller_identity_is_module_identity( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, - ) -> AssertCallerIdentityIsModuleIdentityCallbackId { - AssertCallerIdentityIsModuleIdentityCallbackId(self.imp.on_reducer( - "assert_caller_identity_is_module_identity", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::AssertCallerIdentityIsModuleIdentity { - - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, ) - }), - )) - } - fn remove_on_assert_caller_identity_is_module_identity(&self, callback: AssertCallerIdentityIsModuleIdentityCallbackId) { - self.imp.remove_on_reducer("assert_caller_identity_is_module_identity", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `assert_caller_identity_is_module_identity`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_assert_caller_identity_is_module_identity { - /// Set the call-reducer flags for the reducer `assert_caller_identity_is_module_identity` to `flags`. + /// Request that the remote module invoke the reducer `assert_caller_identity_is_module_identity` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn assert_caller_identity_is_module_identity(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn assert_caller_identity_is_module_identity_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_assert_caller_identity_is_module_identity for super::SetReducerFlags { - fn assert_caller_identity_is_module_identity(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("assert_caller_identity_is_module_identity", flags); +impl assert_caller_identity_is_module_identity for super::RemoteReducers { + fn assert_caller_identity_is_module_identity_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(AssertCallerIdentityIsModuleIdentityArgs { }, callback) } } @@ -497,8 +365,6 @@ impl __sdk::InModule for DeletePlayerArgs { type Module = super::RemoteModule; } -pub struct DeletePlayerCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `delete_player`. /// @@ -508,70 +374,39 @@ pub trait delete_player { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_delete_player`] callbacks. - fn delete_player(&self, id: u64, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_player`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`DeletePlayerCallbackId`] can be passed to [`Self::remove_on_delete_player`] - /// to cancel the callback. - fn on_delete_player(&self, callback: impl FnMut(&super::ReducerEventContext, &u64, ) + Send + 'static) -> DeletePlayerCallbackId; - /// Cancel a callback previously registered by [`Self::on_delete_player`], - /// causing it not to run in the future. - fn remove_on_delete_player(&self, callback: DeletePlayerCallbackId); -} - -impl delete_player for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`delete_player:delete_player_then`] to run a callback after the reducer completes. fn delete_player(&self, id: u64, ) -> __sdk::Result<()> { - self.imp.call_reducer("delete_player", DeletePlayerArgs { id, }) - } - fn on_delete_player( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &u64, ) + Send + 'static, - ) -> DeletePlayerCallbackId { - DeletePlayerCallbackId(self.imp.on_reducer( - "delete_player", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::DeletePlayer { - id, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, id, ) - }), - )) - } - fn remove_on_delete_player(&self, callback: DeletePlayerCallbackId) { - self.imp.remove_on_reducer("delete_player", callback.0) + self.delete_player_then(id, |_, _| {}) } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `delete_player`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_delete_player { - /// Set the call-reducer flags for the reducer `delete_player` to `flags`. + /// Request that the remote module invoke the reducer `delete_player` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn delete_player(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn delete_player_then( + &self, + id: u64, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_delete_player for super::SetReducerFlags { - fn delete_player(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("delete_player", flags); +impl delete_player for super::RemoteReducers { + fn delete_player_then( + &self, + id: u64, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(DeletePlayerArgs { id, }, callback) } } @@ -607,8 +442,6 @@ impl __sdk::InModule for DeletePlayersByNameArgs { type Module = super::RemoteModule; } -pub struct DeletePlayersByNameCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `delete_players_by_name`. /// @@ -618,70 +451,39 @@ pub trait delete_players_by_name { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_delete_players_by_name`] callbacks. - fn delete_players_by_name(&self, name: String, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_players_by_name`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`DeletePlayersByNameCallbackId`] can be passed to [`Self::remove_on_delete_players_by_name`] - /// to cancel the callback. - fn on_delete_players_by_name(&self, callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static) -> DeletePlayersByNameCallbackId; - /// Cancel a callback previously registered by [`Self::on_delete_players_by_name`], - /// causing it not to run in the future. - fn remove_on_delete_players_by_name(&self, callback: DeletePlayersByNameCallbackId); -} - -impl delete_players_by_name for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`delete_players_by_name:delete_players_by_name_then`] to run a callback after the reducer completes. fn delete_players_by_name(&self, name: String, ) -> __sdk::Result<()> { - self.imp.call_reducer("delete_players_by_name", DeletePlayersByNameArgs { name, }) - } - fn on_delete_players_by_name( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &String, ) + Send + 'static, - ) -> DeletePlayersByNameCallbackId { - DeletePlayersByNameCallbackId(self.imp.on_reducer( - "delete_players_by_name", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::DeletePlayersByName { - name, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, name, ) - }), - )) - } - fn remove_on_delete_players_by_name(&self, callback: DeletePlayersByNameCallbackId) { - self.imp.remove_on_reducer("delete_players_by_name", callback.0) + self.delete_players_by_name_then(name, |_, _| {}) } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `delete_players_by_name`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_delete_players_by_name { - /// Set the call-reducer flags for the reducer `delete_players_by_name` to `flags`. + /// Request that the remote module invoke the reducer `delete_players_by_name` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn delete_players_by_name(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn delete_players_by_name_then( + &self, + name: String, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_delete_players_by_name for super::SetReducerFlags { - fn delete_players_by_name(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("delete_players_by_name", flags); +impl delete_players_by_name for super::RemoteReducers { + fn delete_players_by_name_then( + &self, + name: String, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(DeletePlayersByNameArgs { name, }, callback) } } @@ -832,6 +634,8 @@ impl __sdk::__query_builder::HasIxCols for HasSpecialStuff { } } +impl __sdk::__query_builder::CanBeLookupTable for HasSpecialStuff {} + ''' "list_over_age_reducer.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -864,8 +668,6 @@ impl __sdk::InModule for ListOverAgeArgs { type Module = super::RemoteModule; } -pub struct ListOverAgeCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `list_over_age`. /// @@ -875,70 +677,39 @@ pub trait list_over_age { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_list_over_age`] callbacks. - fn list_over_age(&self, age: u8, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `list_over_age`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`ListOverAgeCallbackId`] can be passed to [`Self::remove_on_list_over_age`] - /// to cancel the callback. - fn on_list_over_age(&self, callback: impl FnMut(&super::ReducerEventContext, &u8, ) + Send + 'static) -> ListOverAgeCallbackId; - /// Cancel a callback previously registered by [`Self::on_list_over_age`], - /// causing it not to run in the future. - fn remove_on_list_over_age(&self, callback: ListOverAgeCallbackId); -} - -impl list_over_age for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`list_over_age:list_over_age_then`] to run a callback after the reducer completes. fn list_over_age(&self, age: u8, ) -> __sdk::Result<()> { - self.imp.call_reducer("list_over_age", ListOverAgeArgs { age, }) - } - fn on_list_over_age( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &u8, ) + Send + 'static, - ) -> ListOverAgeCallbackId { - ListOverAgeCallbackId(self.imp.on_reducer( - "list_over_age", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::ListOverAge { - age, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, age, ) - }), - )) - } - fn remove_on_list_over_age(&self, callback: ListOverAgeCallbackId) { - self.imp.remove_on_reducer("list_over_age", callback.0) + self.list_over_age_then(age, |_, _| {}) } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `list_over_age`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_list_over_age { - /// Set the call-reducer flags for the reducer `list_over_age` to `flags`. + /// Request that the remote module invoke the reducer `list_over_age` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn list_over_age(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn list_over_age_then( + &self, + age: u8, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_list_over_age for super::SetReducerFlags { - fn list_over_age(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("list_over_age", flags); +impl list_over_age for super::RemoteReducers { + fn list_over_age_then( + &self, + age: u8, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(ListOverAgeArgs { age, }, callback) } } @@ -971,8 +742,6 @@ impl __sdk::InModule for LogModuleIdentityArgs { type Module = super::RemoteModule; } -pub struct LogModuleIdentityCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `log_module_identity`. /// @@ -982,68 +751,36 @@ pub trait log_module_identity { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_log_module_identity`] callbacks. - fn log_module_identity(&self, ) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `log_module_identity`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`LogModuleIdentityCallbackId`] can be passed to [`Self::remove_on_log_module_identity`] - /// to cancel the callback. - fn on_log_module_identity(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> LogModuleIdentityCallbackId; - /// Cancel a callback previously registered by [`Self::on_log_module_identity`], - /// causing it not to run in the future. - fn remove_on_log_module_identity(&self, callback: LogModuleIdentityCallbackId); -} - -impl log_module_identity for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`log_module_identity:log_module_identity_then`] to run a callback after the reducer completes. fn log_module_identity(&self, ) -> __sdk::Result<()> { - self.imp.call_reducer("log_module_identity", LogModuleIdentityArgs { }) + self.log_module_identity_then( |_, _| {}) } - fn on_log_module_identity( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, - ) -> LogModuleIdentityCallbackId { - LogModuleIdentityCallbackId(self.imp.on_reducer( - "log_module_identity", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::LogModuleIdentity { - - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, ) - }), - )) - } - fn remove_on_log_module_identity(&self, callback: LogModuleIdentityCallbackId) { - self.imp.remove_on_reducer("log_module_identity", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `log_module_identity`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_log_module_identity { - /// Set the call-reducer flags for the reducer `log_module_identity` to `flags`. + /// Request that the remote module invoke the reducer `log_module_identity` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn log_module_identity(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn log_module_identity_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_log_module_identity for super::SetReducerFlags { - fn log_module_identity(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("log_module_identity", flags); +impl log_module_identity for super::RemoteReducers { + fn log_module_identity_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(LogModuleIdentityArgs { }, callback) } } @@ -1094,6 +831,8 @@ impl LoggedOutPlayerTableAccess for super::RemoteTables { } pub struct LoggedOutPlayerInsertCallbackId(__sdk::CallbackId); + + pub struct LoggedOutPlayerDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for LoggedOutPlayerTableHandle<'ctx> { @@ -1130,14 +869,6 @@ impl<'ctx> __sdk::Table for LoggedOutPlayerTableHandle<'ctx> { } } -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - - let _table = client_cache.get_or_make_table::("logged_out_player"); - _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); - _table.add_unique_constraint::("player_id", |row| &row.player_id); - _table.add_unique_constraint::("name", |row| &row.name); -} pub struct LoggedOutPlayerUpdateCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::TableWithPrimaryKey for LoggedOutPlayerTableHandle<'ctx> { @@ -1155,19 +886,6 @@ impl<'ctx> __sdk::TableWithPrimaryKey for LoggedOutPlayerTableHandle<'ctx> { } } - -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, -) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse( - "TableUpdate", - "TableUpdate", - ).with_cause(e).into() - }) -} - /// Access to the `identity` unique index on the table `logged_out_player`, /// which allows point queries on the field of the same name /// via the [`LoggedOutPlayerIdentityUnique::find`] method. @@ -1258,6 +976,27 @@ pub(super) fn parse_table_update( } } +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + + let _table = client_cache.get_or_make_table::("logged_out_player"); + _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); + _table.add_unique_constraint::("player_id", |row| &row.player_id); + _table.add_unique_constraint::("name", |row| &row.name); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse( + "TableUpdate", + "TableUpdate", + ).with_cause(e).into() + }) +} + #[allow(non_camel_case_types)] /// Extension trait for query builder access to the table `Player`. /// @@ -1352,18 +1091,18 @@ pub use person_table::*; pub use player_table::*; pub use test_d_table::*; pub use test_f_table::*; -pub use add_reducer::{add, set_flags_for_add, AddCallbackId}; -pub use add_player_reducer::{add_player, set_flags_for_add_player, AddPlayerCallbackId}; -pub use add_private_reducer::{add_private, set_flags_for_add_private, AddPrivateCallbackId}; -pub use assert_caller_identity_is_module_identity_reducer::{assert_caller_identity_is_module_identity, set_flags_for_assert_caller_identity_is_module_identity, AssertCallerIdentityIsModuleIdentityCallbackId}; -pub use delete_player_reducer::{delete_player, set_flags_for_delete_player, DeletePlayerCallbackId}; -pub use delete_players_by_name_reducer::{delete_players_by_name, set_flags_for_delete_players_by_name, DeletePlayersByNameCallbackId}; -pub use list_over_age_reducer::{list_over_age, set_flags_for_list_over_age, ListOverAgeCallbackId}; -pub use log_module_identity_reducer::{log_module_identity, set_flags_for_log_module_identity, LogModuleIdentityCallbackId}; -pub use query_private_reducer::{query_private, set_flags_for_query_private, QueryPrivateCallbackId}; -pub use say_hello_reducer::{say_hello, set_flags_for_say_hello, SayHelloCallbackId}; -pub use test_reducer::{test, set_flags_for_test, TestCallbackId}; -pub use test_btree_index_args_reducer::{test_btree_index_args, set_flags_for_test_btree_index_args, TestBtreeIndexArgsCallbackId}; +pub use add_reducer::add; +pub use add_player_reducer::add_player; +pub use add_private_reducer::add_private; +pub use assert_caller_identity_is_module_identity_reducer::assert_caller_identity_is_module_identity; +pub use delete_player_reducer::delete_player; +pub use delete_players_by_name_reducer::delete_players_by_name; +pub use list_over_age_reducer::list_over_age; +pub use log_module_identity_reducer::log_module_identity; +pub use query_private_reducer::query_private; +pub use say_hello_reducer::say_hello; +pub use test_reducer::test; +pub use test_btree_index_args_reducer::test_btree_index_args; pub use get_my_schema_via_http_procedure::get_my_schema_via_http; pub use return_value_procedure::return_value; pub use sleep_one_second_procedure::sleep_one_second; @@ -1432,30 +1171,63 @@ impl __sdk::Reducer for Reducer { _ => unreachable!(), } } -} -impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { - type Error = __sdk::Error; -fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result { - match &value.reducer_name[..] { + #[allow(clippy::clone_on_copy)] +fn args_bsatn(&self) -> Result, __sats::bsatn::EncodeError> { + match self { Reducer::Add{ name, age, -} "add" => Ok(__sdk::parse_reducer_args::("add", &value.args)?.into()),Reducer::AddPlayer{ +} => __sats::bsatn::to_vec(&add_reducer::AddArgs { + name: name.clone(), + age: age.clone(), +}), + Reducer::AddPlayer{ name, -} "add_player" => Ok(__sdk::parse_reducer_args::("add_player", &value.args)?.into()),Reducer::AddPrivate{ +} => __sats::bsatn::to_vec(&add_player_reducer::AddPlayerArgs { + name: name.clone(), +}), + Reducer::AddPrivate{ name, -} "add_private" => Ok(__sdk::parse_reducer_args::("add_private", &value.args)?.into()),Reducer::AssertCallerIdentityIsModuleIdentity"assert_caller_identity_is_module_identity" => Ok(__sdk::parse_reducer_args::("assert_caller_identity_is_module_identity", &value.args)?.into()),Reducer::DeletePlayer{ +} => __sats::bsatn::to_vec(&add_private_reducer::AddPrivateArgs { + name: name.clone(), +}), + Reducer::AssertCallerIdentityIsModuleIdentity => __sats::bsatn::to_vec(&assert_caller_identity_is_module_identity_reducer::AssertCallerIdentityIsModuleIdentityArgs { + }), +Reducer::DeletePlayer{ id, -} "delete_player" => Ok(__sdk::parse_reducer_args::("delete_player", &value.args)?.into()),Reducer::DeletePlayersByName{ +} => __sats::bsatn::to_vec(&delete_player_reducer::DeletePlayerArgs { + id: id.clone(), +}), + Reducer::DeletePlayersByName{ name, -} "delete_players_by_name" => Ok(__sdk::parse_reducer_args::("delete_players_by_name", &value.args)?.into()),Reducer::ListOverAge{ +} => __sats::bsatn::to_vec(&delete_players_by_name_reducer::DeletePlayersByNameArgs { + name: name.clone(), +}), + Reducer::ListOverAge{ age, -} "list_over_age" => Ok(__sdk::parse_reducer_args::("list_over_age", &value.args)?.into()),Reducer::LogModuleIdentity"log_module_identity" => Ok(__sdk::parse_reducer_args::("log_module_identity", &value.args)?.into()),Reducer::QueryPrivate"query_private" => Ok(__sdk::parse_reducer_args::("query_private", &value.args)?.into()),Reducer::SayHello"say_hello" => Ok(__sdk::parse_reducer_args::("say_hello", &value.args)?.into()),Reducer::Test{ +} => __sats::bsatn::to_vec(&list_over_age_reducer::ListOverAgeArgs { + age: age.clone(), +}), + Reducer::LogModuleIdentity => __sats::bsatn::to_vec(&log_module_identity_reducer::LogModuleIdentityArgs { + }), +Reducer::QueryPrivate => __sats::bsatn::to_vec(&query_private_reducer::QueryPrivateArgs { + }), +Reducer::SayHello => __sats::bsatn::to_vec(&say_hello_reducer::SayHelloArgs { + }), +Reducer::Test{ arg, arg_2, arg_3, arg_4, -} "test" => Ok(__sdk::parse_reducer_args::("test", &value.args)?.into()),Reducer::TestBtreeIndexArgs"test_btree_index_args" => Ok(__sdk::parse_reducer_args::("test_btree_index_args", &value.args)?.into()),unknown => Err(__sdk::InternalError::unknown_name("reducer", unknown, "ReducerCallInfo").into()), +} => __sats::bsatn::to_vec(&test_reducer::TestArgs { + arg: arg.clone(), + arg_2: arg_2.clone(), + arg_3: arg_3.clone(), + arg_4: arg_4.clone(), +}), + Reducer::TestBtreeIndexArgs => __sats::bsatn::to_vec(&test_btree_index_args_reducer::TestBtreeIndexArgsArgs { + }), +_ => unreachable!(), } } } @@ -1473,11 +1245,11 @@ pub struct DbUpdate { } -impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { +impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate { type Error = __sdk::Error; - fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result { + fn try_from(raw: __ws::v2::TransactionUpdate) -> Result { let mut db_update = DbUpdate::default(); - for table_update in raw.tables { + for table_update in __sdk::transaction_update_iter_table_updates(raw) { match &table_update.table_name[..] { "logged_out_player" => db_update.logged_out_player.append(logged_out_player_table::parse_table_update(table_update)?), @@ -1602,20 +1374,6 @@ impl __sdk::InModule for RemoteProcedures { type Module = RemoteModule; } -#[doc(hidden)] -/// The `set_reducer_flags` field of [`DbConnection`], -/// with methods provided by extension traits for each reducer defined by the module. -/// Each method sets the flags for the reducer with the same name. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub struct SetReducerFlags { - imp: __sdk::DbContextImpl, -} - -impl __sdk::InModule for SetReducerFlags { - type Module = RemoteModule; -} - /// The `db` field of [`EventContext`] and [`DbConnection`], /// with methods provided by extension traits for each table defined by the module. pub struct RemoteTables { @@ -1648,11 +1406,6 @@ pub struct DbConnection { /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, #[doc(hidden)] - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, @@ -1668,7 +1421,6 @@ impl __sdk::DbContext for DbConnection { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1679,9 +1431,6 @@ impl __sdk::DbContext for DbConnection { fn procedures(&self) -> &Self::Procedures { &self.procedures } - fn set_reducer_flags(&self) -> &Self::SetReducerFlags { - &self.set_reducer_flags - } fn is_active(&self) -> bool { self.imp.is_active() @@ -1785,7 +1534,6 @@ impl __sdk::DbConnection for DbConnection { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, - set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } } @@ -1837,13 +1585,11 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle { pub trait RemoteDbContext: __sdk::DbContext< DbView = RemoteTables, Reducers = RemoteReducers, - SetReducerFlags = SetReducerFlags, SubscriptionBuilder = __sdk::SubscriptionBuilder, > {} impl, >> RemoteDbContext for Ctx {} @@ -1855,11 +1601,6 @@ pub struct EventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -1876,7 +1617,6 @@ impl __sdk::AbstractEventContext for EventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, - set_reducer_flags: SetReducerFlags { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, event, imp, @@ -1892,7 +1632,6 @@ impl __sdk::DbContext for EventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1903,9 +1642,6 @@ impl __sdk::DbContext for EventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } - fn set_reducer_flags(&self) -> &Self::SetReducerFlags { - &self.set_reducer_flags - } fn is_active(&self) -> bool { self.imp.is_active() @@ -1941,11 +1677,6 @@ pub struct ReducerEventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -1962,7 +1693,6 @@ impl __sdk::AbstractEventContext for ReducerEventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, - set_reducer_flags: SetReducerFlags { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, event, imp, @@ -1978,7 +1708,6 @@ impl __sdk::DbContext for ReducerEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -1989,9 +1718,6 @@ impl __sdk::DbContext for ReducerEventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } - fn set_reducer_flags(&self) -> &Self::SetReducerFlags { - &self.set_reducer_flags - } fn is_active(&self) -> bool { self.imp.is_active() @@ -2026,11 +1752,6 @@ pub struct ProcedureEventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, imp: __sdk::DbContextImpl, @@ -2046,7 +1767,6 @@ impl __sdk::AbstractEventContext for ProcedureEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, - set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } } @@ -2060,7 +1780,6 @@ impl __sdk::DbContext for ProcedureEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -2071,9 +1790,6 @@ impl __sdk::DbContext for ProcedureEventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } - fn set_reducer_flags(&self) -> &Self::SetReducerFlags { - &self.set_reducer_flags - } fn is_active(&self) -> bool { self.imp.is_active() @@ -2108,11 +1824,6 @@ pub struct SubscriptionEventContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, imp: __sdk::DbContextImpl, @@ -2128,7 +1839,6 @@ impl __sdk::AbstractEventContext for SubscriptionEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, - set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } } @@ -2142,7 +1852,6 @@ impl __sdk::DbContext for SubscriptionEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -2153,9 +1862,6 @@ impl __sdk::DbContext for SubscriptionEventContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } - fn set_reducer_flags(&self) -> &Self::SetReducerFlags { - &self.set_reducer_flags - } fn is_active(&self) -> bool { self.imp.is_active() @@ -2191,11 +1897,6 @@ pub struct ErrorContext { pub db: RemoteTables, /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`]. pub reducers: RemoteReducers, - /// Access to setting the call-flags of each reducer defined for each reducer defined by the module - /// via extension traits implemented for [`SetReducerFlags`]. - /// - /// This type is currently unstable and may be removed without a major version bump. - pub set_reducer_flags: SetReducerFlags, /// Access to procedures defined by the module via extension traits implemented for [`RemoteProcedures`]. pub procedures: RemoteProcedures, /// The event which caused these callbacks to run. @@ -2212,7 +1913,6 @@ impl __sdk::AbstractEventContext for ErrorContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, - set_reducer_flags: SetReducerFlags { imp: imp.clone() }, procedures: RemoteProcedures { imp: imp.clone() }, event, imp, @@ -2228,7 +1928,6 @@ impl __sdk::DbContext for ErrorContext { type DbView = RemoteTables; type Reducers = RemoteReducers; type Procedures = RemoteProcedures; - type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { &self.db @@ -2239,9 +1938,6 @@ impl __sdk::DbContext for ErrorContext { fn procedures(&self) -> &Self::Procedures { &self.procedures } - fn set_reducer_flags(&self) -> &Self::SetReducerFlags { - &self.set_reducer_flags - } fn is_active(&self) -> bool { self.imp.is_active() @@ -2281,7 +1977,7 @@ impl __sdk::SpacetimeModule for RemoteModule { type Reducer = Reducer; type DbView = RemoteTables; type Reducers = RemoteReducers; - type SetReducerFlags = SetReducerFlags; + type Procedures = RemoteProcedures; type DbUpdate = DbUpdate; type AppliedDiff<'r> = AppliedDiff<'r>; type SubscriptionHandle = SubscriptionHandle; @@ -2351,6 +2047,8 @@ impl MyPlayerTableAccess for super::RemoteTables { } pub struct MyPlayerInsertCallbackId(__sdk::CallbackId); + + pub struct MyPlayerDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for MyPlayerTableHandle<'ctx> { @@ -2395,7 +2093,7 @@ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache, + raw_updates: __ws::v2::TableUpdate, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -2528,6 +2226,8 @@ impl PersonTableAccess for super::RemoteTables { } pub struct PersonInsertCallbackId(__sdk::CallbackId); + + pub struct PersonDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for PersonTableHandle<'ctx> { @@ -2564,12 +2264,6 @@ impl<'ctx> __sdk::Table for PersonTableHandle<'ctx> { } } -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - - let _table = client_cache.get_or_make_table::("person"); - _table.add_unique_constraint::("id", |row| &row.id); -} pub struct PersonUpdateCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::TableWithPrimaryKey for PersonTableHandle<'ctx> { @@ -2587,19 +2281,6 @@ impl<'ctx> __sdk::TableWithPrimaryKey for PersonTableHandle<'ctx> { } } - -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, -) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse( - "TableUpdate", - "TableUpdate", - ).with_cause(e).into() - }) -} - /// Access to the `id` unique index on the table `person`, /// which allows point queries on the field of the same name /// via the [`PersonIdUnique::find`] method. @@ -2630,6 +2311,25 @@ pub(super) fn parse_table_update( } } +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + + let _table = client_cache.get_or_make_table::("person"); + _table.add_unique_constraint::("id", |row| &row.id); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse( + "TableUpdate", + "TableUpdate", + ).with_cause(e).into() + }) +} + #[allow(non_camel_case_types)] /// Extension trait for query builder access to the table `Person`. /// @@ -2714,6 +2414,8 @@ impl __sdk::__query_builder::HasIxCols for Person { } } +impl __sdk::__query_builder::CanBeLookupTable for Person {} + ''' "pk_multi_identity_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -2779,6 +2481,8 @@ impl __sdk::__query_builder::HasIxCols for PkMultiIdentity { } } +impl __sdk::__query_builder::CanBeLookupTable for PkMultiIdentity {} + ''' "player_table.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -2826,6 +2530,8 @@ impl PlayerTableAccess for super::RemoteTables { } pub struct PlayerInsertCallbackId(__sdk::CallbackId); + + pub struct PlayerDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for PlayerTableHandle<'ctx> { @@ -2862,14 +2568,6 @@ impl<'ctx> __sdk::Table for PlayerTableHandle<'ctx> { } } -#[doc(hidden)] -pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { - - let _table = client_cache.get_or_make_table::("player"); - _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); - _table.add_unique_constraint::("player_id", |row| &row.player_id); - _table.add_unique_constraint::("name", |row| &row.name); -} pub struct PlayerUpdateCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::TableWithPrimaryKey for PlayerTableHandle<'ctx> { @@ -2887,19 +2585,6 @@ impl<'ctx> __sdk::TableWithPrimaryKey for PlayerTableHandle<'ctx> { } } - -#[doc(hidden)] -pub(super) fn parse_table_update( - raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, -) -> __sdk::Result<__sdk::TableUpdate> { - __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { - __sdk::InternalError::failed_parse( - "TableUpdate", - "TableUpdate", - ).with_cause(e).into() - }) -} - /// Access to the `identity` unique index on the table `player`, /// which allows point queries on the field of the same name /// via the [`PlayerIdentityUnique::find`] method. @@ -2990,6 +2675,27 @@ pub(super) fn parse_table_update( } } +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + + let _table = client_cache.get_or_make_table::("player"); + _table.add_unique_constraint::<__sdk::Identity>("identity", |row| &row.identity); + _table.add_unique_constraint::("player_id", |row| &row.player_id); + _table.add_unique_constraint::("name", |row| &row.name); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::v2::TableUpdate, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse( + "TableUpdate", + "TableUpdate", + ).with_cause(e).into() + }) +} + #[allow(non_camel_case_types)] /// Extension trait for query builder access to the table `Player`. /// @@ -3076,6 +2782,8 @@ impl __sdk::__query_builder::HasIxCols for Player { } } +impl __sdk::__query_builder::CanBeLookupTable for Player {} + ''' "point_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3137,6 +2845,8 @@ impl __sdk::__query_builder::HasIxCols for Point { } } +impl __sdk::__query_builder::CanBeLookupTable for Point {} + ''' "private_table_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3195,6 +2905,8 @@ impl __sdk::__query_builder::HasIxCols for PrivateTable { } } +impl __sdk::__query_builder::CanBeLookupTable for PrivateTable {} + ''' "query_private_reducer.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3224,8 +2936,6 @@ impl __sdk::InModule for QueryPrivateArgs { type Module = super::RemoteModule; } -pub struct QueryPrivateCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `query_private`. /// @@ -3235,68 +2945,36 @@ pub trait query_private { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_query_private`] callbacks. - fn query_private(&self, ) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `query_private`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`QueryPrivateCallbackId`] can be passed to [`Self::remove_on_query_private`] - /// to cancel the callback. - fn on_query_private(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> QueryPrivateCallbackId; - /// Cancel a callback previously registered by [`Self::on_query_private`], - /// causing it not to run in the future. - fn remove_on_query_private(&self, callback: QueryPrivateCallbackId); -} - -impl query_private for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`query_private:query_private_then`] to run a callback after the reducer completes. fn query_private(&self, ) -> __sdk::Result<()> { - self.imp.call_reducer("query_private", QueryPrivateArgs { }) + self.query_private_then( |_, _| {}) } - fn on_query_private( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, - ) -> QueryPrivateCallbackId { - QueryPrivateCallbackId(self.imp.on_reducer( - "query_private", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::QueryPrivate { - - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, ) - }), - )) - } - fn remove_on_query_private(&self, callback: QueryPrivateCallbackId) { - self.imp.remove_on_reducer("query_private", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `query_private`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_query_private { - /// Set the call-reducer flags for the reducer `query_private` to `flags`. + /// Request that the remote module invoke the reducer `query_private` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn query_private(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn query_private_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_query_private for super::SetReducerFlags { - fn query_private(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("query_private", flags); +impl query_private for super::RemoteReducers { + fn query_private_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(QueryPrivateArgs { }, callback) } } @@ -3358,6 +3036,8 @@ impl __sdk::__query_builder::HasIxCols for RemoveTable { } } +impl __sdk::__query_builder::CanBeLookupTable for RemoveTable {} + ''' "repeating_test_arg_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3424,6 +3104,8 @@ impl __sdk::__query_builder::HasIxCols for RepeatingTestArg { } } +impl __sdk::__query_builder::CanBeLookupTable for RepeatingTestArg {} + ''' "return_value_procedure.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3512,8 +3194,6 @@ impl __sdk::InModule for SayHelloArgs { type Module = super::RemoteModule; } -pub struct SayHelloCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `say_hello`. /// @@ -3523,68 +3203,36 @@ pub trait say_hello { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_say_hello`] callbacks. - fn say_hello(&self, ) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `say_hello`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`SayHelloCallbackId`] can be passed to [`Self::remove_on_say_hello`] - /// to cancel the callback. - fn on_say_hello(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> SayHelloCallbackId; - /// Cancel a callback previously registered by [`Self::on_say_hello`], - /// causing it not to run in the future. - fn remove_on_say_hello(&self, callback: SayHelloCallbackId); -} - -impl say_hello for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`say_hello:say_hello_then`] to run a callback after the reducer completes. fn say_hello(&self, ) -> __sdk::Result<()> { - self.imp.call_reducer("say_hello", SayHelloArgs { }) - } - fn on_say_hello( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, - ) -> SayHelloCallbackId { - SayHelloCallbackId(self.imp.on_reducer( - "say_hello", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::SayHello { - - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, ) - }), - )) - } - fn remove_on_say_hello(&self, callback: SayHelloCallbackId) { - self.imp.remove_on_reducer("say_hello", callback.0) + self.say_hello_then( |_, _| {}) } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `say_hello`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_say_hello { - /// Set the call-reducer flags for the reducer `say_hello` to `flags`. + /// Request that the remote module invoke the reducer `say_hello` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn say_hello(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn say_hello_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_say_hello for super::SetReducerFlags { - fn say_hello(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("say_hello", flags); +impl say_hello for super::RemoteReducers { + fn say_hello_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(SayHelloArgs { }, callback) } } @@ -3708,6 +3356,8 @@ impl __sdk::__query_builder::HasIxCols for TestA { } } +impl __sdk::__query_builder::CanBeLookupTable for TestA {} + ''' "test_b_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3762,8 +3412,6 @@ impl __sdk::InModule for TestBtreeIndexArgsArgs { type Module = super::RemoteModule; } -pub struct TestBtreeIndexArgsCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `test_btree_index_args`. /// @@ -3773,68 +3421,36 @@ pub trait test_btree_index_args { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_test_btree_index_args`] callbacks. - fn test_btree_index_args(&self, ) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `test_btree_index_args`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. - /// - /// The returned [`TestBtreeIndexArgsCallbackId`] can be passed to [`Self::remove_on_test_btree_index_args`] - /// to cancel the callback. - fn on_test_btree_index_args(&self, callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static) -> TestBtreeIndexArgsCallbackId; - /// Cancel a callback previously registered by [`Self::on_test_btree_index_args`], - /// causing it not to run in the future. - fn remove_on_test_btree_index_args(&self, callback: TestBtreeIndexArgsCallbackId); -} - -impl test_btree_index_args for super::RemoteReducers { + /// and this method provides no way to listen for its completion status. + /// /// Use [`test_btree_index_args:test_btree_index_args_then`] to run a callback after the reducer completes. fn test_btree_index_args(&self, ) -> __sdk::Result<()> { - self.imp.call_reducer("test_btree_index_args", TestBtreeIndexArgsArgs { }) + self.test_btree_index_args_then( |_, _| {}) } - fn on_test_btree_index_args( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, ) + Send + 'static, - ) -> TestBtreeIndexArgsCallbackId { - TestBtreeIndexArgsCallbackId(self.imp.on_reducer( - "test_btree_index_args", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::TestBtreeIndexArgs { - - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, ) - }), - )) - } - fn remove_on_test_btree_index_args(&self, callback: TestBtreeIndexArgsCallbackId) { - self.imp.remove_on_reducer("test_btree_index_args", callback.0) - } -} -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `test_btree_index_args`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_test_btree_index_args { - /// Set the call-reducer flags for the reducer `test_btree_index_args` to `flags`. + /// Request that the remote module invoke the reducer `test_btree_index_args` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// This type is currently unstable and may be removed without a major version bump. - fn test_btree_index_args(&self, flags: __ws::CallReducerFlags); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn test_btree_index_args_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } -impl set_flags_for_test_btree_index_args for super::SetReducerFlags { - fn test_btree_index_args(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("test_btree_index_args", flags); +impl test_btree_index_args for super::RemoteReducers { + fn test_btree_index_args_then( + &self, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(TestBtreeIndexArgsArgs { }, callback) } } @@ -3886,6 +3502,8 @@ impl TestDTableAccess for super::RemoteTables { } pub struct TestDInsertCallbackId(__sdk::CallbackId); + + pub struct TestDDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for TestDTableHandle<'ctx> { @@ -3930,7 +3548,7 @@ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache, + raw_updates: __ws::v2::TableUpdate, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -4015,6 +3633,8 @@ impl __sdk::__query_builder::HasIxCols for TestD { } } +impl __sdk::__query_builder::CanBeLookupTable for TestD {} + ''' "test_e_type.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -4080,6 +3700,8 @@ impl __sdk::__query_builder::HasIxCols for TestE { } } +impl __sdk::__query_builder::CanBeLookupTable for TestE {} + ''' "test_f_table.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -4128,6 +3750,8 @@ impl TestFTableAccess for super::RemoteTables { } pub struct TestFInsertCallbackId(__sdk::CallbackId); + + pub struct TestFDeleteCallbackId(__sdk::CallbackId); impl<'ctx> __sdk::Table for TestFTableHandle<'ctx> { @@ -4172,7 +3796,7 @@ pub(super) fn register_table(client_cache: &mut __sdk::ClientCache, + raw_updates: __ws::v2::TableUpdate, ) -> __sdk::Result<__sdk::TableUpdate> { __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { __sdk::InternalError::failed_parse( @@ -4257,6 +3881,8 @@ impl __sdk::__query_builder::HasIxCols for TestFoobar { } } +impl __sdk::__query_builder::CanBeLookupTable for TestFoobar {} + ''' "test_reducer.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -4299,8 +3925,6 @@ impl __sdk::InModule for TestArgs { type Module = super::RemoteModule; } -pub struct TestCallbackId(__sdk::CallbackId); - #[allow(non_camel_case_types)] /// Extension trait for access to the reducer `test`. /// @@ -4310,76 +3934,48 @@ pub trait test { /// /// This method returns immediately, and errors only if we are unable to send the request. /// The reducer will run asynchronously in the future, - /// and its status can be observed by listening for [`Self::on_test`] callbacks. + /// and this method provides no way to listen for its completion status. + /// /// Use [`test:test_then`] to run a callback after the reducer completes. fn test(&self, arg: TestA, arg_2: TestB, arg_3: NamespaceTestC, arg_4: NamespaceTestF, -) -> __sdk::Result<()>; - /// Register a callback to run whenever we are notified of an invocation of the reducer `test`. - /// - /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] - /// to determine the reducer's status. +) -> __sdk::Result<()> { + self.test_then(arg, arg_2, arg_3, arg_4, |_, _| {}) + } + + /// Request that the remote module invoke the reducer `test` to run as soon as possible, + /// registering `callback` to run when we are notified that the reducer completed. /// - /// The returned [`TestCallbackId`] can be passed to [`Self::remove_on_test`] - /// to cancel the callback. - fn on_test(&self, callback: impl FnMut(&super::ReducerEventContext, &TestA, &TestB, &NamespaceTestC, &NamespaceTestF, ) + Send + 'static) -> TestCallbackId; - /// Cancel a callback previously registered by [`Self::on_test`], - /// causing it not to run in the future. - fn remove_on_test(&self, callback: TestCallbackId); + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed with the `callback`. + fn test_then( + &self, + arg: TestA, +arg_2: TestB, +arg_3: NamespaceTestC, +arg_4: NamespaceTestF, + + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()>; } impl test for super::RemoteReducers { - fn test(&self, arg: TestA, + fn test_then( + &self, + arg: TestA, arg_2: TestB, arg_3: NamespaceTestC, arg_4: NamespaceTestF, -) -> __sdk::Result<()> { - self.imp.call_reducer("test", TestArgs { arg, arg_2, arg_3, arg_4, }) - } - fn on_test( - &self, - mut callback: impl FnMut(&super::ReducerEventContext, &TestA, &TestB, &NamespaceTestC, &NamespaceTestF, ) + Send + 'static, - ) -> TestCallbackId { - TestCallbackId(self.imp.on_reducer( - "test", - Box::new(move |ctx: &super::ReducerEventContext| { - #[allow(irrefutable_let_patterns)] - let super::ReducerEventContext { - event: __sdk::ReducerEvent { - reducer: super::Reducer::Test { - arg, arg_2, arg_3, arg_4, - }, - .. - }, - .. - } = ctx else { unreachable!() }; - callback(ctx, arg, arg_2, arg_3, arg_4, ) - }), - )) - } - fn remove_on_test(&self, callback: TestCallbackId) { - self.imp.remove_on_reducer("test", callback.0) - } -} - -#[allow(non_camel_case_types)] -#[doc(hidden)] -/// Extension trait for setting the call-flags for the reducer `test`. -/// -/// Implemented for [`super::SetReducerFlags`]. -/// -/// This type is currently unstable and may be removed without a major version bump. -pub trait set_flags_for_test { - /// Set the call-reducer flags for the reducer `test` to `flags`. - /// - /// This type is currently unstable and may be removed without a major version bump. - fn test(&self, flags: __ws::CallReducerFlags); -} -impl set_flags_for_test for super::SetReducerFlags { - fn test(&self, flags: __ws::CallReducerFlags) { - self.imp.set_call_reducer_flags("test", flags); + callback: impl FnOnce(&super::ReducerEventContext, Result, __sdk::InternalError>) + + Send + + 'static, + ) -> __sdk::Result<()> { + self.imp.invoke_reducer_with_callback(TestArgs { arg, arg_2, arg_3, arg_4, }, callback) } } diff --git a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap index 593bb8809d5..9b4d56d000d 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -272,7 +272,7 @@ const tablesSchema = __schema({ { name: 'logged_out_player_player_id_key', constraint: 'unique', columns: ['playerId'] }, ], }, LoggedOutPlayerRow), - __table({ + person: __table({ name: 'person', indexes: [ { name: 'age', algorithm: 'btree', columns: [ diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs b/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs index 7916467866b..9ea61d79d89 100644 --- a/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.12.0 (commit c63169838eff3d5c486ea11a3eb1610f80518e0e). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; @@ -96,7 +96,7 @@ impl __sdk::DbUpdate for DbUpdate { fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache) -> AppliedDiff<'_> { let mut diff = AppliedDiff::default(); - diff.test_event = self.test_event.into_event_diff(); + diff.test_event = cache.apply_diff_to_table::("test_event", &self.test_event); diff } @@ -791,6 +791,5 @@ impl __sdk::SpacetimeModule for RemoteModule { fn register_tables(client_cache: &mut __sdk::ClientCache) { test_event_table::register_table(client_cache); } - const ALL_TABLE_NAMES: &'static [&'static str] = &["test_event"]; } diff --git a/sdks/rust/tests/view-client/src/module_bindings/player_level_type.rs b/sdks/rust/tests/view-client/src/module_bindings/player_level_type.rs index 98b25e6a137..8a5ba6071e4 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/player_level_type.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/player_level_type.rs @@ -50,3 +50,5 @@ impl __sdk::__query_builder::HasIxCols for PlayerLevel { } } } + +impl __sdk::__query_builder::CanBeLookupTable for PlayerLevel {} diff --git a/sdks/rust/tests/view-client/src/module_bindings/player_location_type.rs b/sdks/rust/tests/view-client/src/module_bindings/player_location_type.rs index a2d29d18188..51e3d45390f 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/player_location_type.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/player_location_type.rs @@ -56,3 +56,5 @@ impl __sdk::__query_builder::HasIxCols for PlayerLocation { } } } + +impl __sdk::__query_builder::CanBeLookupTable for PlayerLocation {} diff --git a/sdks/rust/tests/view-client/src/module_bindings/player_type.rs b/sdks/rust/tests/view-client/src/module_bindings/player_type.rs index b99ac46acf3..57b07de9a0b 100644 --- a/sdks/rust/tests/view-client/src/module_bindings/player_type.rs +++ b/sdks/rust/tests/view-client/src/module_bindings/player_type.rs @@ -50,3 +50,5 @@ impl __sdk::__query_builder::HasIxCols for Player { } } } + +impl __sdk::__query_builder::CanBeLookupTable for Player {} From 1279749caa27bff66617fb7bdfc8b3a136cedc22 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 16:32:54 -0500 Subject: [PATCH 14/24] Fix table_cache.ts: use sourceName instead of name for event table callbacks --- crates/bindings-typescript/src/sdk/table_cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bindings-typescript/src/sdk/table_cache.ts b/crates/bindings-typescript/src/sdk/table_cache.ts index 0b14e801d75..9f04ccad0c2 100644 --- a/crates/bindings-typescript/src/sdk/table_cache.ts +++ b/crates/bindings-typescript/src/sdk/table_cache.ts @@ -268,7 +268,7 @@ export class TableCacheImpl< if (op.type === 'insert') { pendingCallbacks.push({ type: 'insert', - table: this.tableDef.name, + table: this.tableDef.sourceName, cb: () => { this.emitter.emit('insert', ctx, op.row); }, From 6d5b64d15640b789d1f26cf9e38dcf018bc8ddf7 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 16:56:20 -0500 Subject: [PATCH 15/24] Add event tables documentation page --- .../00300-tables/00550-event-tables.md | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md diff --git a/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md b/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md new file mode 100644 index 00000000000..cfa324638fd --- /dev/null +++ b/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md @@ -0,0 +1,215 @@ +--- +title: Event Tables +slug: /tables/event-tables +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +In many applications, particularly games and real-time systems, modules need to notify clients about things that happened without storing that information permanently. A combat system might need to tell clients "entity X took 50 damage" so they can display a floating damage number, but there is no reason to keep that record in the database after the moment has passed. + +Event tables provide exactly this capability. An event table is a table whose rows are ephemeral: they exist only for the duration of the transaction that created them. When the transaction commits, the rows are broadcast to subscribed clients and then discarded. Between transactions, the table is always empty. + +From the module's perspective, event tables behave like regular tables during a reducer's execution. You insert rows, query them, and apply constraints just as you would with any other table. The difference is purely in what happens after the transaction completes: rather than merging the rows into the committed database state, SpacetimeDB publishes them to subscribers and throws them away. + +## Defining an Event Table + +To declare a table as an event table, add the `event` attribute to the table definition. Event tables support all the same column types, constraints, indexes, and auto-increment fields as regular tables. + + + + +```typescript +const damageEvent = table({ + public: true, + event: true, +}, { + entity_id: t.identity(), + damage: t.u32(), + source: t.string(), +}); +``` + + + + +```csharp +[SpacetimeDB.Table(Public = true, Event = true)] +public partial struct DamageEvent +{ + public Identity EntityId; + public uint Damage; + public string Source; +} +``` + + + + +```rust +#[spacetimedb::table(name = damage_event, public, event)] +pub struct DamageEvent { + pub entity_id: Identity, + pub damage: u32, + pub source: String, +} +``` + + + + +:::note Changing the event flag +Once a table has been published as an event table (or a regular table), the `event` flag cannot be changed in a subsequent module update. Attempting to convert a regular table to an event table or vice versa will produce a migration error. This restriction exists because the two table types have fundamentally different storage semantics. +::: + +## Publishing Events + +To publish an event, simply insert a row into the event table from within a reducer. The insertion works exactly like inserting into a regular table. The row is visible within the current transaction and can be queried or used in constraints. When the transaction commits successfully, the row is broadcast to all subscribed clients. If the reducer panics or the transaction is rolled back, no events are sent. + + + + +```typescript +export const attack = spacetimedb.reducer( + { target_id: t.identity(), damage: t.u32() }, + (ctx, { target_id, damage }) => { + // Game logic... + + // Publish the event + ctx.db.damageEvent.insert({ + entity_id: target_id, + damage, + source: "melee_attack", + }); + } +); +``` + + + + +```csharp +[SpacetimeDB.Reducer] +public static void Attack(ReducerContext ctx, Identity targetId, uint damage) +{ + // Game logic... + + // Publish the event + ctx.Db.DamageEvent.Insert(new DamageEvent + { + EntityId = targetId, + Damage = damage, + Source = "melee_attack" + }); +} +``` + + + + +```rust +#[spacetimedb::reducer] +fn attack(ctx: &ReducerContext, target_id: Identity, damage: u32) { + // Game logic... + + // Publish the event + ctx.db.damage_event().insert(DamageEvent { + entity_id: target_id, + damage, + source: "melee_attack".to_string(), + }); +} +``` + + + + +Because events are just table inserts, you can publish the same event type from any number of reducers. A `DamageEvent` might be inserted by a melee attack reducer, a spell reducer, and an environmental hazard reducer and clients receive the same event regardless of what triggered it. + +## Constraints and Indexes + +Primary keys, unique constraints, indexes, sequences, and auto-increment columns all work on event tables. The key difference is that these constraints are enforced only within a single transaction and reset between transactions. + +For example, if an event table has a primary key column, inserting two rows with the same primary key within the same transaction will produce an error, just as it would for a regular table. However, inserting a row with primary key `1` in one transaction and another row with primary key `1` in a later transaction will both succeed, because the table is empty at the start of each transaction. + +This behavior follows naturally from the fact that event table rows are never merged into the committed state. Each transaction begins with an empty table. + +## Subscribing to Events + +On the client side, event tables are subscribed to in the same way as regular tables. The important difference is that event table rows are never stored in the client cache. Calling `count()` on an event table always returns 0, and `iter()` always yields no rows. Instead, you observe events through `on_insert` callbacks, which fire for each row that was inserted during the transaction. + +Because event table rows are ephemeral, only `on_insert` callbacks are available. There are no `on_delete`, `on_update`, or `on_before_delete` callbacks, since rows are never present in the client state to be deleted or updated. + + + + +```typescript +conn.db.damageEvent.onInsert((ctx, event) => { + console.log(`Entity ${event.entityId} took ${event.damage} damage from ${event.source}`); +}); +``` + + + + +```csharp +conn.Db.DamageEvent.OnInsert += (ctx, damageEvent) => +{ + Debug.Log($"Entity {damageEvent.EntityId} took {damageEvent.Damage} damage from {damageEvent.Source}"); +}; +``` + + + + +```rust +conn.db.damage_event().on_insert(|ctx, event| { + println!("Entity {} took {} damage from {}", event.entity_id, event.damage, event.source); +}); +``` + + + + +## How It Works + +Conceptually, every insert into an event table is a **noop**: an insert paired with an automatic delete. The result is that the table state never changes; it is always the empty set. This model has several consequences for how SpacetimeDB handles event tables internally. + +**Committed state.** Event table rows are never merged into the committed database state. When a transaction commits, SpacetimeDB records the inserts in the transaction's data but skips the step that would normally add them to the table's persistent storage. + +**Commitlog.** Event table inserts are still written to the commitlog. This ensures that the full history of events is preserved for future replay features. When replaying a commitlog on startup, inserts into event tables are treated as noops; they are acknowledged but do not produce any committed state. + +**Subscriptions.** Event tables are excluded from `SELECT * FROM *` wildcard subscriptions. To receive events from an event table, clients must explicitly subscribe to it by name. This prevents clients from being flooded with events they did not ask for. + +**Wire format.** Event tables require the v2 WebSocket protocol. Clients connected via the v1 protocol that attempt to subscribe to an event table will receive an error message directing them to upgrade. + +:::tip Migrating from reducer callbacks +If you previously used `ctx.reducers.on_()` callbacks to receive transient data, event tables are the recommended replacement. Define an event table with the fields you want to publish, insert a row in your reducer, and register an `on_insert` callback on the client via `ctx.db.().on_insert(...)`. +::: + +## Row-Level Security + +Row-level security applies to event tables with the same semantics as regular tables. This means you can use RLS rules to control which clients receive which events based on their identity. For example, you could restrict a `DamageEvent` so that only clients whose identity matches the `entity_id` field receive the event, preventing players from seeing damage dealt to other players. + +## Current Limitations + +Event tables are fully functional for the use cases described above, but a few capabilities are intentionally restricted for the initial release: + +- **Subscription joins.** Event tables cannot currently be used as the lookup (right/inner) table in a subscription join. While this is well-defined (the noop semantics make joined results behave as event tables too), it is restricted for ease of implementation and will be relaxed in a future release. +- **Views.** Event tables cannot currently be accessed within view functions. Although the proposal defines clear semantics for this (event-table-ness is "infectious," meaning a view that joins on an event table itself becomes an event table), this is deferred to a future release. + +## Use Cases + +Event tables are well-suited to any situation where the module needs to notify clients about something that happened without storing a permanent record: + +- **Combat and damage events.** Floating damage numbers, hit indicators, and kill notifications. +- **Chat messages.** Real-time chat where messages are displayed on arrival but don't need server-side persistence. +- **Notifications.** Transient UI messages like "Player joined", "Achievement unlocked", or "Trade completed". +- **Sound and visual effects.** Triggering client-side effects such as explosions, particles, or audio cues at the right moment. +- **Telemetry and debugging.** Streaming diagnostic data to a connected developer client without accumulating it in the database. + +## Next Steps + +- Learn about [Tables](/tables) for persistent data storage +- Explore [Schedule Tables](/tables/schedule-tables) for time-triggered actions +- See [Row-Level Security](/tables/access-permissions) for controlling data visibility From 8c8beb3455f171799eaa593d3bdfbc76f00d4df2 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 17:06:29 -0500 Subject: [PATCH 16/24] Revise event tables documentation based on review feedback --- .../00300-tables/00550-event-tables.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md b/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md index cfa324638fd..89531b812c1 100644 --- a/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md +++ b/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md @@ -8,9 +8,9 @@ import TabItem from '@theme/TabItem'; In many applications, particularly games and real-time systems, modules need to notify clients about things that happened without storing that information permanently. A combat system might need to tell clients "entity X took 50 damage" so they can display a floating damage number, but there is no reason to keep that record in the database after the moment has passed. -Event tables provide exactly this capability. An event table is a table whose rows are ephemeral: they exist only for the duration of the transaction that created them. When the transaction commits, the rows are broadcast to subscribed clients and then discarded. Between transactions, the table is always empty. +Event tables provide exactly this capability. An event table is a table whose rows are inserted and then immediately deleted by the database: they exist only for the duration of the transaction that created them. When the transaction commits, the rows are broadcast to subscribed clients and then deleted from the table. Between transactions, the table is always empty. -From the module's perspective, event tables behave like regular tables during a reducer's execution. You insert rows, query them, and apply constraints just as you would with any other table. The difference is purely in what happens after the transaction completes: rather than merging the rows into the committed database state, SpacetimeDB publishes them to subscribers and throws them away. +From the module's perspective, event tables behave like regular tables during a reducer's execution. You insert rows, query them, and apply constraints just as you would with any other table. The difference is purely in what happens after the transaction completes: rather than merging the rows into the committed database state, SpacetimeDB publishes them to subscribers and deletes them from the table. The inserts are still recorded in the commitlog, so a full history of events is preserved. ## Defining an Event Table @@ -59,7 +59,7 @@ pub struct DamageEvent { :::note Changing the event flag -Once a table has been published as an event table (or a regular table), the `event` flag cannot be changed in a subsequent module update. Attempting to convert a regular table to an event table or vice versa will produce a migration error. This restriction exists because the two table types have fundamentally different storage semantics. +Once a table has been published as an event table (or a regular table), the `event` flag cannot be changed in a subsequent module update. Attempting to convert a regular table to an event table or vice versa will produce a migration error. ::: ## Publishing Events @@ -175,12 +175,6 @@ conn.db.damage_event().on_insert(|ctx, event| { Conceptually, every insert into an event table is a **noop**: an insert paired with an automatic delete. The result is that the table state never changes; it is always the empty set. This model has several consequences for how SpacetimeDB handles event tables internally. -**Committed state.** Event table rows are never merged into the committed database state. When a transaction commits, SpacetimeDB records the inserts in the transaction's data but skips the step that would normally add them to the table's persistent storage. - -**Commitlog.** Event table inserts are still written to the commitlog. This ensures that the full history of events is preserved for future replay features. When replaying a commitlog on startup, inserts into event tables are treated as noops; they are acknowledged but do not produce any committed state. - -**Subscriptions.** Event tables are excluded from `SELECT * FROM *` wildcard subscriptions. To receive events from an event table, clients must explicitly subscribe to it by name. This prevents clients from being flooded with events they did not ask for. - **Wire format.** Event tables require the v2 WebSocket protocol. Clients connected via the v1 protocol that attempt to subscribe to an event table will receive an error message directing them to upgrade. :::tip Migrating from reducer callbacks From 899f2e0a6144944d54ba6bbc7d19f17c84828c2c Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 17:08:07 -0500 Subject: [PATCH 17/24] Link to migration guide from event tables doc --- .../docs/00200-core-concepts/00300-tables/00550-event-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md b/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md index 89531b812c1..a0d381d16aa 100644 --- a/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md +++ b/docs/docs/00200-core-concepts/00300-tables/00550-event-tables.md @@ -178,7 +178,7 @@ Conceptually, every insert into an event table is a **noop**: an insert paired w **Wire format.** Event tables require the v2 WebSocket protocol. Clients connected via the v1 protocol that attempt to subscribe to an event table will receive an error message directing them to upgrade. :::tip Migrating from reducer callbacks -If you previously used `ctx.reducers.on_()` callbacks to receive transient data, event tables are the recommended replacement. Define an event table with the fields you want to publish, insert a row in your reducer, and register an `on_insert` callback on the client via `ctx.db.().on_insert(...)`. +If you previously used `ctx.reducers.on_()` callbacks to receive transient data, event tables are the recommended replacement. Define an event table with the fields you want to publish, insert a row in your reducer, and register an `on_insert` callback on the client via `ctx.db.().on_insert(...)`. See the [migration guide](/how-to/migrating-to-2-0) for details. ::: ## Row-Level Security From 23d842fb67d039dc84b7958850c67079a2396dd3 Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Fri, 13 Feb 2026 15:28:10 -0800 Subject: [PATCH 18/24] Fix extract-schema V10 output and make C# table rowset parsing tolerant --- .../src/subcommands/extract_schema.rs | 2 +- sdks/csharp/src/Table.cs | 60 ++++++++++++++----- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/crates/standalone/src/subcommands/extract_schema.rs b/crates/standalone/src/subcommands/extract_schema.rs index 9b85e57059a..c9b35369957 100644 --- a/crates/standalone/src/subcommands/extract_schema.rs +++ b/crates/standalone/src/subcommands/extract_schema.rs @@ -67,7 +67,7 @@ pub async fn exec(args: &ArgMatches) -> anyhow::Result<()> { let module_def = extract_schema(program_bytes.into(), host_type.into()).await?; - let raw_def = RawModuleDef::V9(module_def.into()); + let raw_def = RawModuleDef::V10(module_def.into()); serde_json::to_writer(std::io::stdout().lock(), &sats::serde::SerdeWrapper(raw_def))?; diff --git a/sdks/csharp/src/Table.cs b/sdks/csharp/src/Table.cs index 5a8cac6da64..097e0f67dff 100644 --- a/sdks/csharp/src/Table.cs +++ b/sdks/csharp/src/Table.cs @@ -355,30 +355,58 @@ void IRemoteTableHandle.Parse(TableUpdate update, ParsedDatabaseUpdate dbOps) { if (rowSet is TableUpdateRows.PersistentTable(var persistent)) { - // Because we are accumulating into a MultiDictionaryDelta that will be applied all-at-once - // to the table, it doesn't matter that we call Add before Remove here. - var (insertReader, insertRowCount) = CompressionHelpers.ParseRowList(persistent.Inserts); - for (var i = 0; i < insertRowCount; i++) + if (IsEventTable) { - var obj = Decode(insertReader, out var pk); - delta.Delta.Add(pk, obj); + // Be tolerant of persistent rowsets for event tables. + // Treat inserts as event rows, matching Rust SDK behavior. + var (insertReader, insertRowCount) = CompressionHelpers.ParseRowList(persistent.Inserts); + delta.EventRows ??= new(); + for (var i = 0; i < insertRowCount; i++) + { + var obj = DecodeValue(insertReader); + delta.EventRows.Add(obj); + } } - - var (deleteReader, deleteRowCount) = CompressionHelpers.ParseRowList(persistent.Deletes); - for (var i = 0; i < deleteRowCount; i++) + else { - var obj = Decode(deleteReader, out var pk); - delta.Delta.Remove(pk, obj); + // Because we are accumulating into a MultiDictionaryDelta that will be applied all-at-once + // to the table, it doesn't matter that we call Add before Remove here. + var (insertReader, insertRowCount) = CompressionHelpers.ParseRowList(persistent.Inserts); + for (var i = 0; i < insertRowCount; i++) + { + var obj = Decode(insertReader, out var pk); + delta.Delta.Add(pk, obj); + } + + var (deleteReader, deleteRowCount) = CompressionHelpers.ParseRowList(persistent.Deletes); + for (var i = 0; i < deleteRowCount; i++) + { + var obj = Decode(deleteReader, out var pk); + delta.Delta.Remove(pk, obj); + } } } else if (rowSet is TableUpdateRows.EventTable(var events)) { - var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); - delta.EventRows ??= new(); - for (var i = 0; i < eventRowCount; i++) + if (IsEventTable) { - var obj = DecodeValue(eventReader); - delta.EventRows.Add(obj); + var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); + delta.EventRows ??= new(); + for (var i = 0; i < eventRowCount; i++) + { + var obj = DecodeValue(eventReader); + delta.EventRows.Add(obj); + } + } + else + { + // Be tolerant of event rowsets for non-event tables by treating them as inserts. + var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); + for (var i = 0; i < eventRowCount; i++) + { + var obj = Decode(eventReader, out var pk); + delta.Delta.Add(pk, obj); + } } } } From 3cdbede5a5ef7c5c9c44982b06c7da17af4619b9 Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Fri, 13 Feb 2026 15:42:51 -0800 Subject: [PATCH 19/24] Updated RemoteTableHandle to split up in order to remove OnDelete/OnBeforeDelete/OnUpdate from RemoteEventTableHandle --- sdks/csharp/src/Table.cs | 143 +++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 67 deletions(-) diff --git a/sdks/csharp/src/Table.cs b/sdks/csharp/src/Table.cs index 097e0f67dff..bbaac668c02 100644 --- a/sdks/csharp/src/Table.cs +++ b/sdks/csharp/src/Table.cs @@ -90,7 +90,7 @@ interface IParsedTableUpdate /// /// /// - public abstract class RemoteTableHandle : RemoteBase, IRemoteTableHandle + public abstract class RemoteTableHandleBase : RemoteBase, IRemoteTableHandle where EventContext : class, IEventContext where Row : class, IStructuralReadWrite, new() { @@ -110,7 +110,7 @@ public abstract class UniqueIndexBase : IndexBase { private readonly Dictionary cache = new(); - public UniqueIndexBase(RemoteTableHandle table) + public UniqueIndexBase(RemoteTableHandleBase table) { table.OnInternalInsert += row => cache.Add(GetKey(row), row); table.OnInternalDelete += row => cache.Remove(GetKey(row)); @@ -125,7 +125,7 @@ public abstract class BTreeIndexBase : IndexBase // TODO: change to SortedDictionary when adding support for range queries. private readonly Dictionary> cache = new(); - public BTreeIndexBase(RemoteTableHandle table) + public BTreeIndexBase(RemoteTableHandleBase table) { table.OnInternalInsert += row => { @@ -180,7 +180,7 @@ internal class ParsedTableUpdate : IParsedTableUpdate /// internal bool IsEventTable { get; } - public RemoteTableHandle(IDbConnection conn, bool isEventTable = false) : base(conn) + public RemoteTableHandleBase(IDbConnection conn, bool isEventTable = false) : base(conn) { IsEventTable = isEventTable; } @@ -209,7 +209,7 @@ private event Action OnInternalDelete // These are implementations of the type-erased interface. object? IRemoteTableHandle.GetPrimaryKey(IStructuralReadWrite row) => GetPrimaryKey((Row)row); - // These are provided by RemoteTableHandle. + // These are provided by RemoteTableHandleBase. Type IRemoteTableHandle.ClientTableType => typeof(Row); // THE DATA IN THE TABLE. @@ -420,26 +420,7 @@ public event RowEventHandler OnInsert add => OnInsertHandler.AddListener(value); remove => OnInsertHandler.RemoveListener(value); } - private CustomRowEventHandler OnDeleteHandler { get; } = new(); - public event RowEventHandler OnDelete - { - add => OnDeleteHandler.AddListener(value); - remove => OnDeleteHandler.RemoveListener(value); - } - private CustomRowEventHandler OnBeforeDeleteHandler { get; } = new(); - public event RowEventHandler OnBeforeDelete - { - add => OnBeforeDeleteHandler.AddListener(value); - remove => OnBeforeDeleteHandler.RemoveListener(value); - } - public delegate void UpdateEventHandler(EventContext context, Row oldRow, Row newRow); - private CustomUpdateEventHandler OnUpdateHandler { get; } = new(); - public event UpdateEventHandler OnUpdate - { - add => OnUpdateHandler.AddListener(value); - remove => OnUpdateHandler.RemoveListener(value); - } public int Count => (int)Entries.CountDistinct; @@ -460,41 +441,11 @@ void InvokeInsert(IEventContext context, IStructuralReadWrite row) } } - void InvokeDelete(IEventContext context, IStructuralReadWrite row) - { - try - { - OnDeleteHandler.Invoke((EventContext)context, (Row)row); - } - catch (Exception e) - { - Log.Exception(e); - } - } + protected virtual void InvokeDelete(IEventContext context, IStructuralReadWrite row) { } - void InvokeBeforeDelete(IEventContext context, IStructuralReadWrite row) - { - try - { - OnBeforeDeleteHandler.Invoke((EventContext)context, (Row)row); - } - catch (Exception e) - { - Log.Exception(e); - } - } + protected virtual void InvokeBeforeDelete(IEventContext context, IStructuralReadWrite row) { } - void InvokeUpdate(IEventContext context, IStructuralReadWrite oldRow, IStructuralReadWrite newRow) - { - try - { - OnUpdateHandler.Invoke((EventContext)context, (Row)oldRow, (Row)newRow); - } - catch (Exception e) - { - Log.Exception(e); - } - } + protected virtual void InvokeUpdate(IEventContext context, IStructuralReadWrite oldRow, IStructuralReadWrite newRow) { } List> wasInserted = new(); List<(object key, Row oldValue, Row newValue)> wasUpdated = new(); @@ -630,7 +581,7 @@ void IRemoteTableHandle.PostApply(IEventContext context) } - private class CustomRowEventHandler + protected class CustomRowEventHandler { private EventListeners Listeners { get; } = new(); @@ -645,7 +596,7 @@ public void Invoke(EventContext ctx, Row row) public void AddListener(RowEventHandler listener) => Listeners.Add(listener); public void RemoveListener(RowEventHandler listener) => Listeners.Remove(listener); } - private class CustomUpdateEventHandler + protected class CustomUpdateEventHandler { private EventListeners Listeners { get; } = new(); @@ -662,23 +613,81 @@ public void Invoke(EventContext ctx, Row oldRow, Row newRow) } } + /// + /// A table handle for persistent tables, exposing insert/delete/update callbacks. + /// + public abstract class RemoteTableHandle : RemoteTableHandleBase + where EventContext : class, IEventContext + where Row : class, IStructuralReadWrite, new() + { + protected RemoteTableHandle(IDbConnection conn) : base(conn) { } + + private CustomRowEventHandler OnDeleteHandler { get; } = new(); + public event RowEventHandler OnDelete + { + add => OnDeleteHandler.AddListener(value); + remove => OnDeleteHandler.RemoveListener(value); + } + private CustomRowEventHandler OnBeforeDeleteHandler { get; } = new(); + public event RowEventHandler OnBeforeDelete + { + add => OnBeforeDeleteHandler.AddListener(value); + remove => OnBeforeDeleteHandler.RemoveListener(value); + } + private CustomUpdateEventHandler OnUpdateHandler { get; } = new(); + public event UpdateEventHandler OnUpdate + { + add => OnUpdateHandler.AddListener(value); + remove => OnUpdateHandler.RemoveListener(value); + } + + protected override void InvokeDelete(IEventContext context, IStructuralReadWrite row) + { + try + { + OnDeleteHandler.Invoke((EventContext)context, (Row)row); + } + catch (Exception e) + { + Log.Exception(e); + } + } + + protected override void InvokeBeforeDelete(IEventContext context, IStructuralReadWrite row) + { + try + { + OnBeforeDeleteHandler.Invoke((EventContext)context, (Row)row); + } + catch (Exception e) + { + Log.Exception(e); + } + } + + protected override void InvokeUpdate(IEventContext context, IStructuralReadWrite oldRow, IStructuralReadWrite newRow) + { + try + { + OnUpdateHandler.Invoke((EventContext)context, (Row)oldRow, (Row)newRow); + } + catch (Exception e) + { + Log.Exception(e); + } + } + } + /// /// A table handle for event tables, which only expose OnInsert callbacks. /// Event tables do not persist rows in the client cache and do not support /// OnDelete, OnBeforeDelete, or OnUpdate callbacks. /// - public abstract class RemoteEventTableHandle : RemoteTableHandle + public abstract class RemoteEventTableHandle : RemoteTableHandleBase where EventContext : class, IEventContext where Row : class, IStructuralReadWrite, new() { protected RemoteEventTableHandle(IDbConnection conn) : base(conn, isEventTable: true) { } - - // Hide delete/update events from the public API. - // The base class handlers still exist but will never have listeners registered, - // so invoking them in PostApply is a harmless no-op. - private new event RowEventHandler OnDelete { add { } remove { } } - private new event RowEventHandler OnBeforeDelete { add { } remove { } } - private new event UpdateEventHandler OnUpdate { add { } remove { } } } } #nullable disable From 281264a73f3e1ce755820226bf22b0830e256537 Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Fri, 13 Feb 2026 16:19:05 -0800 Subject: [PATCH 20/24] Updated regression tests to include new event tests + regenerated Blackholio --- .../ModuleBindings/SpacetimeDBClient.g.cpp | 420 ------------------ .../ModuleBindings/Reducers/CircleDecay.g.h | 54 --- .../Reducers/CircleRecombine.g.h | 54 --- .../ModuleBindings/Reducers/Connect.g.h | 43 -- .../ModuleBindings/Reducers/ConsumeEntity.g.h | 54 --- .../ModuleBindings/Reducers/Disconnect.g.h | 43 -- .../Reducers/MoveAllPlayers.g.h | 54 --- .../ModuleBindings/Reducers/SpawnFood.g.h | 54 --- .../ModuleBindings/SpacetimeDBClient.g.h | 350 +-------------- .../regression-tests/client/Program.cs | 64 +++ .../Reducers/EmitTestEvent.g.cs | 74 +++ .../{ClientConnected.g.cs => Noop.g.cs} | 19 +- .../module_bindings/SpacetimeDBClient.g.cs | 8 +- .../module_bindings/Tables/TestEvent.g.cs | 47 ++ .../module_bindings/Types/TestEvent.g.cs | 35 ++ .../Procedures/ScheduledProc.g.cs | 77 ---- .../module_bindings/SpacetimeDBClient.g.cs | 2 +- .../module_bindings/SpacetimeDBClient.g.cs | 2 +- .../examples~/regression-tests/server/Lib.cs | 16 + 19 files changed, 258 insertions(+), 1212 deletions(-) delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h delete mode 100644 demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h create mode 100644 sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/EmitTestEvent.g.cs rename sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/{ClientConnected.g.cs => Noop.g.cs} (70%) create mode 100644 sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/TestEvent.g.cs create mode 100644 sdks/csharp/examples~/regression-tests/client/module_bindings/Types/TestEvent.g.cs delete mode 100644 sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Procedures/ScheduledProc.g.cs diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp index cb9ada83974..471fac9e789 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Private/ModuleBindings/SpacetimeDBClient.g.cpp @@ -14,48 +14,12 @@ static FReducer DecodeReducer(const FReducerEvent& Event) { const FString& ReducerName = Event.ReducerCall.ReducerName; - if (ReducerName == TEXT("circle_decay")) - { - FCircleDecayArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::CircleDecay(Args); - } - - if (ReducerName == TEXT("circle_recombine")) - { - FCircleRecombineArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::CircleRecombine(Args); - } - - if (ReducerName == TEXT("connect")) - { - FConnectArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::Connect(Args); - } - - if (ReducerName == TEXT("consume_entity")) - { - FConsumeEntityArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::ConsumeEntity(Args); - } - - if (ReducerName == TEXT("disconnect")) - { - FDisconnectArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::Disconnect(Args); - } - if (ReducerName == TEXT("enter_game")) { FEnterGameArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); return FReducer::EnterGame(Args); } - if (ReducerName == TEXT("move_all_players")) - { - FMoveAllPlayersArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::MoveAllPlayers(Args); - } - if (ReducerName == TEXT("player_split")) { FPlayerSplitArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); @@ -68,12 +32,6 @@ static FReducer DecodeReducer(const FReducerEvent& Event) return FReducer::Respawn(Args); } - if (ReducerName == TEXT("spawn_food")) - { - FSpawnFoodArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); - return FReducer::SpawnFood(Args); - } - if (ReducerName == TEXT("suicide")) { FSuicideArgs Args = UE::SpacetimeDB::Deserialize(Event.ReducerCall.Args); @@ -159,34 +117,10 @@ void URemoteTables::Initialize() /**/ } -void USetReducerFlags::CircleDecay(ECallReducerFlags Flag) -{ - FlagMap.Add("CircleDecay", Flag); -} -void USetReducerFlags::CircleRecombine(ECallReducerFlags Flag) -{ - FlagMap.Add("CircleRecombine", Flag); -} -void USetReducerFlags::Connect(ECallReducerFlags Flag) -{ - FlagMap.Add("Connect", Flag); -} -void USetReducerFlags::ConsumeEntity(ECallReducerFlags Flag) -{ - FlagMap.Add("ConsumeEntity", Flag); -} -void USetReducerFlags::Disconnect(ECallReducerFlags Flag) -{ - FlagMap.Add("Disconnect", Flag); -} void USetReducerFlags::EnterGame(ECallReducerFlags Flag) { FlagMap.Add("EnterGame", Flag); } -void USetReducerFlags::MoveAllPlayers(ECallReducerFlags Flag) -{ - FlagMap.Add("MoveAllPlayers", Flag); -} void USetReducerFlags::PlayerSplit(ECallReducerFlags Flag) { FlagMap.Add("PlayerSplit", Flag); @@ -195,10 +129,6 @@ void USetReducerFlags::Respawn(ECallReducerFlags Flag) { FlagMap.Add("Respawn", Flag); } -void USetReducerFlags::SpawnFood(ECallReducerFlags Flag) -{ - FlagMap.Add("SpawnFood", Flag); -} void USetReducerFlags::Suicide(ECallReducerFlags Flag) { FlagMap.Add("Suicide", Flag); @@ -208,226 +138,6 @@ void USetReducerFlags::UpdatePlayerInput(ECallReducerFlags Flag) FlagMap.Add("UpdatePlayerInput", Flag); } -void URemoteReducers::CircleDecay(const FCircleDecayTimerType& Timer) -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("circle_decay"), FCircleDecayArgs(Timer), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeCircleDecay(const FReducerEventContext& Context, const UCircleDecayReducer* Args) -{ - if (!OnCircleDecay.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for CircleDecay")); - } - return false; - } - - OnCircleDecay.Broadcast(Context, Args->Timer); - return true; -} - -bool URemoteReducers::InvokeCircleDecayWithArgs(const FReducerEventContext& Context, const FCircleDecayArgs& Args) -{ - if (!OnCircleDecay.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for CircleDecay")); - } - return false; - } - - OnCircleDecay.Broadcast(Context, Args.Timer); - return true; -} - -void URemoteReducers::CircleRecombine(const FCircleRecombineTimerType& Timer) -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("circle_recombine"), FCircleRecombineArgs(Timer), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeCircleRecombine(const FReducerEventContext& Context, const UCircleRecombineReducer* Args) -{ - if (!OnCircleRecombine.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for CircleRecombine")); - } - return false; - } - - OnCircleRecombine.Broadcast(Context, Args->Timer); - return true; -} - -bool URemoteReducers::InvokeCircleRecombineWithArgs(const FReducerEventContext& Context, const FCircleRecombineArgs& Args) -{ - if (!OnCircleRecombine.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for CircleRecombine")); - } - return false; - } - - OnCircleRecombine.Broadcast(Context, Args.Timer); - return true; -} - -void URemoteReducers::Connect() -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("connect"), FConnectArgs(), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeConnect(const FReducerEventContext& Context, const UConnectReducer* Args) -{ - if (!OnConnect.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Connect")); - } - return false; - } - - OnConnect.Broadcast(Context); - return true; -} - -bool URemoteReducers::InvokeConnectWithArgs(const FReducerEventContext& Context, const FConnectArgs& Args) -{ - if (!OnConnect.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Connect")); - } - return false; - } - - OnConnect.Broadcast(Context); - return true; -} - -void URemoteReducers::ConsumeEntity(const FConsumeEntityTimerType& Request) -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("consume_entity"), FConsumeEntityArgs(Request), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeConsumeEntity(const FReducerEventContext& Context, const UConsumeEntityReducer* Args) -{ - if (!OnConsumeEntity.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for ConsumeEntity")); - } - return false; - } - - OnConsumeEntity.Broadcast(Context, Args->Request); - return true; -} - -bool URemoteReducers::InvokeConsumeEntityWithArgs(const FReducerEventContext& Context, const FConsumeEntityArgs& Args) -{ - if (!OnConsumeEntity.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for ConsumeEntity")); - } - return false; - } - - OnConsumeEntity.Broadcast(Context, Args.Request); - return true; -} - -void URemoteReducers::Disconnect() -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("disconnect"), FDisconnectArgs(), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeDisconnect(const FReducerEventContext& Context, const UDisconnectReducer* Args) -{ - if (!OnDisconnect.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Disconnect")); - } - return false; - } - - OnDisconnect.Broadcast(Context); - return true; -} - -bool URemoteReducers::InvokeDisconnectWithArgs(const FReducerEventContext& Context, const FDisconnectArgs& Args) -{ - if (!OnDisconnect.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for Disconnect")); - } - return false; - } - - OnDisconnect.Broadcast(Context); - return true; -} - void URemoteReducers::EnterGame(const FString& Name) { if (!Conn) @@ -472,50 +182,6 @@ bool URemoteReducers::InvokeEnterGameWithArgs(const FReducerEventContext& Contex return true; } -void URemoteReducers::MoveAllPlayers(const FMoveAllPlayersTimerType& Timer) -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("move_all_players"), FMoveAllPlayersArgs(Timer), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeMoveAllPlayers(const FReducerEventContext& Context, const UMoveAllPlayersReducer* Args) -{ - if (!OnMoveAllPlayers.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for MoveAllPlayers")); - } - return false; - } - - OnMoveAllPlayers.Broadcast(Context, Args->Timer); - return true; -} - -bool URemoteReducers::InvokeMoveAllPlayersWithArgs(const FReducerEventContext& Context, const FMoveAllPlayersArgs& Args) -{ - if (!OnMoveAllPlayers.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for MoveAllPlayers")); - } - return false; - } - - OnMoveAllPlayers.Broadcast(Context, Args.Timer); - return true; -} - void URemoteReducers::PlayerSplit() { if (!Conn) @@ -604,50 +270,6 @@ bool URemoteReducers::InvokeRespawnWithArgs(const FReducerEventContext& Context, return true; } -void URemoteReducers::SpawnFood(const FSpawnFoodTimerType& Timer) -{ - if (!Conn) - { - UE_LOG(LogTemp, Error, TEXT("SpacetimeDB connection is null")); - return; - } - - Conn->CallReducerTyped(TEXT("spawn_food"), FSpawnFoodArgs(Timer), SetCallReducerFlags); -} - -bool URemoteReducers::InvokeSpawnFood(const FReducerEventContext& Context, const USpawnFoodReducer* Args) -{ - if (!OnSpawnFood.IsBound()) - { - // Handle unhandled reducer error - if (InternalOnUnhandledReducerError.IsBound()) - { - // TODO: Check Context.Event.Status for Failed/OutOfEnergy cases - // For now, just broadcast any error - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for SpawnFood")); - } - return false; - } - - OnSpawnFood.Broadcast(Context, Args->Timer); - return true; -} - -bool URemoteReducers::InvokeSpawnFoodWithArgs(const FReducerEventContext& Context, const FSpawnFoodArgs& Args) -{ - if (!OnSpawnFood.IsBound()) - { - if (InternalOnUnhandledReducerError.IsBound()) - { - InternalOnUnhandledReducerError.Broadcast(Context, TEXT("No handler registered for SpawnFood")); - } - return false; - } - - OnSpawnFood.Broadcast(Context, Args.Timer); - return true; -} - void URemoteReducers::Suicide() { if (!Conn) @@ -790,48 +412,12 @@ void UDbConnection::ReducerEvent(const FReducerEvent& Event) // Use hardcoded string matching for reducer dispatching const FString& ReducerName = Event.ReducerCall.ReducerName; - if (ReducerName == TEXT("circle_decay")) - { - FCircleDecayArgs Args = ReducerEvent.Reducer.GetAsCircleDecay(); - Reducers->InvokeCircleDecayWithArgs(Context, Args); - return; - } - if (ReducerName == TEXT("circle_recombine")) - { - FCircleRecombineArgs Args = ReducerEvent.Reducer.GetAsCircleRecombine(); - Reducers->InvokeCircleRecombineWithArgs(Context, Args); - return; - } - if (ReducerName == TEXT("connect")) - { - FConnectArgs Args = ReducerEvent.Reducer.GetAsConnect(); - Reducers->InvokeConnectWithArgs(Context, Args); - return; - } - if (ReducerName == TEXT("consume_entity")) - { - FConsumeEntityArgs Args = ReducerEvent.Reducer.GetAsConsumeEntity(); - Reducers->InvokeConsumeEntityWithArgs(Context, Args); - return; - } - if (ReducerName == TEXT("disconnect")) - { - FDisconnectArgs Args = ReducerEvent.Reducer.GetAsDisconnect(); - Reducers->InvokeDisconnectWithArgs(Context, Args); - return; - } if (ReducerName == TEXT("enter_game")) { FEnterGameArgs Args = ReducerEvent.Reducer.GetAsEnterGame(); Reducers->InvokeEnterGameWithArgs(Context, Args); return; } - if (ReducerName == TEXT("move_all_players")) - { - FMoveAllPlayersArgs Args = ReducerEvent.Reducer.GetAsMoveAllPlayers(); - Reducers->InvokeMoveAllPlayersWithArgs(Context, Args); - return; - } if (ReducerName == TEXT("player_split")) { FPlayerSplitArgs Args = ReducerEvent.Reducer.GetAsPlayerSplit(); @@ -844,12 +430,6 @@ void UDbConnection::ReducerEvent(const FReducerEvent& Event) Reducers->InvokeRespawnWithArgs(Context, Args); return; } - if (ReducerName == TEXT("spawn_food")) - { - FSpawnFoodArgs Args = ReducerEvent.Reducer.GetAsSpawnFood(); - Reducers->InvokeSpawnFoodWithArgs(Context, Args); - return; - } if (ReducerName == TEXT("suicide")) { FSuicideArgs Args = ReducerEvent.Reducer.GetAsSuicide(); diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h deleted file mode 100644 index d2f2106c566..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleDecay.g.h +++ /dev/null @@ -1,54 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "ModuleBindings/Types/CircleDecayTimerType.g.h" -#include "CircleDecay.g.generated.h" - -// Reducer arguments struct for CircleDecay -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FCircleDecayArgs -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") - FCircleDecayTimerType Timer; - - FCircleDecayArgs() = default; - - FCircleDecayArgs(const FCircleDecayTimerType& InTimer) - : Timer(InTimer) - {} - - - FORCEINLINE bool operator==(const FCircleDecayArgs& Other) const - { - return Timer == Other.Timer; - } - FORCEINLINE bool operator!=(const FCircleDecayArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT(FCircleDecayArgs, Timer); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API UCircleDecayReducer : public UReducerBase -{ - GENERATED_BODY() - -public: - UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - FCircleDecayTimerType Timer; - -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h deleted file mode 100644 index 411925d3334..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/CircleRecombine.g.h +++ /dev/null @@ -1,54 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "ModuleBindings/Types/CircleRecombineTimerType.g.h" -#include "CircleRecombine.g.generated.h" - -// Reducer arguments struct for CircleRecombine -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FCircleRecombineArgs -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") - FCircleRecombineTimerType Timer; - - FCircleRecombineArgs() = default; - - FCircleRecombineArgs(const FCircleRecombineTimerType& InTimer) - : Timer(InTimer) - {} - - - FORCEINLINE bool operator==(const FCircleRecombineArgs& Other) const - { - return Timer == Other.Timer; - } - FORCEINLINE bool operator!=(const FCircleRecombineArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT(FCircleRecombineArgs, Timer); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API UCircleRecombineReducer : public UReducerBase -{ - GENERATED_BODY() - -public: - UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - FCircleRecombineTimerType Timer; - -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h deleted file mode 100644 index 3f4aa4d29d0..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Connect.g.h +++ /dev/null @@ -1,43 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "Connect.g.generated.h" - -// Reducer arguments struct for Connect -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FConnectArgs -{ - GENERATED_BODY() - - FConnectArgs() = default; - - - FORCEINLINE bool operator==(const FConnectArgs& Other) const - { - return true; - } - FORCEINLINE bool operator!=(const FConnectArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT_EMPTY(FConnectArgs); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API UConnectReducer : public UReducerBase -{ - GENERATED_BODY() - -public: -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h deleted file mode 100644 index c51fb1909da..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/ConsumeEntity.g.h +++ /dev/null @@ -1,54 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "ModuleBindings/Types/ConsumeEntityTimerType.g.h" -#include "ConsumeEntity.g.generated.h" - -// Reducer arguments struct for ConsumeEntity -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FConsumeEntityArgs -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") - FConsumeEntityTimerType Request; - - FConsumeEntityArgs() = default; - - FConsumeEntityArgs(const FConsumeEntityTimerType& InRequest) - : Request(InRequest) - {} - - - FORCEINLINE bool operator==(const FConsumeEntityArgs& Other) const - { - return Request == Other.Request; - } - FORCEINLINE bool operator!=(const FConsumeEntityArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT(FConsumeEntityArgs, Request); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API UConsumeEntityReducer : public UReducerBase -{ - GENERATED_BODY() - -public: - UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - FConsumeEntityTimerType Request; - -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h deleted file mode 100644 index 1149cdb400e..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/Disconnect.g.h +++ /dev/null @@ -1,43 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "Disconnect.g.generated.h" - -// Reducer arguments struct for Disconnect -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FDisconnectArgs -{ - GENERATED_BODY() - - FDisconnectArgs() = default; - - - FORCEINLINE bool operator==(const FDisconnectArgs& Other) const - { - return true; - } - FORCEINLINE bool operator!=(const FDisconnectArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT_EMPTY(FDisconnectArgs); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API UDisconnectReducer : public UReducerBase -{ - GENERATED_BODY() - -public: -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h deleted file mode 100644 index 421c680f6ee..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/MoveAllPlayers.g.h +++ /dev/null @@ -1,54 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "ModuleBindings/Types/MoveAllPlayersTimerType.g.h" -#include "MoveAllPlayers.g.generated.h" - -// Reducer arguments struct for MoveAllPlayers -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FMoveAllPlayersArgs -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") - FMoveAllPlayersTimerType Timer; - - FMoveAllPlayersArgs() = default; - - FMoveAllPlayersArgs(const FMoveAllPlayersTimerType& InTimer) - : Timer(InTimer) - {} - - - FORCEINLINE bool operator==(const FMoveAllPlayersArgs& Other) const - { - return Timer == Other.Timer; - } - FORCEINLINE bool operator!=(const FMoveAllPlayersArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT(FMoveAllPlayersArgs, Timer); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API UMoveAllPlayersReducer : public UReducerBase -{ - GENERATED_BODY() - -public: - UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - FMoveAllPlayersTimerType Timer; - -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h deleted file mode 100644 index f3aaaa6c777..00000000000 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/Reducers/SpawnFood.g.h +++ /dev/null @@ -1,54 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#pragma once -#include "CoreMinimal.h" -#include "BSATN/UESpacetimeDB.h" -#include "ModuleBindings/ReducerBase.g.h" -#include "ModuleBindings/Types/SpawnFoodTimerType.g.h" -#include "SpawnFood.g.generated.h" - -// Reducer arguments struct for SpawnFood -USTRUCT(BlueprintType) -struct CLIENT_UNREAL_API FSpawnFoodArgs -{ - GENERATED_BODY() - - UPROPERTY(BlueprintReadWrite, Category="SpacetimeDB") - FSpawnFoodTimerType Timer; - - FSpawnFoodArgs() = default; - - FSpawnFoodArgs(const FSpawnFoodTimerType& InTimer) - : Timer(InTimer) - {} - - - FORCEINLINE bool operator==(const FSpawnFoodArgs& Other) const - { - return Timer == Other.Timer; - } - FORCEINLINE bool operator!=(const FSpawnFoodArgs& Other) const - { - return !(*this == Other); - } -}; - -namespace UE::SpacetimeDB -{ - UE_SPACETIMEDB_STRUCT(FSpawnFoodArgs, Timer); -} - -// Reducer class for internal dispatching -UCLASS(BlueprintType) -class CLIENT_UNREAL_API USpawnFoodReducer : public UReducerBase -{ - GENERATED_BODY() - -public: - UPROPERTY(BlueprintReadOnly, Category="SpacetimeDB") - FSpawnFoodTimerType Timer; - -}; - - diff --git a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h index 8092d9eb856..0ba004f7427 100644 --- a/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h +++ b/demo/Blackholio/client-unreal/Source/client_unreal/Public/ModuleBindings/SpacetimeDBClient.g.h @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.12.0 (commit 5da121d888d60ce178c202e54816252d2e4d4451). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #pragma once #include "CoreMinimal.h" @@ -13,24 +13,12 @@ #include "Connection/Subscription.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "ModuleBindings/ReducerBase.g.h" -#include "ModuleBindings/Reducers/CircleDecay.g.h" -#include "ModuleBindings/Reducers/CircleRecombine.g.h" -#include "ModuleBindings/Reducers/Connect.g.h" -#include "ModuleBindings/Reducers/ConsumeEntity.g.h" -#include "ModuleBindings/Reducers/Disconnect.g.h" #include "ModuleBindings/Reducers/EnterGame.g.h" -#include "ModuleBindings/Reducers/MoveAllPlayers.g.h" #include "ModuleBindings/Reducers/PlayerSplit.g.h" #include "ModuleBindings/Reducers/Respawn.g.h" -#include "ModuleBindings/Reducers/SpawnFood.g.h" #include "ModuleBindings/Reducers/Suicide.g.h" #include "ModuleBindings/Reducers/UpdatePlayerInput.g.h" -#include "ModuleBindings/Types/CircleDecayTimerType.g.h" -#include "ModuleBindings/Types/CircleRecombineTimerType.g.h" -#include "ModuleBindings/Types/ConsumeEntityTimerType.g.h" #include "ModuleBindings/Types/DbVector2Type.g.h" -#include "ModuleBindings/Types/MoveAllPlayersTimerType.g.h" -#include "ModuleBindings/Types/SpawnFoodTimerType.g.h" #include "Types/Builtins.h" #include "SpacetimeDBClient.g.generated.h" @@ -123,16 +111,9 @@ class CLIENT_UNREAL_API UContextBaseBpLib : public UBlueprintFunctionLibrary UENUM(BlueprintType, Category = "SpacetimeDB") enum class EReducerTag : uint8 { - CircleDecay, - CircleRecombine, - Connect, - ConsumeEntity, - Disconnect, EnterGame, - MoveAllPlayers, PlayerSplit, Respawn, - SpawnFood, Suicide, UpdatePlayerInput }; @@ -146,7 +127,7 @@ struct CLIENT_UNREAL_API FReducer UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") EReducerTag Tag = static_cast(0); - TVariant Data; + TVariant Data; // Optional metadata UPROPERTY(BlueprintReadOnly, Category = "SpacetimeDB") @@ -154,86 +135,6 @@ struct CLIENT_UNREAL_API FReducer uint32 ReducerId = 0; uint32 RequestId = 0; - static FReducer CircleDecay(const FCircleDecayArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::CircleDecay; - Out.Data.Set(Value); - Out.ReducerName = TEXT("circle_decay"); - return Out; - } - - FORCEINLINE bool IsCircleDecay() const { return Tag == EReducerTag::CircleDecay; } - FORCEINLINE FCircleDecayArgs GetAsCircleDecay() const - { - ensureMsgf(IsCircleDecay(), TEXT("Reducer does not hold CircleDecay!")); - return Data.Get(); - } - - static FReducer CircleRecombine(const FCircleRecombineArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::CircleRecombine; - Out.Data.Set(Value); - Out.ReducerName = TEXT("circle_recombine"); - return Out; - } - - FORCEINLINE bool IsCircleRecombine() const { return Tag == EReducerTag::CircleRecombine; } - FORCEINLINE FCircleRecombineArgs GetAsCircleRecombine() const - { - ensureMsgf(IsCircleRecombine(), TEXT("Reducer does not hold CircleRecombine!")); - return Data.Get(); - } - - static FReducer Connect(const FConnectArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::Connect; - Out.Data.Set(Value); - Out.ReducerName = TEXT("connect"); - return Out; - } - - FORCEINLINE bool IsConnect() const { return Tag == EReducerTag::Connect; } - FORCEINLINE FConnectArgs GetAsConnect() const - { - ensureMsgf(IsConnect(), TEXT("Reducer does not hold Connect!")); - return Data.Get(); - } - - static FReducer ConsumeEntity(const FConsumeEntityArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::ConsumeEntity; - Out.Data.Set(Value); - Out.ReducerName = TEXT("consume_entity"); - return Out; - } - - FORCEINLINE bool IsConsumeEntity() const { return Tag == EReducerTag::ConsumeEntity; } - FORCEINLINE FConsumeEntityArgs GetAsConsumeEntity() const - { - ensureMsgf(IsConsumeEntity(), TEXT("Reducer does not hold ConsumeEntity!")); - return Data.Get(); - } - - static FReducer Disconnect(const FDisconnectArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::Disconnect; - Out.Data.Set(Value); - Out.ReducerName = TEXT("disconnect"); - return Out; - } - - FORCEINLINE bool IsDisconnect() const { return Tag == EReducerTag::Disconnect; } - FORCEINLINE FDisconnectArgs GetAsDisconnect() const - { - ensureMsgf(IsDisconnect(), TEXT("Reducer does not hold Disconnect!")); - return Data.Get(); - } - static FReducer EnterGame(const FEnterGameArgs& Value) { FReducer Out; @@ -250,22 +151,6 @@ struct CLIENT_UNREAL_API FReducer return Data.Get(); } - static FReducer MoveAllPlayers(const FMoveAllPlayersArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::MoveAllPlayers; - Out.Data.Set(Value); - Out.ReducerName = TEXT("move_all_players"); - return Out; - } - - FORCEINLINE bool IsMoveAllPlayers() const { return Tag == EReducerTag::MoveAllPlayers; } - FORCEINLINE FMoveAllPlayersArgs GetAsMoveAllPlayers() const - { - ensureMsgf(IsMoveAllPlayers(), TEXT("Reducer does not hold MoveAllPlayers!")); - return Data.Get(); - } - static FReducer PlayerSplit(const FPlayerSplitArgs& Value) { FReducer Out; @@ -298,22 +183,6 @@ struct CLIENT_UNREAL_API FReducer return Data.Get(); } - static FReducer SpawnFood(const FSpawnFoodArgs& Value) - { - FReducer Out; - Out.Tag = EReducerTag::SpawnFood; - Out.Data.Set(Value); - Out.ReducerName = TEXT("spawn_food"); - return Out; - } - - FORCEINLINE bool IsSpawnFood() const { return Tag == EReducerTag::SpawnFood; } - FORCEINLINE FSpawnFoodArgs GetAsSpawnFood() const - { - ensureMsgf(IsSpawnFood(), TEXT("Reducer does not hold SpawnFood!")); - return Data.Get(); - } - static FReducer Suicide(const FSuicideArgs& Value) { FReducer Out; @@ -351,26 +220,12 @@ struct CLIENT_UNREAL_API FReducer if (Tag != Other.Tag || ReducerId != Other.ReducerId || RequestId != Other.RequestId || ReducerName != Other.ReducerName) return false; switch (Tag) { - case EReducerTag::CircleDecay: - return GetAsCircleDecay() == Other.GetAsCircleDecay(); - case EReducerTag::CircleRecombine: - return GetAsCircleRecombine() == Other.GetAsCircleRecombine(); - case EReducerTag::Connect: - return GetAsConnect() == Other.GetAsConnect(); - case EReducerTag::ConsumeEntity: - return GetAsConsumeEntity() == Other.GetAsConsumeEntity(); - case EReducerTag::Disconnect: - return GetAsDisconnect() == Other.GetAsDisconnect(); case EReducerTag::EnterGame: return GetAsEnterGame() == Other.GetAsEnterGame(); - case EReducerTag::MoveAllPlayers: - return GetAsMoveAllPlayers() == Other.GetAsMoveAllPlayers(); case EReducerTag::PlayerSplit: return GetAsPlayerSplit() == Other.GetAsPlayerSplit(); case EReducerTag::Respawn: return GetAsRespawn() == Other.GetAsRespawn(); - case EReducerTag::SpawnFood: - return GetAsSpawnFood() == Other.GetAsSpawnFood(); case EReducerTag::Suicide: return GetAsSuicide() == Other.GetAsSuicide(); case EReducerTag::UpdatePlayerInput: @@ -388,71 +243,6 @@ class CLIENT_UNREAL_API UReducerBpLib : public UBlueprintFunctionLibrary private: - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer CircleDecay(const FCircleDecayArgs& Value) { - return FReducer::CircleDecay(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsCircleDecay(const FReducer& Reducer) { return Reducer.IsCircleDecay(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FCircleDecayArgs GetAsCircleDecay(const FReducer& Reducer) { - return Reducer.GetAsCircleDecay(); - } - - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer CircleRecombine(const FCircleRecombineArgs& Value) { - return FReducer::CircleRecombine(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsCircleRecombine(const FReducer& Reducer) { return Reducer.IsCircleRecombine(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FCircleRecombineArgs GetAsCircleRecombine(const FReducer& Reducer) { - return Reducer.GetAsCircleRecombine(); - } - - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer Connect(const FConnectArgs& Value) { - return FReducer::Connect(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsConnect(const FReducer& Reducer) { return Reducer.IsConnect(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FConnectArgs GetAsConnect(const FReducer& Reducer) { - return Reducer.GetAsConnect(); - } - - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer ConsumeEntity(const FConsumeEntityArgs& Value) { - return FReducer::ConsumeEntity(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsConsumeEntity(const FReducer& Reducer) { return Reducer.IsConsumeEntity(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FConsumeEntityArgs GetAsConsumeEntity(const FReducer& Reducer) { - return Reducer.GetAsConsumeEntity(); - } - - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer Disconnect(const FDisconnectArgs& Value) { - return FReducer::Disconnect(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsDisconnect(const FReducer& Reducer) { return Reducer.IsDisconnect(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FDisconnectArgs GetAsDisconnect(const FReducer& Reducer) { - return Reducer.GetAsDisconnect(); - } - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") static FReducer EnterGame(const FEnterGameArgs& Value) { return FReducer::EnterGame(Value); @@ -466,19 +256,6 @@ class CLIENT_UNREAL_API UReducerBpLib : public UBlueprintFunctionLibrary return Reducer.GetAsEnterGame(); } - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer MoveAllPlayers(const FMoveAllPlayersArgs& Value) { - return FReducer::MoveAllPlayers(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsMoveAllPlayers(const FReducer& Reducer) { return Reducer.IsMoveAllPlayers(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FMoveAllPlayersArgs GetAsMoveAllPlayers(const FReducer& Reducer) { - return Reducer.GetAsMoveAllPlayers(); - } - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") static FReducer PlayerSplit(const FPlayerSplitArgs& Value) { return FReducer::PlayerSplit(Value); @@ -505,19 +282,6 @@ class CLIENT_UNREAL_API UReducerBpLib : public UBlueprintFunctionLibrary return Reducer.GetAsRespawn(); } - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") - static FReducer SpawnFood(const FSpawnFoodArgs& Value) { - return FReducer::SpawnFood(Value); - } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static bool IsSpawnFood(const FReducer& Reducer) { return Reducer.IsSpawnFood(); } - - UFUNCTION(BlueprintPure, Category = "SpacetimeDB|Reducer") - static FSpawnFoodArgs GetAsSpawnFood(const FReducer& Reducer) { - return Reducer.GetAsSpawnFood(); - } - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB|Reducer") static FReducer Suicide(const FSuicideArgs& Value) { return FReducer::Suicide(Value); @@ -923,27 +687,13 @@ class CLIENT_UNREAL_API USetReducerFlags : public USetReducerFlagsBase GENERATED_BODY() public: - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void CircleDecay(ECallReducerFlags Flag); - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void CircleRecombine(ECallReducerFlags Flag); - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void Connect(ECallReducerFlags Flag); - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void ConsumeEntity(ECallReducerFlags Flag); - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void Disconnect(ECallReducerFlags Flag); UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") void EnterGame(ECallReducerFlags Flag); UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void MoveAllPlayers(ECallReducerFlags Flag); - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") void PlayerSplit(ECallReducerFlags Flag); UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") void Respawn(ECallReducerFlags Flag); UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") - void SpawnFood(ECallReducerFlags Flag); - UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") void Suicide(ECallReducerFlags Flag); UFUNCTION(BlueprintCallable, Category = "SpacetimeDB") void UpdatePlayerInput(ECallReducerFlags Flag); @@ -984,74 +734,6 @@ class CLIENT_UNREAL_API URemoteReducers : public UObject public: - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( - FCircleDecayHandler, - const FReducerEventContext&, Context, - const FCircleDecayTimerType&, Timer - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FCircleDecayHandler OnCircleDecay; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void CircleDecay(const FCircleDecayTimerType& Timer); - - bool InvokeCircleDecay(const FReducerEventContext& Context, const UCircleDecayReducer* Args); - bool InvokeCircleDecayWithArgs(const FReducerEventContext& Context, const FCircleDecayArgs& Args); - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( - FCircleRecombineHandler, - const FReducerEventContext&, Context, - const FCircleRecombineTimerType&, Timer - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FCircleRecombineHandler OnCircleRecombine; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void CircleRecombine(const FCircleRecombineTimerType& Timer); - - bool InvokeCircleRecombine(const FReducerEventContext& Context, const UCircleRecombineReducer* Args); - bool InvokeCircleRecombineWithArgs(const FReducerEventContext& Context, const FCircleRecombineArgs& Args); - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( - FConnectHandler, - const FReducerEventContext&, Context - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FConnectHandler OnConnect; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void Connect(); - - bool InvokeConnect(const FReducerEventContext& Context, const UConnectReducer* Args); - bool InvokeConnectWithArgs(const FReducerEventContext& Context, const FConnectArgs& Args); - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( - FConsumeEntityHandler, - const FReducerEventContext&, Context, - const FConsumeEntityTimerType&, Request - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FConsumeEntityHandler OnConsumeEntity; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void ConsumeEntity(const FConsumeEntityTimerType& Request); - - bool InvokeConsumeEntity(const FReducerEventContext& Context, const UConsumeEntityReducer* Args); - bool InvokeConsumeEntityWithArgs(const FReducerEventContext& Context, const FConsumeEntityArgs& Args); - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( - FDisconnectHandler, - const FReducerEventContext&, Context - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FDisconnectHandler OnDisconnect; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void Disconnect(); - - bool InvokeDisconnect(const FReducerEventContext& Context, const UDisconnectReducer* Args); - bool InvokeDisconnectWithArgs(const FReducerEventContext& Context, const FDisconnectArgs& Args); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FEnterGameHandler, const FReducerEventContext&, Context, @@ -1066,20 +748,6 @@ class CLIENT_UNREAL_API URemoteReducers : public UObject bool InvokeEnterGame(const FReducerEventContext& Context, const UEnterGameReducer* Args); bool InvokeEnterGameWithArgs(const FReducerEventContext& Context, const FEnterGameArgs& Args); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( - FMoveAllPlayersHandler, - const FReducerEventContext&, Context, - const FMoveAllPlayersTimerType&, Timer - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FMoveAllPlayersHandler OnMoveAllPlayers; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void MoveAllPlayers(const FMoveAllPlayersTimerType& Timer); - - bool InvokeMoveAllPlayers(const FReducerEventContext& Context, const UMoveAllPlayersReducer* Args); - bool InvokeMoveAllPlayersWithArgs(const FReducerEventContext& Context, const FMoveAllPlayersArgs& Args); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FPlayerSplitHandler, const FReducerEventContext&, Context @@ -1106,20 +774,6 @@ class CLIENT_UNREAL_API URemoteReducers : public UObject bool InvokeRespawn(const FReducerEventContext& Context, const URespawnReducer* Args); bool InvokeRespawnWithArgs(const FReducerEventContext& Context, const FRespawnArgs& Args); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( - FSpawnFoodHandler, - const FReducerEventContext&, Context, - const FSpawnFoodTimerType&, Timer - ); - UPROPERTY(BlueprintAssignable, Category="SpacetimeDB") - FSpawnFoodHandler OnSpawnFood; - - UFUNCTION(BlueprintCallable, Category="SpacetimeDB") - void SpawnFood(const FSpawnFoodTimerType& Timer); - - bool InvokeSpawnFood(const FReducerEventContext& Context, const USpawnFoodReducer* Args); - bool InvokeSpawnFoodWithArgs(const FReducerEventContext& Context, const FSpawnFoodArgs& Args); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FSuicideHandler, const FReducerEventContext&, Context diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs index 3055bb8af42..61740321737 100644 --- a/sdks/csharp/examples~/regression-tests/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/client/Program.cs @@ -16,6 +16,8 @@ const string THROW_ERROR_MESSAGE = "this is an error"; const uint UPDATED_WHERE_TEST_VALUE = 42; const string UPDATED_WHERE_TEST_NAME = "this_name_was_updated"; +const string EXPECTED_TEST_EVENT_NAME = "hello"; +const ulong EXPECTED_TEST_EVENT_VALUE = 42; DbConnection ConnectToDB() { @@ -51,6 +53,7 @@ DbConnection ConnectToDB() uint waiting = 0; var applied = false; SubscriptionHandle? handle = null; +uint testEventInsertCount = 0; void OnConnected(DbConnection conn, Identity identity, string authToken) { @@ -72,6 +75,7 @@ void OnConnected(DbConnection conn, Identity identity, string authToken) .AddQuery(qb => qb.From.NullStringNonnullable().Build()) .AddQuery(qb => qb.From.NullStringNullable().Build()) .AddQuery(qb => qb.From.MyLog().Build()) + .AddQuery(qb => qb.From.TestEvent().Build()) .AddQuery(qb => qb.From.Admins().Build()) .AddQuery(qb => qb.From.NullableVecView().Build()) .AddQuery(qb => qb.From.WhereTest().Where(c => c.Value.Gt(10)).Build()) @@ -226,6 +230,58 @@ string name ValidateWhereSubscription(ctx, UPDATED_WHERE_TEST_NAME); ValidateWhereTestViews(ctx, UPDATED_WHERE_TEST_VALUE, UPDATED_WHERE_TEST_NAME); }; + + conn.Db.TestEvent.OnInsert += (EventContext ctx, TestEvent row) => + { + Log.Info($"Got TestEvent.OnInsert callback: {row.Name} / {row.Value}"); + testEventInsertCount++; + Debug.Assert( + row.Name == EXPECTED_TEST_EVENT_NAME, + $"Expected TestEvent.Name == {EXPECTED_TEST_EVENT_NAME}, got {row.Name}" + ); + Debug.Assert( + row.Value == EXPECTED_TEST_EVENT_VALUE, + $"Expected TestEvent.Value == {EXPECTED_TEST_EVENT_VALUE}, got {row.Value}" + ); + Debug.Assert( + ctx.Db.TestEvent.Count == 0, + $"Event table should not persist rows. Count was {ctx.Db.TestEvent.Count}" + ); + Debug.Assert( + !ctx.Db.TestEvent.Iter().Any(), + "Event table iterator should be empty after event delivery" + ); + }; + + conn.Reducers.OnEmitTestEvent += (ReducerEventContext ctx, string name, ulong value) => + { + Log.Info("Got EmitTestEvent callback"); + waiting--; + Debug.Assert( + ctx.Event.Status is Status.Committed, + $"EmitTestEvent should commit, got {ctx.Event.Status}" + ); + Debug.Assert(name == EXPECTED_TEST_EVENT_NAME, $"Expected name={EXPECTED_TEST_EVENT_NAME}, got {name}"); + Debug.Assert(value == EXPECTED_TEST_EVENT_VALUE, $"Expected value={EXPECTED_TEST_EVENT_VALUE}, got {value}"); + }; + + conn.Reducers.OnNoop += (ReducerEventContext ctx) => + { + Log.Info("Got Noop callback"); + waiting--; + Debug.Assert( + testEventInsertCount == 1, + $"Expected exactly one TestEvent insert callback after noop, got {testEventInsertCount}" + ); + Debug.Assert( + ctx.Db.TestEvent.Count == 0, + $"Event table should still be empty after noop. Count was {ctx.Db.TestEvent.Count}" + ); + Debug.Assert( + !ctx.Db.TestEvent.Iter().Any(), + "Event table iterator should remain empty after noop" + ); + }; } const uint MAX_ID = 10; @@ -646,6 +702,14 @@ void OnSubscriptionApplied(SubscriptionEventContext context) waiting++; context.Reducers.InsertNullStringIntoNullable(); + Log.Debug("Calling EmitTestEvent"); + waiting++; + context.Reducers.EmitTestEvent(EXPECTED_TEST_EVENT_NAME, EXPECTED_TEST_EVENT_VALUE); + + Log.Debug("Calling Noop after EmitTestEvent"); + waiting++; + context.Reducers.Noop(); + // Procedures tests Log.Debug("Calling ReadMySchemaViaHttp"); waiting++; diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/EmitTestEvent.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/EmitTestEvent.g.cs new file mode 100644 index 00000000000..ce0343964c0 --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/EmitTestEvent.g.cs @@ -0,0 +1,74 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteReducers : RemoteBase + { + public delegate void EmitTestEventHandler(ReducerEventContext ctx, string name, ulong value); + public event EmitTestEventHandler? OnEmitTestEvent; + + public void EmitTestEvent(string name, ulong value) + { + conn.InternalCallReducer(new Reducer.EmitTestEvent(name, value)); + } + + public bool InvokeEmitTestEvent(ReducerEventContext ctx, Reducer.EmitTestEvent args) + { + if (OnEmitTestEvent == null) + { + if (InternalOnUnhandledReducerError != null) + { + switch (ctx.Event.Status) + { + case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; + case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; + } + } + return false; + } + OnEmitTestEvent( + ctx, + args.Name, + args.Value + ); + return true; + } + } + + public abstract partial class Reducer + { + [SpacetimeDB.Type] + [DataContract] + public sealed partial class EmitTestEvent : Reducer, IReducerArgs + { + [DataMember(Name = "name")] + public string Name; + [DataMember(Name = "value")] + public ulong Value; + + public EmitTestEvent( + string Name, + ulong Value + ) + { + this.Name = Name; + this.Value = Value; + } + + public EmitTestEvent() + { + this.Name = ""; + } + + string IReducerArgs.ReducerName => "EmitTestEvent"; + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ClientConnected.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/Noop.g.cs similarity index 70% rename from sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ClientConnected.g.cs rename to sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/Noop.g.cs index a23c796730b..6bd734fccf5 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/ClientConnected.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Reducers/Noop.g.cs @@ -12,12 +12,17 @@ namespace SpacetimeDB.Types { public sealed partial class RemoteReducers : RemoteBase { - public delegate void ClientConnectedHandler(ReducerEventContext ctx); - public event ClientConnectedHandler? OnClientConnected; + public delegate void NoopHandler(ReducerEventContext ctx); + public event NoopHandler? OnNoop; - public bool InvokeClientConnected(ReducerEventContext ctx, Reducer.ClientConnected args) + public void Noop() { - if (OnClientConnected == null) + conn.InternalCallReducer(new Reducer.Noop()); + } + + public bool InvokeNoop(ReducerEventContext ctx, Reducer.Noop args) + { + if (OnNoop == null) { if (InternalOnUnhandledReducerError != null) { @@ -29,7 +34,7 @@ public bool InvokeClientConnected(ReducerEventContext ctx, Reducer.ClientConnect } return false; } - OnClientConnected( + OnNoop( ctx ); return true; @@ -40,9 +45,9 @@ public abstract partial class Reducer { [SpacetimeDB.Type] [DataContract] - public sealed partial class ClientConnected : Reducer, IReducerArgs + public sealed partial class Noop : Reducer, IReducerArgs { - string IReducerArgs.ReducerName => "ClientConnected"; + string IReducerArgs.ReducerName => "Noop"; } } } diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs index b725b37552d..69271cdcc18 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.0.0 (commit 18e8d1958a9e9cb62ca15cc849d35c1a17f9982c). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #nullable enable @@ -48,6 +48,7 @@ public RemoteTables(DbConnection conn) AddTable(ScoresPlayer123 = new(conn)); AddTable(ScoresPlayer123Level5 = new(conn)); AddTable(ScoresPlayer123Range = new(conn)); + AddTable(TestEvent = new(conn)); AddTable(User = new(conn)); AddTable(UsersAge1865 = new(conn)); AddTable(UsersAge18Plus = new(conn)); @@ -573,6 +574,7 @@ public sealed class QueryBuilder new QueryBuilder().From.ScoresPlayer123().ToSql(), new QueryBuilder().From.ScoresPlayer123Level5().ToSql(), new QueryBuilder().From.ScoresPlayer123Range().ToSql(), + new QueryBuilder().From.TestEvent().ToSql(), new QueryBuilder().From.User().ToSql(), new QueryBuilder().From.UsersAge1865().ToSql(), new QueryBuilder().From.UsersAge18Plus().ToSql(), @@ -608,6 +610,7 @@ public sealed class From public global::SpacetimeDB.Table ScoresPlayer123() => new("scores_player_123", new ScoresPlayer123Cols("scores_player_123"), new ScoresPlayer123IxCols("scores_player_123")); public global::SpacetimeDB.Table ScoresPlayer123Level5() => new("scores_player_123_level5", new ScoresPlayer123Level5Cols("scores_player_123_level5"), new ScoresPlayer123Level5IxCols("scores_player_123_level5")); public global::SpacetimeDB.Table ScoresPlayer123Range() => new("scores_player_123_range", new ScoresPlayer123RangeCols("scores_player_123_range"), new ScoresPlayer123RangeIxCols("scores_player_123_range")); + public global::SpacetimeDB.Table TestEvent() => new("test_event", new TestEventCols("test_event"), new TestEventIxCols("test_event")); public global::SpacetimeDB.Table User() => new("user", new UserCols("user"), new UserIxCols("user")); public global::SpacetimeDB.Table UsersAge1865() => new("users_age_18_65", new UsersAge1865Cols("users_age_18_65"), new UsersAge1865IxCols("users_age_18_65")); public global::SpacetimeDB.Table UsersAge18Plus() => new("users_age_18_plus", new UsersAge18PlusCols("users_age_18_plus"), new UsersAge18PlusIxCols("users_age_18_plus")); @@ -698,13 +701,14 @@ protected override bool Dispatch(IReducerEventContext context, Reducer reducer) return reducer switch { Reducer.Add args => Reducers.InvokeAdd(eventContext, args), - Reducer.ClientConnected args => Reducers.InvokeClientConnected(eventContext, args), Reducer.Delete args => Reducers.InvokeDelete(eventContext, args), + Reducer.EmitTestEvent args => Reducers.InvokeEmitTestEvent(eventContext, args), Reducer.InsertEmptyStringIntoNonNullable args => Reducers.InvokeInsertEmptyStringIntoNonNullable(eventContext, args), Reducer.InsertNullStringIntoNonNullable args => Reducers.InvokeInsertNullStringIntoNonNullable(eventContext, args), Reducer.InsertNullStringIntoNullable args => Reducers.InvokeInsertNullStringIntoNullable(eventContext, args), Reducer.InsertResult args => Reducers.InvokeInsertResult(eventContext, args), Reducer.InsertWhereTest args => Reducers.InvokeInsertWhereTest(eventContext, args), + Reducer.Noop args => Reducers.InvokeNoop(eventContext, args), Reducer.SetNullableVec args => Reducers.InvokeSetNullableVec(eventContext, args), Reducer.ThrowError args => Reducers.InvokeThrowError(eventContext, args), Reducer.UpdateWhereTest args => Reducers.InvokeUpdateWhereTest(eventContext, args), diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/TestEvent.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/TestEvent.g.cs new file mode 100644 index 00000000000..9b8f8f3d489 --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/TestEvent.g.cs @@ -0,0 +1,47 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using SpacetimeDB.BSATN; +using SpacetimeDB.ClientApi; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + public sealed partial class RemoteTables + { + public sealed class TestEventHandle : RemoteEventTableHandle + { + protected override string RemoteTableName => "test_event"; + + internal TestEventHandle(DbConnection conn) : base(conn) + { + } + } + + public readonly TestEventHandle TestEvent; + } + + public sealed class TestEventCols + { + public global::SpacetimeDB.Col Name { get; } + public global::SpacetimeDB.Col Value { get; } + + public TestEventCols(string tableName) + { + Name = new global::SpacetimeDB.Col(tableName, "Name"); + Value = new global::SpacetimeDB.Col(tableName, "Value"); + } + } + + public sealed class TestEventIxCols + { + + public TestEventIxCols(string tableName) + { + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/TestEvent.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/TestEvent.g.cs new file mode 100644 index 00000000000..d9faeffc79d --- /dev/null +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Types/TestEvent.g.cs @@ -0,0 +1,35 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace SpacetimeDB.Types +{ + [SpacetimeDB.Type] + [DataContract] + public sealed partial class TestEvent + { + [DataMember(Name = "Name")] + public string Name; + [DataMember(Name = "Value")] + public ulong Value; + + public TestEvent( + string Name, + ulong Value + ) + { + this.Name = Name; + this.Value = Value; + } + + public TestEvent() + { + this.Name = ""; + } + } +} diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Procedures/ScheduledProc.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Procedures/ScheduledProc.g.cs deleted file mode 100644 index 4d99bfe501c..00000000000 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Procedures/ScheduledProc.g.cs +++ /dev/null @@ -1,77 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteProcedures : RemoteBase - { - public void ScheduledProc(SpacetimeDB.Types.ScheduledProcTable data, ProcedureCallback callback) - { - // Convert the clean callback to the wrapper callback - InternalScheduledProc(data, (ctx, result) => - { - if (result.IsSuccess && result.Value != null) - { - callback(ctx, ProcedureCallbackResult.Success(result.Value.Value)); - } - else - { - callback(ctx, ProcedureCallbackResult.Failure(result.Error!)); - } - }); - } - - private void InternalScheduledProc(SpacetimeDB.Types.ScheduledProcTable data, ProcedureCallback callback) - { - conn.InternalCallProcedure(new Procedure.ScheduledProcArgs(data), callback); - } - - } - - public abstract partial class Procedure - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class ScheduledProc - { - [DataMember(Name = "Value")] - public SpacetimeDB.Unit Value; - - public ScheduledProc(SpacetimeDB.Unit Value) - { - this.Value = Value; - } - - public ScheduledProc() - { - } - } - [SpacetimeDB.Type] - [DataContract] - public sealed partial class ScheduledProcArgs : Procedure, IProcedureArgs - { - [DataMember(Name = "data")] - public ScheduledProcTable Data; - - public ScheduledProcArgs(ScheduledProcTable Data) - { - this.Data = Data; - } - - public ScheduledProcArgs() - { - this.Data = new(); - } - - string IProcedureArgs.ProcedureName => "scheduled_proc"; - } - - } -} diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs index 188b733f976..59344f7ba69 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.0.0 (commit 18e8d1958a9e9cb62ca15cc849d35c1a17f9982c). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #nullable enable diff --git a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs index 88cc4336398..2e01c9cce00 100644 --- a/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs +++ b/sdks/csharp/examples~/regression-tests/republishing/client/module_bindings/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.0.0 (commit 18e8d1958a9e9cb62ca15cc849d35c1a17f9982c). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #nullable enable diff --git a/sdks/csharp/examples~/regression-tests/server/Lib.cs b/sdks/csharp/examples~/regression-tests/server/Lib.cs index e4353f2cec5..47c62dc4b87 100644 --- a/sdks/csharp/examples~/regression-tests/server/Lib.cs +++ b/sdks/csharp/examples~/regression-tests/server/Lib.cs @@ -692,6 +692,13 @@ public partial class RetryLog public uint Attempts; } + [SpacetimeDB.Table(Name = "test_event", Public = true, Event = true)] + public partial struct TestEvent + { + public string Name; + public ulong Value; + } + [SpacetimeDB.Procedure] public static void InsertWithTxRetry(ProcedureContext ctx) { @@ -735,6 +742,15 @@ public static void InsertWithTxRetry(ProcedureContext ctx) Debug.Assert(outcome.IsSuccess, "Retry should have succeeded"); } + [SpacetimeDB.Reducer] + public static void EmitTestEvent(ReducerContext ctx, string name, ulong value) + { + ctx.Db.test_event.Insert(new TestEvent { Name = name, Value = value }); + } + + [SpacetimeDB.Reducer] + public static void Noop(ReducerContext ctx) { } + [SpacetimeDB.Procedure] public static void InsertWithTxPanic(ProcedureContext ctx) { From ab083e129f8487f7441fec7be5c757655fb736c9 Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Fri, 13 Feb 2026 16:57:11 -0800 Subject: [PATCH 21/24] Blackholio removed usage of scheduled reducer information as it's now private --- .../Assets/Scripts/EntityController.cs | 21 +++--- .../Scripts/autogen/Reducers/CircleDecay.g.cs | 67 ------------------- .../autogen/Reducers/CircleDecay.g.cs.meta | 11 --- .../autogen/Reducers/CircleRecombine.g.cs | 67 ------------------- .../Reducers/CircleRecombine.g.cs.meta | 11 --- .../Scripts/autogen/Reducers/Connect.g.cs | 48 ------------- .../autogen/Reducers/Connect.g.cs.meta | 11 --- .../autogen/Reducers/ConsumeEntity.g.cs | 67 ------------------- .../autogen/Reducers/ConsumeEntity.g.cs.meta | 11 --- .../Scripts/autogen/Reducers/Disconnect.g.cs | 48 ------------- .../autogen/Reducers/Disconnect.g.cs.meta | 11 --- .../autogen/Reducers/MoveAllPlayers.g.cs | 67 ------------------- .../autogen/Reducers/MoveAllPlayers.g.cs.meta | 11 --- .../Scripts/autogen/Reducers/SpawnFood.g.cs | 67 ------------------- .../autogen/Reducers/SpawnFood.g.cs.meta | 11 --- .../Scripts/autogen/SpacetimeDBClient.g.cs | 9 +-- 16 files changed, 12 insertions(+), 526 deletions(-) delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs.meta delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs.meta delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs.meta delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs.meta delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs.meta delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs.meta delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs delete mode 100644 demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs.meta diff --git a/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs b/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs index 8e23f92a24e..651aa425421 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/EntityController.cs @@ -44,16 +44,17 @@ public virtual void OnEntityUpdated(Entity newVal) public virtual void OnDelete(EventContext context) { - if (context.Event is SpacetimeDB.Event.Reducer reducer && - reducer.ReducerEvent.Reducer is Reducer.ConsumeEntity consume) - { - var consumerId = consume.Request.ConsumerEntityId; - if (GameManager.Entities.TryGetValue(consumerId, out var consumerEntity)) - { - StartCoroutine(DespawnCoroutine(consumerEntity.transform)); - return; - } - } + // TODO: Refactor to Event Tables + // if (context.Event is SpacetimeDB.Event.Reducer reducer && + // reducer.ReducerEvent.Reducer is Reducer.ConsumeEntity consume) + // { + // var consumerId = consume.Request.ConsumerEntityId; + // if (GameManager.Entities.TryGetValue(consumerId, out var consumerEntity)) + // { + // StartCoroutine(DespawnCoroutine(consumerEntity.transform)); + // return; + // } + // } Destroy(gameObject); } diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs deleted file mode 100644 index 2006c816d75..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs +++ /dev/null @@ -1,67 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void CircleDecayHandler(ReducerEventContext ctx, SpacetimeDB.Types.CircleDecayTimer timer); - public event CircleDecayHandler? OnCircleDecay; - - public void CircleDecay(SpacetimeDB.Types.CircleDecayTimer timer) - { - conn.InternalCallReducer(new Reducer.CircleDecay(timer)); - } - - public bool InvokeCircleDecay(ReducerEventContext ctx, Reducer.CircleDecay args) - { - if (OnCircleDecay == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnCircleDecay( - ctx, - args.Timer - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class CircleDecay : Reducer, IReducerArgs - { - [DataMember(Name = "_timer")] - public CircleDecayTimer Timer; - - public CircleDecay(CircleDecayTimer Timer) - { - this.Timer = Timer; - } - - public CircleDecay() - { - this.Timer = new(); - } - - string IReducerArgs.ReducerName => "circle_decay"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs.meta deleted file mode 100644 index fbedf95edf4..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleDecay.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ac1b2381309d41e4aa05575b5e1181ba -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs deleted file mode 100644 index 1738c237489..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs +++ /dev/null @@ -1,67 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void CircleRecombineHandler(ReducerEventContext ctx, SpacetimeDB.Types.CircleRecombineTimer timer); - public event CircleRecombineHandler? OnCircleRecombine; - - public void CircleRecombine(SpacetimeDB.Types.CircleRecombineTimer timer) - { - conn.InternalCallReducer(new Reducer.CircleRecombine(timer)); - } - - public bool InvokeCircleRecombine(ReducerEventContext ctx, Reducer.CircleRecombine args) - { - if (OnCircleRecombine == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnCircleRecombine( - ctx, - args.Timer - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class CircleRecombine : Reducer, IReducerArgs - { - [DataMember(Name = "timer")] - public CircleRecombineTimer Timer; - - public CircleRecombine(CircleRecombineTimer Timer) - { - this.Timer = Timer; - } - - public CircleRecombine() - { - this.Timer = new(); - } - - string IReducerArgs.ReducerName => "circle_recombine"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs.meta deleted file mode 100644 index 080bb38d655..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/CircleRecombine.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: af62a50bbddd30c4691de48550ecfb23 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs deleted file mode 100644 index ed31c1614f3..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs +++ /dev/null @@ -1,48 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void ConnectHandler(ReducerEventContext ctx); - public event ConnectHandler? OnConnect; - - public bool InvokeConnect(ReducerEventContext ctx, Reducer.Connect args) - { - if (OnConnect == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnConnect( - ctx - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class Connect : Reducer, IReducerArgs - { - string IReducerArgs.ReducerName => "connect"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs.meta deleted file mode 100644 index e4947377fb7..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Connect.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 80093a18a56cbb541a82bdafa8c12ccd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs deleted file mode 100644 index 8917fcf0ff6..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs +++ /dev/null @@ -1,67 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void ConsumeEntityHandler(ReducerEventContext ctx, SpacetimeDB.Types.ConsumeEntityTimer request); - public event ConsumeEntityHandler? OnConsumeEntity; - - public void ConsumeEntity(SpacetimeDB.Types.ConsumeEntityTimer request) - { - conn.InternalCallReducer(new Reducer.ConsumeEntity(request)); - } - - public bool InvokeConsumeEntity(ReducerEventContext ctx, Reducer.ConsumeEntity args) - { - if (OnConsumeEntity == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnConsumeEntity( - ctx, - args.Request - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class ConsumeEntity : Reducer, IReducerArgs - { - [DataMember(Name = "request")] - public ConsumeEntityTimer Request; - - public ConsumeEntity(ConsumeEntityTimer Request) - { - this.Request = Request; - } - - public ConsumeEntity() - { - this.Request = new(); - } - - string IReducerArgs.ReducerName => "consume_entity"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs.meta deleted file mode 100644 index cc8d4c8cc2f..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/ConsumeEntity.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: fdd3eba64fff07c4db7d4112a8d1af38 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs deleted file mode 100644 index 4a64b0ad0bd..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs +++ /dev/null @@ -1,48 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void DisconnectHandler(ReducerEventContext ctx); - public event DisconnectHandler? OnDisconnect; - - public bool InvokeDisconnect(ReducerEventContext ctx, Reducer.Disconnect args) - { - if (OnDisconnect == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnDisconnect( - ctx - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class Disconnect : Reducer, IReducerArgs - { - string IReducerArgs.ReducerName => "disconnect"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs.meta deleted file mode 100644 index f633768e82a..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/Disconnect.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8e1569d9cfc81fe45bd19a448d7c8777 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs deleted file mode 100644 index 3dafd3779bd..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs +++ /dev/null @@ -1,67 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void MoveAllPlayersHandler(ReducerEventContext ctx, SpacetimeDB.Types.MoveAllPlayersTimer timer); - public event MoveAllPlayersHandler? OnMoveAllPlayers; - - public void MoveAllPlayers(SpacetimeDB.Types.MoveAllPlayersTimer timer) - { - conn.InternalCallReducer(new Reducer.MoveAllPlayers(timer)); - } - - public bool InvokeMoveAllPlayers(ReducerEventContext ctx, Reducer.MoveAllPlayers args) - { - if (OnMoveAllPlayers == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnMoveAllPlayers( - ctx, - args.Timer - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class MoveAllPlayers : Reducer, IReducerArgs - { - [DataMember(Name = "_timer")] - public MoveAllPlayersTimer Timer; - - public MoveAllPlayers(MoveAllPlayersTimer Timer) - { - this.Timer = Timer; - } - - public MoveAllPlayers() - { - this.Timer = new(); - } - - string IReducerArgs.ReducerName => "move_all_players"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs.meta deleted file mode 100644 index abb234a63c7..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/MoveAllPlayers.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a452ce1270574d34a888a3f16987b74c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs deleted file mode 100644 index d7339f061d2..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs +++ /dev/null @@ -1,67 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -#nullable enable - -using System; -using SpacetimeDB.ClientApi; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace SpacetimeDB.Types -{ - public sealed partial class RemoteReducers : RemoteBase - { - public delegate void SpawnFoodHandler(ReducerEventContext ctx, SpacetimeDB.Types.SpawnFoodTimer timer); - public event SpawnFoodHandler? OnSpawnFood; - - public void SpawnFood(SpacetimeDB.Types.SpawnFoodTimer timer) - { - conn.InternalCallReducer(new Reducer.SpawnFood(timer)); - } - - public bool InvokeSpawnFood(ReducerEventContext ctx, Reducer.SpawnFood args) - { - if (OnSpawnFood == null) - { - if (InternalOnUnhandledReducerError != null) - { - switch (ctx.Event.Status) - { - case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break; - case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break; - } - } - return false; - } - OnSpawnFood( - ctx, - args.Timer - ); - return true; - } - } - - public abstract partial class Reducer - { - [SpacetimeDB.Type] - [DataContract] - public sealed partial class SpawnFood : Reducer, IReducerArgs - { - [DataMember(Name = "_timer")] - public SpawnFoodTimer Timer; - - public SpawnFood(SpawnFoodTimer Timer) - { - this.Timer = Timer; - } - - public SpawnFood() - { - this.Timer = new(); - } - - string IReducerArgs.ReducerName => "spawn_food"; - } - } -} diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs.meta b/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs.meta deleted file mode 100644 index 12f56b0e854..00000000000 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/Reducers/SpawnFood.g.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 38f8775dc9616e64db7a90f5370de352 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs b/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs index 1dc447a063a..7f62b8a8f2e 100644 --- a/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs +++ b/demo/Blackholio/client-unity/Assets/Scripts/autogen/SpacetimeDBClient.g.cs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.0.0 (commit 18e8d1958a9e9cb62ca15cc849d35c1a17f9982c). +// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). #nullable enable @@ -625,16 +625,9 @@ protected override bool Dispatch(IReducerEventContext context, Reducer reducer) var eventContext = (ReducerEventContext)context; return reducer switch { - Reducer.CircleDecay args => Reducers.InvokeCircleDecay(eventContext, args), - Reducer.CircleRecombine args => Reducers.InvokeCircleRecombine(eventContext, args), - Reducer.Connect args => Reducers.InvokeConnect(eventContext, args), - Reducer.ConsumeEntity args => Reducers.InvokeConsumeEntity(eventContext, args), - Reducer.Disconnect args => Reducers.InvokeDisconnect(eventContext, args), Reducer.EnterGame args => Reducers.InvokeEnterGame(eventContext, args), - Reducer.MoveAllPlayers args => Reducers.InvokeMoveAllPlayers(eventContext, args), Reducer.PlayerSplit args => Reducers.InvokePlayerSplit(eventContext, args), Reducer.Respawn args => Reducers.InvokeRespawn(eventContext, args), - Reducer.SpawnFood args => Reducers.InvokeSpawnFood(eventContext, args), Reducer.Suicide args => Reducers.InvokeSuicide(eventContext, args), Reducer.UpdatePlayerInput args => Reducers.InvokeUpdatePlayerInput(eventContext, args), _ => throw new ArgumentOutOfRangeException("Reducer", $"Unknown reducer {reducer}") From c66ef5265ebce2f3bfbbf18dad8cd0cc6e5140cc Mon Sep 17 00:00:00 2001 From: = Date: Fri, 13 Feb 2026 21:27:51 -0500 Subject: [PATCH 22/24] Send EventTable wire variant for event tables and bypass client cache The server was always sending event table rows as PersistentTable inserts, causing clients to store them in the cache. Fix the server to send the EventTable variant, fix Rust codegen to use into_event_diff() (bypassing the cache), revert C# Table.cs tolerance checks that are no longer needed, and fix event table test race conditions where tests passed without actually running assertions. --- crates/codegen/src/rust.rs | 38 +++++++----- .../module_subscription_manager.rs | 38 ++++++++---- sdks/csharp/src/Table.cs | 60 +++++-------------- .../rust/tests/event-table-client/src/main.rs | 23 +++---- .../src/module_bindings/mod.rs | 4 +- 5 files changed, 81 insertions(+), 82 deletions(-) diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index 411401c7251..590368e794a 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -1384,21 +1384,31 @@ impl __sdk::InModule for DbUpdate {{ ", |out| { for table in iter_tables(module, visibility) { - let with_updates = table - .primary_key - .map(|col| { - let pk_field = table.get_column(col).unwrap().name.deref().to_case(Case::Snake); - format!(".with_updates_by_pk(|row| &row.{pk_field})") - }) - .unwrap_or_default(); - let field_name = table_method_name(&table.name); - writeln!( - out, - "diff.{field_name} = cache.apply_diff_to_table::<{}>({:?}, &self.{field_name}){with_updates};", - type_ref_name(module, table.product_type_ref), - table.name.deref(), - ); + if table.is_event { + // Event tables bypass the client cache entirely. + // We construct an applied diff directly from the inserts, + // which will fire on_insert callbacks without storing rows. + writeln!( + out, + "diff.{field_name} = self.{field_name}.into_event_diff();", + ); + } else { + let with_updates = table + .primary_key + .map(|col| { + let pk_field = table.get_column(col).unwrap().name.deref().to_case(Case::Snake); + format!(".with_updates_by_pk(|row| &row.{pk_field})") + }) + .unwrap_or_default(); + + writeln!( + out, + "diff.{field_name} = cache.apply_diff_to_table::<{}>({:?}, &self.{field_name}){with_updates};", + type_ref_name(module, table.product_type_ref), + table.name.deref(), + ); + } } for view in iter_views(module) { let field_name = table_method_name(&view.name); diff --git a/crates/core/src/subscription/module_subscription_manager.rs b/crates/core/src/subscription/module_subscription_manager.rs index f68e5f2dc5e..4cdacd39ff4 100644 --- a/crates/core/src/subscription/module_subscription_manager.rs +++ b/crates/core/src/subscription/module_subscription_manager.rs @@ -1410,22 +1410,31 @@ impl SubscriptionManager { updates: &UpdatesRelValue<'_>, metrics: &mut ExecutionMetrics, rlb_pool: &impl RowListBuilderSource, + is_event_table: bool, ) -> TableUpdateRows { - let (deletes, nr_del) = ::encode_list( - rlb_pool.take_row_list_builder(), - updates.deletes.iter(), - ); let (inserts, nr_ins) = ::encode_list( rlb_pool.take_row_list_builder(), updates.inserts.iter(), ); - // TODO: Fix metrics. We only encode once, then we clone for other clients, so this isn't the place - // to report the metrics. - let _num_rows = nr_del + nr_ins; - let num_bytes = deletes.num_bytes() + inserts.num_bytes(); - metrics.bytes_scanned += num_bytes; - metrics.bytes_sent_to_clients += num_bytes; - TableUpdateRows::PersistentTable(ws_v2::PersistentTableRows { inserts, deletes }) + if is_event_table { + // Event tables only have inserts (events); no deletes. + debug_assert!(updates.deletes.is_empty(), "event tables should not produce deletes"); + metrics.bytes_scanned += inserts.num_bytes(); + metrics.bytes_sent_to_clients += inserts.num_bytes(); + TableUpdateRows::EventTable(ws_v2::EventTableRows { events: inserts }) + } else { + let (deletes, nr_del) = ::encode_list( + rlb_pool.take_row_list_builder(), + updates.deletes.iter(), + ); + // TODO: Fix metrics. We only encode once, then we clone for other clients, so this isn't the place + // to report the metrics. + let _num_rows = nr_del + nr_ins; + let num_bytes = deletes.num_bytes() + inserts.num_bytes(); + metrics.bytes_scanned += num_bytes; + metrics.bytes_sent_to_clients += num_bytes; + TableUpdateRows::PersistentTable(ws_v2::PersistentTableRows { inserts, deletes }) + } } let FoldState { updates, errs, metrics } = tables @@ -1473,7 +1482,12 @@ impl SubscriptionManager { } Ok(None) => {} Ok(Some(delta_updates)) => { - let rows = encode_v2_rows(&delta_updates, &mut acc.metrics, bsatn_rlb_pool); + let rows = encode_v2_rows( + &delta_updates, + &mut acc.metrics, + bsatn_rlb_pool, + plan.returns_event_table(), + ); for &(client_id, query_set_id) in qstate.v2_subscriptions.iter() { acc.updates.push(V2ClientUpdate { id: client_id, diff --git a/sdks/csharp/src/Table.cs b/sdks/csharp/src/Table.cs index bbaac668c02..620d99fffd1 100644 --- a/sdks/csharp/src/Table.cs +++ b/sdks/csharp/src/Table.cs @@ -355,58 +355,30 @@ void IRemoteTableHandle.Parse(TableUpdate update, ParsedDatabaseUpdate dbOps) { if (rowSet is TableUpdateRows.PersistentTable(var persistent)) { - if (IsEventTable) + // Because we are accumulating into a MultiDictionaryDelta that will be applied all-at-once + // to the table, it doesn't matter that we call Add before Remove here. + var (insertReader, insertRowCount) = CompressionHelpers.ParseRowList(persistent.Inserts); + for (var i = 0; i < insertRowCount; i++) { - // Be tolerant of persistent rowsets for event tables. - // Treat inserts as event rows, matching Rust SDK behavior. - var (insertReader, insertRowCount) = CompressionHelpers.ParseRowList(persistent.Inserts); - delta.EventRows ??= new(); - for (var i = 0; i < insertRowCount; i++) - { - var obj = DecodeValue(insertReader); - delta.EventRows.Add(obj); - } + var obj = Decode(insertReader, out var pk); + delta.Delta.Add(pk, obj); } - else + + var (deleteReader, deleteRowCount) = CompressionHelpers.ParseRowList(persistent.Deletes); + for (var i = 0; i < deleteRowCount; i++) { - // Because we are accumulating into a MultiDictionaryDelta that will be applied all-at-once - // to the table, it doesn't matter that we call Add before Remove here. - var (insertReader, insertRowCount) = CompressionHelpers.ParseRowList(persistent.Inserts); - for (var i = 0; i < insertRowCount; i++) - { - var obj = Decode(insertReader, out var pk); - delta.Delta.Add(pk, obj); - } - - var (deleteReader, deleteRowCount) = CompressionHelpers.ParseRowList(persistent.Deletes); - for (var i = 0; i < deleteRowCount; i++) - { - var obj = Decode(deleteReader, out var pk); - delta.Delta.Remove(pk, obj); - } + var obj = Decode(deleteReader, out var pk); + delta.Delta.Remove(pk, obj); } } else if (rowSet is TableUpdateRows.EventTable(var events)) { - if (IsEventTable) - { - var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); - delta.EventRows ??= new(); - for (var i = 0; i < eventRowCount; i++) - { - var obj = DecodeValue(eventReader); - delta.EventRows.Add(obj); - } - } - else + var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); + delta.EventRows ??= new(); + for (var i = 0; i < eventRowCount; i++) { - // Be tolerant of event rowsets for non-event tables by treating them as inserts. - var (eventReader, eventRowCount) = CompressionHelpers.ParseRowList(events.Events); - for (var i = 0; i < eventRowCount; i++) - { - var obj = Decode(eventReader, out var pk); - delta.Delta.Add(pk, obj); - } + var obj = DecodeValue(eventReader); + delta.EventRows.Add(obj); } } } diff --git a/sdks/rust/tests/event-table-client/src/main.rs b/sdks/rust/tests/event-table-client/src/main.rs index 33303f923f2..5edb1fdfe0a 100644 --- a/sdks/rust/tests/event-table-client/src/main.rs +++ b/sdks/rust/tests/event-table-client/src/main.rs @@ -91,18 +91,19 @@ fn subscribe_these_then( fn exec_event_table() { let test_counter = TestCounter::new(); + let sub_applied_result = test_counter.add_test("subscription_applied"); + let on_insert_result = test_counter.add_test("event-table-on-insert"); + let on_insert_result = std::sync::Mutex::new(Some(on_insert_result)); connect_then(&test_counter, { - let test_counter = test_counter.clone(); move |ctx| { subscribe_these_then(ctx, &["SELECT * FROM test_event;"], move |ctx| { // Event table should be empty on subscription applied assert_eq!(0usize, ctx.db.test_event().iter().count()); - - let mut on_insert_result = Some(test_counter.add_test("event-table-on-insert")); + sub_applied_result(Ok(())); ctx.db.test_event().on_insert(move |ctx, row| { - if let Some(set_result) = on_insert_result.take() { + if let Some(set_result) = on_insert_result.lock().unwrap().take() { let run_checks = || { assert_eq_or_bail!("hello", row.name); assert_eq_or_bail!(42u64, row.value); @@ -137,16 +138,17 @@ fn exec_event_table() { /// Test that multiple events emitted in a single reducer call all arrive as inserts. fn exec_multiple_events() { let test_counter = TestCounter::new(); + let sub_applied_result = test_counter.add_test("subscription_applied"); + let result = test_counter.add_test("multiple-events"); + let result = std::sync::Mutex::new(Some(result)); connect_then(&test_counter, { - let test_counter = test_counter.clone(); move |ctx| { subscribe_these_then(ctx, &["SELECT * FROM test_event;"], move |ctx| { assert_eq!(0usize, ctx.db.test_event().iter().count()); + sub_applied_result(Ok(())); let received = std::sync::Arc::new(AtomicU32::new(0)); - let result = test_counter.add_test("multiple-events"); - let result = std::sync::Mutex::new(Some(result)); ctx.db.test_event().on_insert({ let received = received.clone(); @@ -172,16 +174,17 @@ fn exec_multiple_events() { /// verify we didn't receive any additional event inserts. fn exec_events_dont_persist() { let test_counter = TestCounter::new(); + let sub_applied_result = test_counter.add_test("subscription_applied"); + let noop_result = test_counter.add_test("events-dont-persist"); + let noop_result = std::sync::Mutex::new(Some(noop_result)); connect_then(&test_counter, { - let test_counter = test_counter.clone(); move |ctx| { subscribe_these_then(ctx, &["SELECT * FROM test_event;"], move |ctx| { assert_eq!(0usize, ctx.db.test_event().iter().count()); + sub_applied_result(Ok(())); let insert_count = std::sync::Arc::new(AtomicU32::new(0)); - let noop_result = test_counter.add_test("events-dont-persist"); - let noop_result = std::sync::Mutex::new(Some(noop_result)); ctx.db.test_event().on_insert({ let insert_count = insert_count.clone(); diff --git a/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs b/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs index 9ea61d79d89..270a63fc8c2 100644 --- a/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/event-table-client/src/module_bindings/mod.rs @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.0.0 (commit 9e0e81a6aaec6bf3619cfb9f7916743d86ab7ffc). +// This was generated using spacetimedb cli version 2.0.0 (commit f9ecae027971fa57c15a8a38f49d2df66ee48026). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; @@ -96,7 +96,7 @@ impl __sdk::DbUpdate for DbUpdate { fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache) -> AppliedDiff<'_> { let mut diff = AppliedDiff::default(); - diff.test_event = cache.apply_diff_to_table::("test_event", &self.test_event); + diff.test_event = self.test_event.into_event_diff(); diff } From eb6fb3e8eca34993b41f1ed5d1f6386e0ecb2274 Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Sat, 14 Feb 2026 09:46:20 -0800 Subject: [PATCH 23/24] Revert changes away from Phoebe's work on tests for Websocket V2 (specifically reducer errors) --- sdks/rust/tests/test-client/src/main.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sdks/rust/tests/test-client/src/main.rs b/sdks/rust/tests/test-client/src/main.rs index 8429d1fa9a5..53b9c5e5606 100644 --- a/sdks/rust/tests/test-client/src/main.rs +++ b/sdks/rust/tests/test-client/src/main.rs @@ -1010,7 +1010,7 @@ fn exec_fail_reducer() { let test_counter = TestCounter::new(); let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); let reducer_success_result = test_counter.add_test("reducer-callback-success"); - let reducer_fail_result = test_counter.add_test("reducer-callback-fail"); + let reducer_fail_result = test_counter.add_test("reducer-callback-failure"); let connection = connect(&test_counter); @@ -1061,12 +1061,16 @@ fn exec_fail_reducer() { ctx.reducers .insert_pk_u_8_then(key, fail_data, move |ctx, status| { let run_checks = || { - match &status { - Ok(Err(_err_msg)) => {} - other => anyhow::bail!("Expected reducer error but got {other:?}"), + if let Ok(Ok(())) = &status { + anyhow::bail!( + "Expected reducer `insert_pk_u_8` to error or panic, but got a successful return" + ) } - if !matches!(ctx.event.status, Status::Err(_)) { - anyhow::bail!("Unexpected status. Expected Err but found {:?}", ctx.event.status); + + if matches!(ctx.event.status, Status::Committed) { + anyhow::bail!( + "Expected reducer `insert_pk_u_8` to error or panic, but got a `Status::Committed`" + ); } let expected_reducer = Reducer::InsertPkU8 { n: key, @@ -1865,7 +1869,7 @@ fn exec_subscribe_all_select_star() { sub_applied_nothing_result(assert_all_tables_empty(ctx)); } }) - .on_error(|_, _| panic!("Subscription error")) + .on_error(|_, e| panic!("Subscription error: {e:?}")) .subscribe_to_all_tables(); test_counter.wait_for_all(); From 829ed0d53aeba81d2eb9ae374e1e3d8fb46babc1 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 14 Feb 2026 17:02:30 -0500 Subject: [PATCH 24/24] Update codegen outputs for V10 lifecycle reducer visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The V9→V10 change in extract_schema marks lifecycle reducers as private, so they are no longer included in public codegen output. This removes stale on_connect/on_disconnect reducer files from the chat template and updates smoketest expected namespace counts from 7 to 5. --- crates/smoketests/tests/namespaces.rs | 12 ++++++------ smoketests/tests/namespaces.py | 6 +++--- .../src/module_bindings/on_connect_reducer.ts | 13 ------------- .../src/module_bindings/on_disconnect_reducer.ts | 13 ------------- .../src/module_bindings/types/reducers.ts | 4 ---- 5 files changed, 9 insertions(+), 39 deletions(-) delete mode 100644 templates/chat-react-ts/src/module_bindings/on_connect_reducer.ts delete mode 100644 templates/chat-react-ts/src/module_bindings/on_disconnect_reducer.ts diff --git a/crates/smoketests/tests/namespaces.rs b/crates/smoketests/tests/namespaces.rs index b9582749e73..96cdd9eab42 100644 --- a/crates/smoketests/tests/namespaces.rs +++ b/crates/smoketests/tests/namespaces.rs @@ -53,8 +53,8 @@ fn test_spacetimedb_ns_csharp() { let namespace = "SpacetimeDB.Types"; assert_eq!( count_matches(tmpdir.path(), &format!("namespace {}", namespace)), - 7, - "Expected 7 occurrences of 'namespace {}'", + 5, + "Expected 5 occurrences of 'namespace {}'", namespace ); assert_eq!( @@ -92,13 +92,13 @@ fn test_custom_ns_csharp() { assert_eq!( count_matches(tmpdir.path(), &format!("namespace {}", namespace)), - 7, - "Expected 7 occurrences of 'namespace {}'", + 5, + "Expected 5 occurrences of 'namespace {}'", namespace ); assert_eq!( count_matches(tmpdir.path(), "using SpacetimeDB;"), - 7, - "Expected 7 occurrences of 'using SpacetimeDB;'" + 5, + "Expected 5 occurrences of 'using SpacetimeDB;'" ); } diff --git a/smoketests/tests/namespaces.py b/smoketests/tests/namespaces.py index e0da0db729f..2cd2276876f 100644 --- a/smoketests/tests/namespaces.py +++ b/smoketests/tests/namespaces.py @@ -23,7 +23,7 @@ def test_spacetimedb_ns_csharp(self): with tempfile.TemporaryDirectory() as tmpdir: self.spacetime("generate", "--out-dir", tmpdir, "--lang=cs", "--project-path", self.project_path) - self.assertEqual(count_matches(tmpdir, f"namespace {namespace}"), 7) + self.assertEqual(count_matches(tmpdir, f"namespace {namespace}"), 5) self.assertEqual(count_matches(tmpdir, "using SpacetimeDB;"), 0) def test_custom_ns_csharp(self): @@ -34,5 +34,5 @@ def test_custom_ns_csharp(self): with tempfile.TemporaryDirectory() as tmpdir: self.spacetime("generate", "--out-dir", tmpdir, "--lang=cs", "--namespace", namespace, "--project-path", self.project_path) - self.assertEqual(count_matches(tmpdir, f"namespace {namespace}"), 7) - self.assertEqual(count_matches(tmpdir, "using SpacetimeDB;"), 7) + self.assertEqual(count_matches(tmpdir, f"namespace {namespace}"), 5) + self.assertEqual(count_matches(tmpdir, "using SpacetimeDB;"), 5) diff --git a/templates/chat-react-ts/src/module_bindings/on_connect_reducer.ts b/templates/chat-react-ts/src/module_bindings/on_connect_reducer.ts deleted file mode 100644 index 2ca99c88fea..00000000000 --- a/templates/chat-react-ts/src/module_bindings/on_connect_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from 'spacetimedb'; - -export default {}; diff --git a/templates/chat-react-ts/src/module_bindings/on_disconnect_reducer.ts b/templates/chat-react-ts/src/module_bindings/on_disconnect_reducer.ts deleted file mode 100644 index 2ca99c88fea..00000000000 --- a/templates/chat-react-ts/src/module_bindings/on_disconnect_reducer.ts +++ /dev/null @@ -1,13 +0,0 @@ -// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE -// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. - -/* eslint-disable */ -/* tslint:disable */ -import { - TypeBuilder as __TypeBuilder, - t as __t, - type AlgebraicTypeType as __AlgebraicTypeType, - type Infer as __Infer, -} from 'spacetimedb'; - -export default {}; diff --git a/templates/chat-react-ts/src/module_bindings/types/reducers.ts b/templates/chat-react-ts/src/module_bindings/types/reducers.ts index f3ed1660bff..ff1fdae4337 100644 --- a/templates/chat-react-ts/src/module_bindings/types/reducers.ts +++ b/templates/chat-react-ts/src/module_bindings/types/reducers.ts @@ -6,12 +6,8 @@ import { type Infer as __Infer } from 'spacetimedb'; // Import all reducer arg schemas -import OnConnectReducer from '../on_connect_reducer'; -import OnDisconnectReducer from '../on_disconnect_reducer'; import SendMessageReducer from '../send_message_reducer'; import SetNameReducer from '../set_name_reducer'; -export type OnConnectParams = __Infer; -export type OnDisconnectParams = __Infer; export type SendMessageParams = __Infer; export type SetNameParams = __Infer;