diff --git a/Cargo.lock b/Cargo.lock index a8c65fe12c8..defcc7c2a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5260,6 +5260,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procedure-client" +version = "1.7.0" +dependencies = [ + "anyhow", + "env_logger 0.10.2", + "spacetimedb-sdk", + "test-counter", +] + [[package]] name = "prometheus" version = "0.13.4" @@ -6538,6 +6548,16 @@ dependencies = [ "spacetimedb 1.7.0", ] +[[package]] +name = "sdk-test-procedure-module" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "paste", + "spacetimedb 1.7.0", +] + [[package]] name = "sdk-unreal-test-harness" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index d1dc2344c4d..0551f9f5edb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,9 +43,11 @@ members = [ "modules/quickstart-chat", "modules/sdk-test", "modules/sdk-test-connect-disconnect", + "modules/sdk-test-procedure", "sdks/rust/tests/test-client", "sdks/rust/tests/test-counter", "sdks/rust/tests/connect_disconnect_client", + "sdks/rust/tests/procedure-client", "tools/upgrade-version", "tools/license-check", "crates/bindings-typescript/test-app/server", diff --git a/crates/codegen/src/csharp.rs b/crates/codegen/src/csharp.rs index 51201863203..e81035aa594 100644 --- a/crates/codegen/src/csharp.rs +++ b/crates/codegen/src/csharp.rs @@ -704,6 +704,18 @@ impl Lang for Csharp<'_> { } } + fn generate_procedure_file( + &self, + _module: &ModuleDef, + procedure: &spacetimedb_schema::def::ProcedureDef, + ) -> OutputFile { + // TODO(procedure-csharp-client): implement this + OutputFile { + filename: format!("Procedures/{}.g.cs", procedure.name.deref().to_case(Case::Pascal)), + code: "".to_string(), + } + } + fn generate_global_files(&self, module: &ModuleDef) -> Vec { let mut output = CsharpAutogen::new( self.namespace, diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index a38b1135eac..6dbaeeea910 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -1,6 +1,5 @@ -use spacetimedb_schema::def::{ModuleDef, ReducerDef, TableDef, TypeDef, ViewDef}; +use spacetimedb_schema::def::{ModuleDef, ProcedureDef, ReducerDef, TableDef, TypeDef, ViewDef}; use spacetimedb_schema::schema::{Schema, TableSchema}; - mod code_indenter; pub mod csharp; pub mod rust; @@ -20,6 +19,7 @@ pub fn generate(module: &ModuleDef, lang: &dyn Lang) -> Vec { module.views().map(|view| lang.generate_view_file(module, view)), module.types().flat_map(|typ| lang.generate_type_files(module, typ)), util::iter_reducers(module).map(|reducer| lang.generate_reducer_file(module, reducer)), + util::iter_procedures(module).map(|procedure| lang.generate_procedure_file(module, procedure)), lang.generate_global_files(module), ) .collect() @@ -34,6 +34,7 @@ pub trait Lang { fn generate_table_file_from_schema(&self, module: &ModuleDef, tbl: &TableDef, schema: TableSchema) -> OutputFile; fn generate_type_files(&self, module: &ModuleDef, typ: &TypeDef) -> Vec; fn generate_reducer_file(&self, module: &ModuleDef, reducer: &ReducerDef) -> OutputFile; + fn generate_procedure_file(&self, module: &ModuleDef, procedure: &ProcedureDef) -> OutputFile; fn generate_global_files(&self, module: &ModuleDef) -> Vec; fn generate_table_file(&self, module: &ModuleDef, tbl: &TableDef) -> OutputFile { diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index 97dea6815a5..a968ec7826a 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -2,13 +2,14 @@ use super::code_indenter::{CodeIndenter, Indenter}; use super::util::{collect_case, iter_reducers, print_lines, type_ref_name}; use super::Lang; use crate::util::{ - iter_tables, iter_types, iter_unique_cols, print_auto_generated_file_comment, print_auto_generated_version_comment, + iter_procedures, iter_tables, iter_types, iter_unique_cols, print_auto_generated_file_comment, + print_auto_generated_version_comment, }; use crate::OutputFile; use convert_case::{Case, Casing}; use spacetimedb_lib::sats::layout::PrimitiveType; use spacetimedb_lib::sats::AlgebraicTypeRef; -use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef}; +use spacetimedb_schema::def::{ModuleDef, ProcedureDef, ReducerDef, ScopedTypeName, TableDef, TypeDef}; use spacetimedb_schema::identifier::Identifier; use spacetimedb_schema::schema::TableSchema; use spacetimedb_schema::type_for_generate::{AlgebraicTypeDef, AlgebraicTypeUse}; @@ -313,7 +314,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 = reducer_args_type_name(&reducer.name); + let args_type = function_args_type_name(&reducer.name); let enum_variant_name = reducer_variant_name(&reducer.name); // Define an "args struct" for the reducer. @@ -338,26 +339,11 @@ pub(super) fn parse_table_update( let callback_id = reducer_callback_id_name(&reducer.name); - // The reducer arguments as `ident: ty, ident: ty, ident: ty,`, - // like an argument list. - let mut arglist = String::new(); - write_arglist_no_delimiters(module, &mut arglist, &reducer.params_for_generate.elements, None).unwrap(); - - // The reducer argument types as `&ty, &ty, &ty`, - // for use as the params in a `FnMut` closure type. - let mut arg_types_ref_list = String::new(); - // The reducer argument names as `ident, ident, ident`, - // for passing to function call and struct literal expressions. - let mut arg_names_list = String::new(); - for (arg_ident, arg_ty) in &reducer.params_for_generate.elements[..] { - arg_types_ref_list += "&"; - write_type(module, &mut arg_types_ref_list, arg_ty).unwrap(); - arg_types_ref_list += ", "; - - let arg_name = arg_ident.deref().to_case(Case::Snake); - arg_names_list += &arg_name; - arg_names_list += ", "; - } + let FormattedArglist { + arglist_no_delimiters, + arg_type_refs, + arg_names, + } = FormattedArglist::for_arguments(module, &reducer.params_for_generate.elements); write!(out, "impl From<{args_type}> for super::Reducer "); out.delimited_block( @@ -412,7 +398,7 @@ 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}) -> __sdk::Result<()>; + 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`] @@ -420,19 +406,19 @@ pub trait {func_name} {{ /// /// 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_types_ref_list}) + Send + 'static) -> {callback_id}; + 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}) -> __sdk::Result<()> {{ - self.imp.call_reducer({reducer_name:?}, {args_type} {{ {arg_names_list} }}) + 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_types_ref_list}) + Send + 'static, + mut callback: impl FnMut(&super::ReducerEventContext, {arg_type_refs}) + Send + 'static, ) -> {callback_id} {{ {callback_id}(self.imp.on_reducer( {reducer_name:?}, @@ -440,13 +426,13 @@ impl {func_name} for super::RemoteReducers {{ let super::ReducerEventContext {{ event: __sdk::ReducerEvent {{ reducer: super::Reducer::{enum_variant_name} {{ - {arg_names_list} + {arg_names} }}, .. }}, .. }} = ctx else {{ unreachable!() }}; - callback(ctx, {arg_names_list}) + callback(ctx, {arg_names}) }}), )) }} @@ -483,6 +469,93 @@ impl {set_reducer_flags_trait} for super::SetReducerFlags {{ } } + fn generate_procedure_file(&self, module: &ModuleDef, procedure: &ProcedureDef) -> OutputFile { + let mut output = CodeIndenter::new(String::new(), INDENT); + let out = &mut output; + + print_file_header(out, false); + + out.newline(); + + let mut imports = Imports::new(); + gen_imports(&mut imports, &procedure.params_for_generate.elements); + add_one_import(&mut imports, &procedure.return_type_for_generate); + print_imports(module, out, imports); + + out.newline(); + + let procedure_name = procedure.name.deref(); + let func_name = procedure_function_name(procedure); + let func_name_with_callback = procedure_function_with_callback_name(procedure); + let args_type = function_args_type_name(&procedure.name); + let res_ty_name = type_name(module, &procedure.return_type_for_generate); + + // Define an "args struct" as a serialization helper. + // This is not user-facing, it's not used outside this file. + // Unlike with reducers, we don't have to deserialize procedure args to build events, + // as we don't broadcast procedure args. + define_struct_for_product( + module, + out, + &args_type, + &procedure.params_for_generate.elements, + // non-pub visibility. + "", + ); + + out.newline(); + + let FormattedArglist { + arglist_no_delimiters, + arg_names, + .. + } = FormattedArglist::for_arguments(module, &procedure.params_for_generate.elements); + + writeln!( + out, + " +impl __sdk::InModule for {args_type} {{ + type Module = super::RemoteModule; +}} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `{procedure_name}`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait {func_name} {{ + fn {func_name}(&self, {arglist_no_delimiters}) {{ + self.{func_name_with_callback}({arg_names} |_, _| {{}}); + }} + + fn {func_name_with_callback}( + &self, + {arglist_no_delimiters} + __callback: impl FnOnce(&super::ProcedureEventContext, Result<{res_ty_name}, __sdk::InternalError>) + Send + 'static, + ); +}} + +impl {func_name} for super::RemoteProcedures {{ + fn {func_name_with_callback}( + &self, + {arglist_no_delimiters} + __callback: impl FnOnce(&super::ProcedureEventContext, Result<{res_ty_name}, __sdk::InternalError>) + Send + 'static, + ) {{ + self.imp.invoke_procedure_with_callback::<_, {res_ty_name}>( + {procedure_name:?}, + {args_type} {{ {arg_names} }}, + __callback, + ); + }} +}} +" + ); + + OutputFile { + filename: procedure_module_name(&procedure.name) + ".rs", + code: output.into_inner(), + } + } + fn generate_global_files(&self, module: &ModuleDef) -> Vec { let mut output = CodeIndenter::new(String::new(), INDENT); let out = &mut output; @@ -516,7 +589,8 @@ impl {set_reducer_flags_trait} for super::SetReducerFlags {{ out.newline(); - // Define `RemoteModule`, `DbConnection`, `EventContext`, `RemoteTables`, `RemoteReducers` and `SubscriptionHandle`. + // Define `RemoteModule`, `DbConnection`, `EventContext`, `RemoteTables`, + // `RemoteReducers`, `RemoteProcedures` and `SubscriptionHandle`. // Note that these do not change based on the module. print_const_db_context_types(out); @@ -583,6 +657,51 @@ pub fn type_name(module: &ModuleDef, ty: &AlgebraicTypeUse) -> String { s } +/// Arguments to a reducer or procedure pretty-printed in various ways that are convenient to compute together. +struct FormattedArglist { + /// The arguments as `ident: ty, ident: ty, ident: ty,`, + /// like an argument list. + /// + /// 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. + /// + /// Always carries a trailing comma, unless it's zero elements. + arg_names: String, +} + +impl FormattedArglist { + fn for_arguments(module: &ModuleDef, params: &[(Identifier, AlgebraicTypeUse)]) -> Self { + let mut arglist_no_delimiters = String::new(); + 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 += ", "; + + let arg_name = arg_ident.deref().to_case(Case::Snake); + arg_names += &arg_name; + arg_names += ", "; + } + + Self { + arglist_no_delimiters, + arg_type_refs, + arg_names, + } + } +} + const ALLOW_LINTS: &str = "#![allow(unused, clippy::all)]"; const SPACETIMEDB_IMPORTS: &[&str] = &[ @@ -775,8 +894,8 @@ fn table_access_trait_name(table_name: &Identifier) -> String { table_name.deref().to_case(Case::Pascal) + "TableAccess" } -fn reducer_args_type_name(reducer_name: &Identifier) -> String { - reducer_name.deref().to_case(Case::Pascal) + "Args" +fn function_args_type_name(function_name: &Identifier) -> String { + function_name.deref().to_case(Case::Pascal) + "Args" } fn reducer_variant_name(reducer_name: &Identifier) -> String { @@ -795,6 +914,18 @@ fn reducer_function_name(reducer: &ReducerDef) -> String { reducer.name.deref().to_case(Case::Snake) } +fn procedure_module_name(procedure_name: &Identifier) -> String { + procedure_name.deref().to_case(Case::Snake) + "_procedure" +} + +fn procedure_function_name(procedure: &ProcedureDef) -> String { + procedure.name.deref().to_case(Case::Snake) +} + +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)) } @@ -805,6 +936,7 @@ fn iter_module_names(module: &ModuleDef) -> impl Iterator + '_ { iter_types(module).map(|ty| type_module_name(&ty.name)), iter_reducers(module).map(|r| reducer_module_name(&r.name)), iter_tables(module).map(|tbl| table_module_name(&tbl.name)), + iter_procedures(module).map(|proc| procedure_module_name(&proc.name)), ) } @@ -841,6 +973,11 @@ fn print_module_reexports(module: &ModuleDef, out: &mut Indenter) { "pub use {mod_name}::{{{reducer_trait_name}, {flags_trait_name}, {callback_id_name}}};" ); } + for procedure in iter_procedures(module) { + let mod_name = procedure_module_name(&procedure.name); + let trait_name = procedure_function_name(procedure); + writeln!(out, "pub use {mod_name}::{trait_name};"); + } } fn print_reducer_enum_defn(module: &ModuleDef, out: &mut Indenter) { @@ -905,6 +1042,10 @@ impl __sdk::InModule for Reducer {{ } writeln!(out, " => {:?},", 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!(),"); }, "}\n", ); @@ -946,7 +1087,7 @@ impl __sdk::InModule for Reducer {{ "{:?} => Ok(__sdk::parse_reducer_args::<{}::{}>({:?}, &value.args)?.into()),", reducer.name.deref(), reducer_module_name(&reducer.name), - reducer_args_type_name(&reducer.name), + function_args_type_name(&reducer.name), reducer.name.deref(), ); } @@ -1082,6 +1223,10 @@ fn print_applied_diff_defn(module: &ModuleDef, out: &mut Indenter) { type_ref_name(module, table.product_type_ref), ); } + // Also write a `PhantomData` field which uses the lifetime `r`, + // in case the module defines zero tables, + // as unused lifetime params are an error. + writeln!(out, "__unused: std::marker::PhantomData<&'r ()>,",); }, "}\n", ); @@ -1130,6 +1275,7 @@ fn print_impl_spacetime_module(module: &ModuleDef, out: &mut Indenter) { type DbConnection = DbConnection; type EventContext = EventContext; type ReducerEventContext = ReducerEventContext; +type ProcedureEventContext = ProcedureEventContext; type SubscriptionEventContext = SubscriptionEventContext; type ErrorContext = ErrorContext; type Reducer = Reducer; @@ -1176,6 +1322,16 @@ 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; +}} + #[doc(hidden)] /// The `set_reducer_flags` field of [`DbConnection`], /// with methods provided by extension traits for each reducer defined by the module. @@ -1228,6 +1384,9 @@ pub struct DbConnection {{ /// 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, }} @@ -1238,6 +1397,7 @@ impl __sdk::InModule for DbConnection {{ impl __sdk::DbContext for DbConnection {{ type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView {{ @@ -1246,6 +1406,9 @@ impl __sdk::DbContext for DbConnection {{ fn reducers(&self) -> &Self::Reducers {{ &self.reducers }} + fn procedures(&self) -> &Self::Procedures {{ + &self.procedures + }} fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{ &self.set_reducer_flags }} @@ -1351,6 +1514,7 @@ impl __sdk::DbConnection for DbConnection {{ Self {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, + procedures: RemoteProcedures {{ imp: imp.clone() }}, set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, imp, }} @@ -1431,6 +1595,14 @@ impl, @@ -1503,6 +1677,7 @@ impl __sdk::AbstractEventContext for {struct_and_trait_name} {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, + procedures: RemoteProcedures {{ imp: imp.clone() }}, event, imp, }} @@ -1526,6 +1701,8 @@ pub struct {struct_and_trait_name} {{ /// /// 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, }} @@ -1538,6 +1715,7 @@ impl __sdk::AbstractEventContext for {struct_and_trait_name} {{ Self {{ db: RemoteTables {{ imp: imp.clone() }}, reducers: RemoteReducers {{ imp: imp.clone() }}, + procedures: RemoteProcedures {{ imp: imp.clone() }}, set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }}, imp, }} @@ -1557,6 +1735,7 @@ impl __sdk::InModule for {struct_and_trait_name} {{ 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 {{ @@ -1565,6 +1744,9 @@ impl __sdk::DbContext for {struct_and_trait_name} {{ fn reducers(&self) -> &Self::Reducers {{ &self.reducers }} + fn procedures(&self) -> &Self::Procedures {{ + &self.procedures + }} fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{ &self.set_reducer_flags }} @@ -1608,6 +1790,24 @@ fn print_imports(module: &ModuleDef, out: &mut Indenter, imports: Imports) { } } +fn add_one_import(imports: &mut Imports, import: &AlgebraicTypeUse) { + import.for_each_ref(|r| { + imports.insert(r); + }) +} + +fn gen_imports(imports: &mut Imports, roots: &[(Identifier, AlgebraicTypeUse)]) { + for (_, ty) in roots { + add_one_import(imports, ty); + } +} + +fn remove_skipped_imports(imports: &mut Imports, dont_import: &[AlgebraicTypeRef]) { + for skip in dont_import { + imports.remove(skip); + } +} + /// Use `search_function` on `roots` to detect required imports, then print them with `print_imports`. /// /// `this_file` is passed and excluded for the case of recursive types: @@ -1621,14 +1821,8 @@ fn gen_and_print_imports( ) { let mut imports = BTreeSet::new(); - for (_, ty) in roots { - ty.for_each_ref(|r| { - imports.insert(r); - }); - } - for skip in dont_import { - imports.remove(skip); - } + gen_imports(&mut imports, roots); + remove_skipped_imports(&mut imports, dont_import); print_imports(module, out, imports); } diff --git a/crates/codegen/src/typescript.rs b/crates/codegen/src/typescript.rs index 6f055ee1622..135c5852c67 100644 --- a/crates/codegen/src/typescript.rs +++ b/crates/codegen/src/typescript.rs @@ -310,6 +310,18 @@ removeOnUpdate = (cb: (ctx: EventContext, onRow: {row_type}, newRow: {row_type}) } } + fn generate_procedure_file( + &self, + _module: &ModuleDef, + procedure: &spacetimedb_schema::def::ProcedureDef, + ) -> OutputFile { + // TODO(procedure-typescript-client): implement this + OutputFile { + filename: procedure_module_name(&procedure.name) + ".ts", + code: "".to_string(), + } + } + fn generate_global_files(&self, module: &ModuleDef) -> Vec { let mut output = CodeIndenter::new(String::new(), INDENT); let out = &mut output; @@ -1047,6 +1059,10 @@ fn reducer_function_name(reducer: &ReducerDef) -> String { reducer.name.deref().to_case(Case::Camel) } +fn procedure_module_name(procedure_name: &Identifier) -> String { + procedure_name.deref().to_case(Case::Snake) + "_procedure" +} + pub fn type_name(module: &ModuleDef, ty: &AlgebraicTypeUse) -> String { let mut s = String::new(); write_type(module, &mut s, ty, None, None).unwrap(); diff --git a/crates/codegen/src/unrealcpp.rs b/crates/codegen/src/unrealcpp.rs index 70cde2359a8..280b1be4475 100644 --- a/crates/codegen/src/unrealcpp.rs +++ b/crates/codegen/src/unrealcpp.rs @@ -599,6 +599,22 @@ impl Lang for UnrealCpp<'_> { } } + fn generate_procedure_file( + &self, + _module: &ModuleDef, + procedure: &spacetimedb_schema::def::ProcedureDef, + ) -> OutputFile { + // TODO(procedure-unreal-client): implement this + OutputFile { + filename: format!( + "Source/{}/Public/ModuleBindings/Procedures/{}.g.h", + self.module_name, + procedure.name.deref().to_case(Case::Pascal) + ), + code: "".to_string(), + } + } + fn generate_global_files(&self, module: &ModuleDef) -> Vec { let mut files: Vec = vec![]; diff --git a/crates/codegen/src/util.rs b/crates/codegen/src/util.rs index a00a7daa024..1065fc39152 100644 --- a/crates/codegen/src/util.rs +++ b/crates/codegen/src/util.rs @@ -12,8 +12,8 @@ use spacetimedb_lib::sats::layout::PrimitiveType; use spacetimedb_lib::version; use spacetimedb_lib::{db::raw_def::v9::Lifecycle, sats::AlgebraicTypeRef}; use spacetimedb_primitives::ColList; -use spacetimedb_schema::schema::TableSchema; use spacetimedb_schema::type_for_generate::ProductTypeDef; +use spacetimedb_schema::{def::ProcedureDef, schema::TableSchema}; use spacetimedb_schema::{ def::{IndexDef, TableDef, TypeDef}, type_for_generate::TypespaceForGenerate, @@ -98,6 +98,13 @@ pub(super) fn iter_reducers(module: &ModuleDef) -> impl Iterator impl Iterator { + module.procedures().sorted_by_key(|procedure| &procedure.name) +} + /// Iterate over all the [`TableDef`]s defined by the module, in alphabetical order by name. /// /// Sorting is necessary to have deterministic reproducible codegen. diff --git a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap index 601a470272d..4d2796d4811 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_csharp.snap @@ -2,6 +2,8 @@ source: crates/codegen/tests/codegen.rs expression: outfiles --- +"Procedures/ReturnValue.g.cs" = '' +"Procedures/SleepOneSecond.g.cs" = '' "Reducers/Add.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 121416c4069..a334dec7a9c 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_rust.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_rust.snap @@ -1421,6 +1421,8 @@ pub mod test_a_table; pub mod test_d_table; pub mod test_e_table; pub mod test_f_table; +pub mod return_value_procedure; +pub mod sleep_one_second_procedure; pub use baz_type::Baz; pub use foobar_type::Foobar; @@ -1464,6 +1466,8 @@ pub use repeating_test_reducer::{repeating_test, set_flags_for_repeating_test, R 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 return_value_procedure::return_value; +pub use sleep_one_second_procedure::sleep_one_second; #[derive(Clone, PartialEq, Debug)] @@ -1531,6 +1535,7 @@ impl __sdk::Reducer for Reducer { Reducer::SayHello => "say_hello", Reducer::Test { .. } => "test", Reducer::TestBtreeIndexArgs => "test_btree_index_args", + _ => unreachable!(), } } } @@ -1650,6 +1655,7 @@ pub struct AppliedDiff<'r> { test_d: __sdk::TableAppliedDiff<'r, TestD>, test_e: __sdk::TableAppliedDiff<'r, TestE>, test_f: __sdk::TableAppliedDiff<'r, TestFoobar>, + __unused: std::marker::PhantomData<&'r ()>, } @@ -1692,6 +1698,16 @@ 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; +} + #[doc(hidden)] /// The `set_reducer_flags` field of [`DbConnection`], /// with methods provided by extension traits for each reducer defined by the module. @@ -1744,6 +1760,9 @@ pub struct DbConnection { /// 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, } @@ -1754,6 +1773,7 @@ impl __sdk::InModule for DbConnection { impl __sdk::DbContext for DbConnection { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -1762,6 +1782,9 @@ impl __sdk::DbContext for DbConnection { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -1867,6 +1890,7 @@ impl __sdk::DbConnection for DbConnection { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } @@ -1942,6 +1966,8 @@ pub struct EventContext { /// /// 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. pub event: __sdk::Event, imp: __sdk::DbContextImpl, @@ -1957,6 +1983,7 @@ impl __sdk::AbstractEventContext for EventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -1970,6 +1997,7 @@ impl __sdk::InModule for EventContext { impl __sdk::DbContext for EventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -1978,6 +2006,9 @@ impl __sdk::DbContext for EventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -2021,6 +2052,8 @@ pub struct ReducerEventContext { /// /// 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. pub event: __sdk::ReducerEvent, imp: __sdk::DbContextImpl, @@ -2036,6 +2069,7 @@ impl __sdk::AbstractEventContext for ReducerEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -2049,6 +2083,7 @@ impl __sdk::InModule for ReducerEventContext { impl __sdk::DbContext for ReducerEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -2057,6 +2092,9 @@ impl __sdk::DbContext for ReducerEventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -2088,6 +2126,88 @@ impl __sdk::DbContext for ReducerEventContext { 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 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, +} + +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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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`]. @@ -2099,6 +2219,8 @@ pub struct SubscriptionEventContext { /// /// 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, } @@ -2111,6 +2233,7 @@ impl __sdk::AbstractEventContext for SubscriptionEventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } @@ -2124,6 +2247,7 @@ impl __sdk::InModule for SubscriptionEventContext { impl __sdk::DbContext for SubscriptionEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -2132,6 +2256,9 @@ impl __sdk::DbContext for SubscriptionEventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -2175,6 +2302,8 @@ pub struct ErrorContext { /// /// 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. pub event: Option<__sdk::Error>, imp: __sdk::DbContextImpl, @@ -2190,6 +2319,7 @@ impl __sdk::AbstractEventContext for ErrorContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -2203,6 +2333,7 @@ impl __sdk::InModule for ErrorContext { impl __sdk::DbContext for ErrorContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -2211,6 +2342,9 @@ impl __sdk::DbContext for ErrorContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -2247,6 +2381,7 @@ 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; @@ -3689,6 +3824,65 @@ impl set_flags_for_repeating_test for super::SetReducerFlags { } } +''' +"return_value_procedure.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::baz_type::Baz; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] + struct ReturnValueArgs { + pub foo: u64, +} + + +impl __sdk::InModule for ReturnValueArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `return_value`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait return_value { + fn return_value(&self, foo: u64, +) { + self.return_value_then(foo, |_, _| {}); + } + + fn return_value_then( + &self, + foo: u64, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ); +} + +impl return_value for super::RemoteProcedures { + fn return_value_then( + &self, + foo: u64, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ) { + self.imp.invoke_procedure_with_callback::<_, Baz>( + "return_value", + ReturnValueArgs { foo, }, + __callback, + ); + } +} + ''' "say_hello_reducer.rs" = ''' // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE @@ -3793,6 +3987,60 @@ impl set_flags_for_say_hello for super::SetReducerFlags { } } +''' +"sleep_one_second_procedure.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)] + struct SleepOneSecondArgs { + } + + +impl __sdk::InModule for SleepOneSecondArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `sleep_one_second`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait sleep_one_second { + fn sleep_one_second(&self, ) { + self.sleep_one_second_then( |_, _| {}); + } + + fn sleep_one_second_then( + &self, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result<(), __sdk::InternalError>) + Send + 'static, + ); +} + +impl sleep_one_second for super::RemoteProcedures { + fn sleep_one_second_then( + &self, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result<(), __sdk::InternalError>) + Send + 'static, + ) { + self.imp.invoke_procedure_with_callback::<_, ()>( + "sleep_one_second", + SleepOneSecondArgs { }, + __callback, + ); + } +} + ''' "test_a_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 7ad3de0f26a..799e85147f9 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -3234,6 +3234,7 @@ export const RepeatingTest = { export default RepeatingTest; ''' +"return_value_procedure.ts" = '' "say_hello_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. @@ -3297,6 +3298,7 @@ export const SayHello = { export default SayHello; ''' +"sleep_one_second_procedure.ts" = '' "test_a_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. diff --git a/flake.nix b/flake.nix index 3b6c6815f4c..05ade3d349c 100644 --- a/flake.nix +++ b/flake.nix @@ -168,7 +168,6 @@ packages = [ rustStable rustNightly - cargoArtifacts pkgs.cargo-insta ]; }; diff --git a/modules/sdk-test-connect-disconnect/README.md b/modules/sdk-test-connect-disconnect/README.md index 79ba644fdad..23250dd5d4b 100644 --- a/modules/sdk-test-connect-disconnect/README.md +++ b/modules/sdk-test-connect-disconnect/README.md @@ -8,9 +8,9 @@ This module tests that we can observe `connect`/`disconnect` events for WebSocke ## How to Run -Execute the tests on `spacetimedb-sdk` at [test.rs](../../crates/sdk/tests/test.rs): +Execute the tests on `spacetimedb-sdk` at [test.rs](../../sdks/rust/tests/test.rs): ```bash # Will run both Rust/C# modules cargo test -p spacetimedb-sdk connect -``` \ No newline at end of file +``` diff --git a/modules/sdk-test-procedure/.cargo/config.toml b/modules/sdk-test-procedure/.cargo/config.toml new file mode 100644 index 00000000000..f4e8c002fc2 --- /dev/null +++ b/modules/sdk-test-procedure/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/modules/sdk-test-procedure/Cargo.toml b/modules/sdk-test-procedure/Cargo.toml new file mode 100644 index 00000000000..6e143334e10 --- /dev/null +++ b/modules/sdk-test-procedure/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sdk-test-procedure-module" +version = "0.1.0" +edition.workspace = true +license-file = "LICENSE" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +log.workspace = true +anyhow.workspace = true +paste.workspace = true + +[dependencies.spacetimedb] +workspace = true +features = ["unstable"] diff --git a/modules/sdk-test-procedure/LICENSE b/modules/sdk-test-procedure/LICENSE new file mode 120000 index 00000000000..8540cf8a991 --- /dev/null +++ b/modules/sdk-test-procedure/LICENSE @@ -0,0 +1 @@ +../../licenses/BSL.txt \ No newline at end of file diff --git a/modules/sdk-test-procedure/README.md b/modules/sdk-test-procedure/README.md new file mode 100644 index 00000000000..ea81bc60693 --- /dev/null +++ b/modules/sdk-test-procedure/README.md @@ -0,0 +1,13 @@ +# `sdk-test-procedure` *Rust* test + +This module tests that our client SDKs can invoke procedures and observe their return values. + +It is separate from [`sdk-test`](../sdk-test) because module library support for procedures across languages is was not yet universal as of writing (pgoldman 2025-10-30), and so it was not possible to implement this module in [TypeScript](../sdk-test-ts) and [C#](../sdk-test-cs). + +## How to Run + +Run tests named with `procedure` in the [Rust client SDK test suite](../../sdks/rust/tests/test.rs): + +```sh +cargo test -p spacetimedb-sdk procedure +``` diff --git a/modules/sdk-test-procedure/src/lib.rs b/modules/sdk-test-procedure/src/lib.rs new file mode 100644 index 00000000000..3918a2eec6b --- /dev/null +++ b/modules/sdk-test-procedure/src/lib.rs @@ -0,0 +1,52 @@ +use spacetimedb::{procedure, ProcedureContext, SpacetimeType}; + +#[derive(SpacetimeType)] +struct ReturnStruct { + a: u32, + b: String, +} + +#[derive(SpacetimeType)] +enum ReturnEnum { + A(u32), + B(String), +} + +#[procedure] +fn return_primitive(_ctx: &mut ProcedureContext, lhs: u32, rhs: u32) -> u32 { + lhs + rhs +} + +#[procedure] +fn return_struct(_ctx: &mut ProcedureContext, a: u32, b: String) -> ReturnStruct { + ReturnStruct { a, b } +} + +#[procedure] +fn return_enum_a(_ctx: &mut ProcedureContext, a: u32) -> ReturnEnum { + ReturnEnum::A(a) +} + +#[procedure] +fn return_enum_b(_ctx: &mut ProcedureContext, b: String) -> ReturnEnum { + ReturnEnum::B(b) +} + +#[procedure] +fn will_panic(_ctx: &mut ProcedureContext) { + panic!("This procedure is expected to panic") +} + +// TODO(procedure-http): Add a procedure here which does an HTTP request against a SpacetimeDB route (as `http://localhost:3000/v1/`) +// and returns some value derived from the response. +// Then write a test which invokes it in the Rust client SDK test suite. + +// TODO(procedure-http): Add a procedure here which does an HTTP request against an invalid SpacetimeDB route +// and returns some value derived from the error. +// Then write a test which invokes it in the Rust client SDK test suite. + +// TODO(procedure-tx): Add a procedure here which acquires a transaction, inserts a row, commits, then returns. +// Then write a test which invokes it and asserts observing the row in the Rust client SDK test suite. + +// TODO(procedure-tx): Add a procedure here which acquires a transaction, inserts a row, rolls back, then returns. +// Then write a test which invokes it and asserts not observing the row in the Rust client SDK test suite. diff --git a/modules/sdk-test/README.md b/modules/sdk-test/README.md index 69056f1a0d3..6a76e199ba2 100644 --- a/modules/sdk-test/README.md +++ b/modules/sdk-test/README.md @@ -9,7 +9,7 @@ Called as part of our tests to ensure the system is working as expected. ## How to Run -Execute the tests on `spacetimedb-sdk` at [test.rs](../../crates/sdk/tests/test.rs): +Execute the tests on `spacetimedb-sdk` at [test.rs](../../sdks/rust/tests/test.rs): ```bash # Will run both Rust/C# modules @@ -18,4 +18,4 @@ cargo test -p spacetimedb-sdk cargo test -p spacetimedb-sdk rust # Only C# cargo test -p spacetimedb-sdk csharp -``` \ No newline at end of file +``` diff --git a/sdks/rust/src/callbacks.rs b/sdks/rust/src/callbacks.rs index e8d04f82b09..d5be33e3541 100644 --- a/sdks/rust/src/callbacks.rs +++ b/sdks/rust/src/callbacks.rs @@ -17,6 +17,7 @@ use crate::{ client_cache::TableAppliedDiff, + error::InternalError, spacetime_module::{AbstractEventContext, Reducer, SpacetimeModule}, }; use spacetimedb_data_structures::map::HashMap; @@ -262,3 +263,42 @@ impl ReducerCallbacks { .expect("Attempt to remove non-existent reducer callback"); } } + +/// A procedure callback for a procedure defined by the module `M`. +/// +/// Procedure return values are deserialized within this function by code injected by the SDK. +pub(crate) type ProcedureCallback = + Box::ProcedureEventContext, Result, InternalError>) + Send + 'static>; + +pub struct ProcedureCallbacks { + request_id_to_callback: HashMap>, +} + +impl Default for ProcedureCallbacks { + fn default() -> Self { + Self { + request_id_to_callback: Default::default(), + } + } +} + +impl ProcedureCallbacks { + pub(crate) fn insert(&mut self, request_id: u32, callback: ProcedureCallback) { + if self.request_id_to_callback.insert(request_id, callback).is_some() { + unreachable!("Request IDs are drawn from a global monotonic atomic counter and so are unique"); + }; + } + + pub(crate) fn resolve( + &mut self, + ctx: &::ProcedureEventContext, + request_id: u32, + result: Result, InternalError>, + ) { + let callback = self + .request_id_to_callback + .remove(&request_id) + .expect("Attempting to resolve a non-existent procedure callback"); + callback(ctx, result) + } +} diff --git a/sdks/rust/src/db_connection.rs b/sdks/rust/src/db_connection.rs index b7f507f263b..6ad86a45ba3 100644 --- a/sdks/rust/src/db_connection.rs +++ b/sdks/rust/src/db_connection.rs @@ -19,15 +19,18 @@ //! This module is internal, and may incompatibly change without warning. use crate::{ - callbacks::{CallbackId, DbCallbacks, ReducerCallback, ReducerCallbacks, RowCallback, UpdateCallback}, + Event, ReducerEvent, Status, + __codegen::InternalError, + callbacks::{ + CallbackId, DbCallbacks, ProcedureCallback, ProcedureCallbacks, ReducerCallback, ReducerCallbacks, RowCallback, + UpdateCallback, + }, client_cache::{ClientCache, TableHandle}, spacetime_module::{AbstractEventContext, AppliedDiff, DbConnection, DbUpdate, InModule, SpacetimeModule}, subscription::{ OnAppliedCallback, OnErrorCallback, PendingUnsubscribeResult, SubscriptionHandleImpl, SubscriptionManager, }, websocket::{WsConnection, WsParams}, - Event, ReducerEvent, Status, - __codegen::InternalError, }; use bytes::Bytes; use futures::StreamExt; @@ -36,6 +39,7 @@ use http::Uri; use spacetimedb_client_api_messages::websocket as ws; use spacetimedb_client_api_messages::websocket::{BsatnFormat, CallReducerFlags, Compression}; use spacetimedb_lib::{bsatn, ser::Serialize, ConnectionId, Identity}; +use spacetimedb_sats::Deserialize; use std::{ collections::HashMap, sync::{atomic::AtomicU32, Arc, Mutex as StdMutex, OnceLock}, @@ -226,6 +230,15 @@ impl DbContextImpl { inner.subscriptions.subscription_error(&ctx, query_id); Ok(()) } + ParsedMessage::ProcedureResult { request_id, result } => { + let ctx = self.make_event_ctx(()); + self.inner + .lock() + .unwrap() + .procedure_callbacks + .resolve(&ctx, request_id, result); + Ok(()) + } }; res @@ -357,6 +370,8 @@ impl DbContextImpl { let msg = ws::ClientMessage::CallReducer(ws::CallReducer { reducer: reducer.into(), args: args_bsatn.into(), + // We could call `next_request_id` to get a unique ID to include here, + // but we don't have any use for such an ID, so we don't bother. request_id: 0, flags, }); @@ -369,6 +384,35 @@ impl DbContextImpl { .expect("Unable to send reducer call message: WS sender loop has dropped its recv channel"); } + // Invoke a procedure: stash its callback, then send the `CallProcedure` WS message. + PendingMutation::InvokeProcedureWithCallback { + procedure, + args, + callback, + } => { + // We need to include a request_id in the message so that we can find the callback once it completes. + let request_id = next_request_id(); + self.inner + .lock() + .unwrap() + .procedure_callbacks + .insert(request_id, callback); + + let msg = ws::ClientMessage::CallProcedure(ws::CallProcedure { + procedure: procedure.into(), + args: args.into(), + request_id, + flags: ws::CallProcedureFlags::Default, + }); + self.send_chan + .lock() + .unwrap() + .as_mut() + .ok_or(crate::Error::Disconnected)? + .unbounded_send(msg) + .expect("Unable to send procedure call message: WS sender loop has dropped its recv channel"); + } + // Disconnect: close the connection. PendingMutation::Disconnect => { // Set `send_chan` to `None`, since `Self::is_active` checks that. @@ -699,6 +743,32 @@ impl DbContextImpl { pub fn try_connection_id(&self) -> Option { *self.connection_id.lock().unwrap() } + + pub fn invoke_procedure_with_callback< + Args: Serialize + InModule, + RetVal: for<'a> Deserialize<'a> + 'static, + >( + &self, + procedure_name: &'static str, + args: Args, + callback: impl FnOnce(&::ProcedureEventContext, Result) + + Send + + 'static, + ) { + self.queue_mutation(PendingMutation::InvokeProcedureWithCallback { + procedure: procedure_name, + args: bsatn::to_vec(&args).expect("Failed to BSATN serialize procedure args"), + callback: Box::new(move |ctx, ret| { + callback( + ctx, + ret.map(|ret| { + bsatn::from_slice::(&ret[..]) + .expect("Failed to BSATN deserialize procedure return value") + }), + ) + }), + }); + } } type OnConnectCallback = Box::DbConnection, Identity, &str) + Send + 'static>; @@ -726,6 +796,8 @@ pub(crate) struct DbContextImplInner { on_disconnect: Option>, call_reducer_flags: CallReducerFlagsMap, + + procedure_callbacks: ProcedureCallbacks, } /// Maps reducer names to the flags to use for `.call_reducer(..)`. @@ -856,6 +928,7 @@ but you must call one of them, or else the connection will never progress. let (runtime, handle) = enter_or_create_runtime()?; let db_callbacks = DbCallbacks::default(); let reducer_callbacks = ReducerCallbacks::default(); + let procedure_callbacks = ProcedureCallbacks::default(); let connection_id_override = get_connection_id_override(); let ws_connection = tokio::task::block_in_place(|| { @@ -885,6 +958,7 @@ but you must call one of them, or else the connection will never progress. on_connect_error: self.on_connect_error, on_disconnect: self.on_disconnect, call_reducer_flags: <_>::default(), + procedure_callbacks, })); let mut cache = ClientCache::default(); @@ -1063,13 +1137,29 @@ fn enter_or_create_runtime() -> crate::Result<(Option, runtime::Handle) } enum ParsedMessage { - InitialSubscription { db_update: M::DbUpdate, sub_id: u32 }, + InitialSubscription { + db_update: M::DbUpdate, + sub_id: u32, + }, TransactionUpdate(Event, Option), IdentityToken(Identity, Box, ConnectionId), - SubscribeApplied { query_id: u32, initial_update: M::DbUpdate }, - UnsubscribeApplied { query_id: u32, initial_update: M::DbUpdate }, - SubscriptionError { query_id: Option, error: String }, + SubscribeApplied { + query_id: u32, + initial_update: M::DbUpdate, + }, + UnsubscribeApplied { + query_id: u32, + initial_update: M::DbUpdate, + }, + SubscriptionError { + query_id: Option, + error: String, + }, Error(crate::Error), + ProcedureResult { + request_id: u32, + result: Result, InternalError>, + }, } fn spawn_parse_loop( @@ -1185,7 +1275,14 @@ async fn parse_loop( }, ws::ServerMessage::SubscribeApplied(_) => unreachable!("Rust client SDK never sends `SubscribeSingle`, but received a `SubscribeApplied` from the host... huh?"), ws::ServerMessage::UnsubscribeApplied(_) => unreachable!("Rust client SDK never sends `UnsubscribeSingle`, but received a `UnsubscribeApplied` from the host... huh?"), - ws::ServerMessage::ProcedureResult(_) => todo!("Rust client SDK procedure support"), + ws::ServerMessage::ProcedureResult(procedure_result) => ParsedMessage::ProcedureResult { + request_id: procedure_result.request_id, + result: match procedure_result.status { + ws::ProcedureStatus::InternalError(msg) => Err(InternalError::new(msg)), + ws::ProcedureStatus::OutOfEnergy => Err(InternalError::new("Procedure execution aborted due to insufficient energy")), + ws::ProcedureStatus::Returned(val) => Ok(val), + } + }, }) .expect("Failed to send ParsedMessage to main thread"); } @@ -1252,6 +1349,11 @@ pub(crate) enum PendingMutation { reducer: &'static str, flags: CallReducerFlags, }, + InvokeProcedureWithCallback { + procedure: &'static str, + args: Vec, + callback: ProcedureCallback, + }, } enum Message { diff --git a/sdks/rust/src/db_context.rs b/sdks/rust/src/db_context.rs index 02da901c984..718dfe4b98d 100644 --- a/sdks/rust/src/db_context.rs +++ b/sdks/rust/src/db_context.rs @@ -27,6 +27,16 @@ pub trait DbContext { /// so accesses to concrete-typed contexts don't need to use this method. fn reducers(&self) -> &Self::Reducers; + type Procedures; + + /// Access to procedures defined by the module. + /// + /// The returned `Procedures` will have a method to invoke each procedure defined by the module. + /// + /// `DbConnection` and `EventContext` also have a public field `procedures`, + /// so accesses to concrete-typed contexts don't need to use this method. + fn procedures(&self) -> &Self::Procedures; + type SetReducerFlags; /// Access to setters for per-reducer flags. diff --git a/sdks/rust/src/lib.rs b/sdks/rust/src/lib.rs index 07be8ecc7fb..8e4dcefaf8a 100644 --- a/sdks/rust/src/lib.rs +++ b/sdks/rust/src/lib.rs @@ -54,8 +54,8 @@ pub mod __codegen { pub use crate::error::{Error, InternalError, Result}; pub use crate::spacetime_module::{ parse_reducer_args, AbstractEventContext, AppliedDiff, DbConnection, DbUpdate, ErrorContext, EventContext, - InModule, Reducer, ReducerEventContext, SpacetimeModule, SubscriptionEventContext, SubscriptionHandle, - TableUpdate, + InModule, ProcedureEventContext, Reducer, ReducerEventContext, SpacetimeModule, SubscriptionEventContext, + SubscriptionHandle, TableUpdate, }; pub use crate::subscription::{OnEndedCallback, SubscriptionBuilder, SubscriptionHandleImpl}; pub use crate::{ diff --git a/sdks/rust/src/spacetime_module.rs b/sdks/rust/src/spacetime_module.rs index 8b795d7b426..7b8e3fba176 100644 --- a/sdks/rust/src/spacetime_module.rs +++ b/sdks/rust/src/spacetime_module.rs @@ -35,6 +35,9 @@ pub trait SpacetimeModule: Send + Sync + 'static { /// [`crate::DbContext`] implementor passed to reducer callbacks. type ReducerEventContext: ReducerEventContext; + /// [`crate::DbContext`] implementor passed to procedure callbacks. + type ProcedureEventContext: ProcedureEventContext; + /// [`crate::DbContext`] implementor passed to subscription on-applied and on-removed callbacks. type SubscriptionEventContext: SubscriptionEventContext; @@ -139,6 +142,12 @@ where { } +pub trait ProcedureEventContext: AbstractEventContext +where + Self::Module: SpacetimeModule, +{ +} + /// [`AbstractEventContext`] subtrait for subscription applied and removed callbacks. pub trait SubscriptionEventContext: AbstractEventContext where diff --git a/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs b/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs index 4c763beb353..6b3edda4f10 100644 --- a/sdks/rust/tests/connect_disconnect_client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/connect_disconnect_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.6.0 (commit a952ba57372dfbee37da9d079a3f86704ca35611). +// This was generated using spacetimedb cli version 1.6.0 (commit 3f1de9e09651bc412de3cb9daf49cc553ebb81e8). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; @@ -45,6 +45,7 @@ impl __sdk::Reducer for Reducer { match self { Reducer::IdentityConnected => "identity_connected", Reducer::IdentityDisconnected => "identity_disconnected", + _ => unreachable!(), } } } @@ -119,6 +120,7 @@ impl __sdk::DbUpdate for DbUpdate { pub struct AppliedDiff<'r> { connected: __sdk::TableAppliedDiff<'r, Connected>, disconnected: __sdk::TableAppliedDiff<'r, Disconnected>, + __unused: std::marker::PhantomData<&'r ()>, } impl __sdk::InModule for AppliedDiff<'_> { @@ -149,6 +151,16 @@ 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; +} + #[doc(hidden)] /// The `set_reducer_flags` field of [`DbConnection`], /// with methods provided by extension traits for each reducer defined by the module. @@ -201,6 +213,9 @@ pub struct DbConnection { /// 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, } @@ -211,6 +226,7 @@ impl __sdk::InModule for DbConnection { impl __sdk::DbContext for DbConnection { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -219,6 +235,9 @@ impl __sdk::DbContext for DbConnection { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -324,6 +343,7 @@ impl __sdk::DbConnection for DbConnection { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } @@ -404,6 +424,8 @@ pub struct EventContext { /// /// 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. pub event: __sdk::Event, imp: __sdk::DbContextImpl, @@ -419,6 +441,7 @@ impl __sdk::AbstractEventContext for EventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -432,6 +455,7 @@ impl __sdk::InModule for EventContext { impl __sdk::DbContext for EventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -440,6 +464,9 @@ impl __sdk::DbContext for EventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -483,6 +510,8 @@ pub struct ReducerEventContext { /// /// 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. pub event: __sdk::ReducerEvent, imp: __sdk::DbContextImpl, @@ -498,6 +527,7 @@ impl __sdk::AbstractEventContext for ReducerEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -511,6 +541,7 @@ impl __sdk::InModule for ReducerEventContext { impl __sdk::DbContext for ReducerEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -519,6 +550,9 @@ impl __sdk::DbContext for ReducerEventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -550,6 +584,88 @@ impl __sdk::DbContext for ReducerEventContext { 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 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, +} + +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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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`]. @@ -561,6 +677,8 @@ pub struct SubscriptionEventContext { /// /// 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, } @@ -573,6 +691,7 @@ impl __sdk::AbstractEventContext for SubscriptionEventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } @@ -586,6 +705,7 @@ impl __sdk::InModule for SubscriptionEventContext { impl __sdk::DbContext for SubscriptionEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -594,6 +714,9 @@ impl __sdk::DbContext for SubscriptionEventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -637,6 +760,8 @@ pub struct ErrorContext { /// /// 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. pub event: Option<__sdk::Error>, imp: __sdk::DbContextImpl, @@ -652,6 +777,7 @@ impl __sdk::AbstractEventContext for ErrorContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -665,6 +791,7 @@ impl __sdk::InModule for ErrorContext { impl __sdk::DbContext for ErrorContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -673,6 +800,9 @@ impl __sdk::DbContext for ErrorContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -708,6 +838,7 @@ 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; diff --git a/sdks/rust/tests/procedure-client/Cargo.toml b/sdks/rust/tests/procedure-client/Cargo.toml new file mode 100644 index 00000000000..615bba2f39f --- /dev/null +++ b/sdks/rust/tests/procedure-client/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "procedure-client" +version.workspace = true +edition.workspace = true +license-file = "LICENSE" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[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/procedure-client/LICENSE b/sdks/rust/tests/procedure-client/LICENSE new file mode 120000 index 00000000000..424c4c33df2 --- /dev/null +++ b/sdks/rust/tests/procedure-client/LICENSE @@ -0,0 +1 @@ +../../../../licenses/BSL.txt \ No newline at end of file diff --git a/sdks/rust/tests/procedure-client/README.md b/sdks/rust/tests/procedure-client/README.md new file mode 100644 index 00000000000..3f8ef6ee17d --- /dev/null +++ b/sdks/rust/tests/procedure-client/README.md @@ -0,0 +1,23 @@ +This test client is used with two modules: + +- [`sdk-test-connect-disconnect`](/modules/sdk-test-connect-disconnect) +- [`sdk-test-connect-disconnect-cs`](/modules/sdk-test-connect-disconnect-cs) + +Currently, the bindings are generated using only one of those two modules, +chosen arbitrarily on each test run. +The two tests which use this client, +`connect_disconnect_callbacks` and `connect_disconnect_callbacks_csharp`, +are not intended to test code generation. + +The goal of the two tests is to verify that module-side `connect` and `disconnect` events +fire when an SDK connects or disconnects via WebSocket, +and that the client can observe mutations performed by those events. + +To (re-)generate the `module_bindings`, from this directory, run: + +```sh +mkdir -p src/module_bindings +spacetime generate --lang rust \ + --out-dir src/module_bindings \ + --project-path ../../../../modules/sdk-test-connect-disconnect +``` diff --git a/sdks/rust/tests/procedure-client/src/main.rs b/sdks/rust/tests/procedure-client/src/main.rs new file mode 100644 index 00000000000..f3deb565fab --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/main.rs @@ -0,0 +1,144 @@ +mod module_bindings; + +use module_bindings::*; + +use anyhow::Context; +use spacetimedb_sdk::DbConnectionBuilder; +use test_counter::TestCounter; + +const LOCALHOST: &str = "http://localhost:3000"; + +/// 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() { + // The default panic hook is responsible for printing the panic message and backtrace to stderr. + // Grab a handle on it, and invoke it in our custom hook before exiting. + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |panic_info| { + // Print panic information + default_hook(panic_info); + + // Exit the process with a non-zero code to denote failure. + std::process::exit(1); + })); +} + +fn db_name_or_panic() -> String { + std::env::var("SPACETIME_SDK_TEST_DB_NAME").expect("Failed to read db name from env") +} + +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 { + "procedure-return-values" => exec_procedure_return_values(), + "procedure-observe-panic" => exec_procedure_panic(), + _ => panic!("Unknown test: {test}"), + } +} + +fn connect_with_then( + test_counter: &std::sync::Arc, + on_connect_suffix: &str, + with_builder: impl FnOnce(DbConnectionBuilder) -> DbConnectionBuilder, + callback: impl FnOnce(&DbConnection) + Send + 'static, +) -> DbConnection { + let connected_result = test_counter.add_test(format!("on_connect_{on_connect_suffix}")); + let name = db_name_or_panic(); + let builder = 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:?}")); + let conn = with_builder(builder).build().unwrap(); + conn.run_threaded(); + conn +} + +fn connect_then( + test_counter: &std::sync::Arc, + callback: impl FnOnce(&DbConnection) + Send + 'static, +) -> DbConnection { + connect_with_then(test_counter, "", |x| x, callback) +} + +fn exec_procedure_return_values() { + let test_counter = TestCounter::new(); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + let return_primitive_result = test_counter.add_test("return_primitive"); + let return_struct_result = test_counter.add_test("return_struct"); + let return_enum_a_result = test_counter.add_test("return_enum_a"); + let return_enum_b_result = test_counter.add_test("return_enum_b"); + + ctx.procedures.return_primitive_then(1, 2, move |_, res| { + return_primitive_result(res.context("return_primtive failed unexpectedly").and_then(|sum| { + if sum == 3 { + Ok(()) + } else { + Err(anyhow::anyhow!( + "Expected return value from return_primitive of 3 but got {sum}" + )) + } + })); + }); + ctx.procedures + .return_struct_then(1234, "foo".to_string(), move |_, res| { + return_struct_result(res.context("return_struct failed unexpectedly").and_then(|strukt| { + anyhow::ensure!(strukt.a == 1234); + anyhow::ensure!(&*strukt.b == "foo"); + Ok(()) + })); + }); + ctx.procedures.return_enum_a_then(1234, move |_, res| { + return_enum_a_result(res.context("return_enum_a failed unexpectedly").and_then(|enum_a| { + anyhow::ensure!(matches!(enum_a, ReturnEnum::A(1234))); + Ok(()) + })); + }); + ctx.procedures.return_enum_b_then("foo".to_string(), move |_, res| { + return_enum_b_result(res.context("return_enum_b failed unexpectedly").and_then(|enum_b| { + let ReturnEnum::B(string) = enum_b else { + anyhow::bail!("Unexpected variant for returned enum {enum_b:?}"); + }; + anyhow::ensure!(&*string == "foo"); + Ok(()) + })); + }); + } + }); + + test_counter.wait_for_all(); +} + +fn exec_procedure_panic() { + let test_counter = TestCounter::new(); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + let will_panic_result = test_counter.add_test("will_panic"); + + ctx.procedures.will_panic_then(move |_, res| { + will_panic_result(if res.is_err() { + Ok(()) + } else { + Err(anyhow::anyhow!("Expected failure but got Ok... huh? {res:?}")) + }); + }); + } + }); + + test_counter.wait_for_all(); +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/mod.rs b/sdks/rust/tests/procedure-client/src/module_bindings/mod.rs new file mode 100644 index 00000000000..c0afac8f44a --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/mod.rs @@ -0,0 +1,817 @@ +// 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.6.0 (commit 3f1de9e09651bc412de3cb9daf49cc553ebb81e8). + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +pub mod return_enum_a_procedure; +pub mod return_enum_b_procedure; +pub mod return_enum_type; +pub mod return_primitive_procedure; +pub mod return_struct_procedure; +pub mod return_struct_type; +pub mod will_panic_procedure; + +pub use return_enum_a_procedure::return_enum_a; +pub use return_enum_b_procedure::return_enum_b; +pub use return_enum_type::ReturnEnum; +pub use return_primitive_procedure::return_primitive; +pub use return_struct_procedure::return_struct; +pub use return_struct_type::ReturnStruct; +pub use will_panic_procedure::will_panic; + +#[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 {} + +impl __sdk::InModule for Reducer { + type Module = RemoteModule; +} + +impl __sdk::Reducer for Reducer { + fn reducer_name(&self) -> &'static str { + match self { + _ => 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[..] { + unknown => Err(__sdk::InternalError::unknown_name("reducer", unknown, "ReducerCallInfo").into()), + } + } +} + +#[derive(Default)] +#[allow(non_snake_case)] +#[doc(hidden)] +pub struct DbUpdate {} + +impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { + type Error = __sdk::Error; + fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result { + let mut db_update = DbUpdate::default(); + for table_update in raw.tables { + match &table_update.table_name[..] { + 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 + } +} + +#[derive(Default)] +#[allow(non_snake_case)] +#[doc(hidden)] +pub struct AppliedDiff<'r> { + __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) {} +} + +#[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; +} + +#[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 { + 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 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, +} + +impl __sdk::InModule for DbConnection { + type Module = RemoteModule; +} + +impl __sdk::DbContext for DbConnection { + type DbView = RemoteTables; + type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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() }, + set_reducer_flags: SetReducerFlags { 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, + SetReducerFlags = SetReducerFlags, + SubscriptionBuilder = __sdk::SubscriptionBuilder, +> +{ +} +impl< + Ctx: __sdk::DbContext< + DbView = RemoteTables, + Reducers = RemoteReducers, + SetReducerFlags = SetReducerFlags, + 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 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. + 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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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 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. + 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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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 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, +} + +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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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 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, +} + +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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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 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. + 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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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 SetReducerFlags = SetReducerFlags; + type DbUpdate = DbUpdate; + type AppliedDiff<'r> = AppliedDiff<'r>; + type SubscriptionHandle = SubscriptionHandle; + + fn register_tables(client_cache: &mut __sdk::ClientCache) {} +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_a_procedure.rs b/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_a_procedure.rs new file mode 100644 index 00000000000..3ba17d4764c --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_a_procedure.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}; + +use super::return_enum_type::ReturnEnum; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct ReturnEnumAArgs { + pub a: u32, +} + +impl __sdk::InModule for ReturnEnumAArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `return_enum_a`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait return_enum_a { + fn return_enum_a(&self, a: u32) { + self.return_enum_a_then(a, |_, _| {}); + } + + fn return_enum_a_then( + &self, + a: u32, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ); +} + +impl return_enum_a for super::RemoteProcedures { + fn return_enum_a_then( + &self, + a: u32, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, ReturnEnum>("return_enum_a", ReturnEnumAArgs { a }, __callback); + } +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_b_procedure.rs b/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_b_procedure.rs new file mode 100644 index 00000000000..d8729f8b974 --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_b_procedure.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}; + +use super::return_enum_type::ReturnEnum; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct ReturnEnumBArgs { + pub b: String, +} + +impl __sdk::InModule for ReturnEnumBArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `return_enum_b`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait return_enum_b { + fn return_enum_b(&self, b: String) { + self.return_enum_b_then(b, |_, _| {}); + } + + fn return_enum_b_then( + &self, + b: String, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ); +} + +impl return_enum_b for super::RemoteProcedures { + fn return_enum_b_then( + &self, + b: String, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, ReturnEnum>("return_enum_b", ReturnEnumBArgs { b }, __callback); + } +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_type.rs b/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_type.rs new file mode 100644 index 00000000000..a9db98ac1b8 --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/return_enum_type.rs @@ -0,0 +1,17 @@ +// 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 enum ReturnEnum { + A(u32), + + B(String), +} + +impl __sdk::InModule for ReturnEnum { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/return_primitive_procedure.rs b/sdks/rust/tests/procedure-client/src/module_bindings/return_primitive_procedure.rs new file mode 100644 index 00000000000..07b29c2bafb --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/return_primitive_procedure.rs @@ -0,0 +1,50 @@ +// 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)] +struct ReturnPrimitiveArgs { + pub lhs: u32, + pub rhs: u32, +} + +impl __sdk::InModule for ReturnPrimitiveArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `return_primitive`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait return_primitive { + fn return_primitive(&self, lhs: u32, rhs: u32) { + self.return_primitive_then(lhs, rhs, |_, _| {}); + } + + fn return_primitive_then( + &self, + lhs: u32, + rhs: u32, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ); +} + +impl return_primitive for super::RemoteProcedures { + fn return_primitive_then( + &self, + lhs: u32, + rhs: u32, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ) { + self.imp.invoke_procedure_with_callback::<_, u32>( + "return_primitive", + ReturnPrimitiveArgs { lhs, rhs }, + __callback, + ); + } +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/return_struct_procedure.rs b/sdks/rust/tests/procedure-client/src/module_bindings/return_struct_procedure.rs new file mode 100644 index 00000000000..f09fa54f6b4 --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/return_struct_procedure.rs @@ -0,0 +1,52 @@ +// 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::return_struct_type::ReturnStruct; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +struct ReturnStructArgs { + pub a: u32, + pub b: String, +} + +impl __sdk::InModule for ReturnStructArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `return_struct`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait return_struct { + fn return_struct(&self, a: u32, b: String) { + self.return_struct_then(a, b, |_, _| {}); + } + + fn return_struct_then( + &self, + a: u32, + b: String, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ); +} + +impl return_struct for super::RemoteProcedures { + fn return_struct_then( + &self, + a: u32, + b: String, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result) + Send + 'static, + ) { + self.imp.invoke_procedure_with_callback::<_, ReturnStruct>( + "return_struct", + ReturnStructArgs { a, b }, + __callback, + ); + } +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/return_struct_type.rs b/sdks/rust/tests/procedure-client/src/module_bindings/return_struct_type.rs new file mode 100644 index 00000000000..ab1a355fdaf --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/return_struct_type.rs @@ -0,0 +1,16 @@ +// 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 ReturnStruct { + pub a: u32, + pub b: String, +} + +impl __sdk::InModule for ReturnStruct { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/procedure-client/src/module_bindings/will_panic_procedure.rs b/sdks/rust/tests/procedure-client/src/module_bindings/will_panic_procedure.rs new file mode 100644 index 00000000000..21f4d4658fc --- /dev/null +++ b/sdks/rust/tests/procedure-client/src/module_bindings/will_panic_procedure.rs @@ -0,0 +1,40 @@ +// 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)] +struct WillPanicArgs {} + +impl __sdk::InModule for WillPanicArgs { + type Module = super::RemoteModule; +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the procedure `will_panic`. +/// +/// Implemented for [`super::RemoteProcedures`]. +pub trait will_panic { + fn will_panic(&self) { + self.will_panic_then(|_, _| {}); + } + + fn will_panic_then( + &self, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result<(), __sdk::InternalError>) + Send + 'static, + ); +} + +impl will_panic for super::RemoteProcedures { + fn will_panic_then( + &self, + + __callback: impl FnOnce(&super::ProcedureEventContext, Result<(), __sdk::InternalError>) + Send + 'static, + ) { + self.imp + .invoke_procedure_with_callback::<_, ()>("will_panic", WillPanicArgs {}, __callback); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/mod.rs b/sdks/rust/tests/test-client/src/module_bindings/mod.rs index 234638a8f28..5957aee2af3 100644 --- a/sdks/rust/tests/test-client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/test-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.6.0 (commit a952ba57372dfbee37da9d079a3f86704ca35611). +// This was generated using spacetimedb cli version 1.6.0 (commit 3f1de9e09651bc412de3cb9daf49cc553ebb81e8). #![allow(unused, clippy::all)] use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; @@ -1775,6 +1775,7 @@ impl __sdk::Reducer for Reducer { Reducer::UpdateUniqueU32 { .. } => "update_unique_u32", Reducer::UpdateUniqueU64 { .. } => "update_unique_u64", Reducer::UpdateUniqueU8 { .. } => "update_unique_u8", + _ => unreachable!(), } } } @@ -3444,6 +3445,7 @@ pub struct AppliedDiff<'r> { vec_u_64: __sdk::TableAppliedDiff<'r, VecU64>, vec_u_8: __sdk::TableAppliedDiff<'r, VecU8>, vec_unit_struct: __sdk::TableAppliedDiff<'r, VecUnitStruct>, + __unused: std::marker::PhantomData<&'r ()>, } impl __sdk::InModule for AppliedDiff<'_> { @@ -3610,6 +3612,16 @@ 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; +} + #[doc(hidden)] /// The `set_reducer_flags` field of [`DbConnection`], /// with methods provided by extension traits for each reducer defined by the module. @@ -3662,6 +3674,9 @@ pub struct DbConnection { /// 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, } @@ -3672,6 +3687,7 @@ impl __sdk::InModule for DbConnection { impl __sdk::DbContext for DbConnection { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -3680,6 +3696,9 @@ impl __sdk::DbContext for DbConnection { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -3785,6 +3804,7 @@ impl __sdk::DbConnection for DbConnection { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } @@ -3865,6 +3885,8 @@ pub struct EventContext { /// /// 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. pub event: __sdk::Event, imp: __sdk::DbContextImpl, @@ -3880,6 +3902,7 @@ impl __sdk::AbstractEventContext for EventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -3893,6 +3916,7 @@ impl __sdk::InModule for EventContext { impl __sdk::DbContext for EventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -3901,6 +3925,9 @@ impl __sdk::DbContext for EventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -3944,6 +3971,8 @@ pub struct ReducerEventContext { /// /// 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. pub event: __sdk::ReducerEvent, imp: __sdk::DbContextImpl, @@ -3959,6 +3988,7 @@ impl __sdk::AbstractEventContext for ReducerEventContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -3972,6 +4002,7 @@ impl __sdk::InModule for ReducerEventContext { impl __sdk::DbContext for ReducerEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -3980,6 +4011,9 @@ impl __sdk::DbContext for ReducerEventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -4011,6 +4045,88 @@ impl __sdk::DbContext for ReducerEventContext { 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 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, +} + +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() }, + set_reducer_flags: SetReducerFlags { 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; + type SetReducerFlags = SetReducerFlags; + + fn db(&self) -> &Self::DbView { + &self.db + } + fn reducers(&self) -> &Self::Reducers { + &self.reducers + } + 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() + } + + 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`]. @@ -4022,6 +4138,8 @@ pub struct SubscriptionEventContext { /// /// 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, } @@ -4034,6 +4152,7 @@ impl __sdk::AbstractEventContext for SubscriptionEventContext { Self { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, imp, } @@ -4047,6 +4166,7 @@ impl __sdk::InModule for SubscriptionEventContext { impl __sdk::DbContext for SubscriptionEventContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -4055,6 +4175,9 @@ impl __sdk::DbContext for SubscriptionEventContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -4098,6 +4221,8 @@ pub struct ErrorContext { /// /// 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. pub event: Option<__sdk::Error>, imp: __sdk::DbContextImpl, @@ -4113,6 +4238,7 @@ impl __sdk::AbstractEventContext for ErrorContext { db: RemoteTables { imp: imp.clone() }, reducers: RemoteReducers { imp: imp.clone() }, set_reducer_flags: SetReducerFlags { imp: imp.clone() }, + procedures: RemoteProcedures { imp: imp.clone() }, event, imp, } @@ -4126,6 +4252,7 @@ impl __sdk::InModule for ErrorContext { impl __sdk::DbContext for ErrorContext { type DbView = RemoteTables; type Reducers = RemoteReducers; + type Procedures = RemoteProcedures; type SetReducerFlags = SetReducerFlags; fn db(&self) -> &Self::DbView { @@ -4134,6 +4261,9 @@ impl __sdk::DbContext for ErrorContext { fn reducers(&self) -> &Self::Reducers { &self.reducers } + fn procedures(&self) -> &Self::Procedures { + &self.procedures + } fn set_reducer_flags(&self) -> &Self::SetReducerFlags { &self.set_reducer_flags } @@ -4169,6 +4299,7 @@ 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; diff --git a/sdks/rust/tests/test.rs b/sdks/rust/tests/test.rs index 093a45eb20a..cabac445f8a 100644 --- a/sdks/rust/tests/test.rs +++ b/sdks/rust/tests/test.rs @@ -268,3 +268,37 @@ declare_tests_with_suffix!(rust, ""); declare_tests_with_suffix!(typescript, "-ts"); // TODO: migrate csharp to snake_case table names declare_tests_with_suffix!(csharp, "-cs"); + +mod procedure { + //! Tests of procedure functionality, using <./procedure_client> and <../../../modules/sdk-test-procedure>. + //! + //! These are separate from the existing client and module because as of writing (pgoldman 2025-10-30), + //! we do not have procedure support in all of the module languages we have tested. + + use spacetimedb_testing::sdk::Test; + + const MODULE: &str = "sdk-test-procedure"; + const CLIENT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/procedure-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 return_values() { + make_test("procedure-return-values").run() + } + + #[test] + fn observe_panic() { + make_test("procedure-observe-panic").run() + } +}