spacetimedb_cli/subcommands/generate/
rust.rs

1use super::code_indenter::{CodeIndenter, Indenter};
2use super::util::{collect_case, iter_reducers, print_lines, type_ref_name};
3use super::Lang;
4use crate::detect::{has_rust_fmt, has_rust_up};
5use crate::generate::util::{iter_tables, iter_types, iter_unique_cols, print_auto_generated_file_comment};
6use anyhow::Context;
7use convert_case::{Case, Casing};
8use duct::cmd;
9use spacetimedb_lib::sats::AlgebraicTypeRef;
10use spacetimedb_schema::def::{ModuleDef, ReducerDef, ScopedTypeName, TableDef, TypeDef};
11use spacetimedb_schema::identifier::Identifier;
12use spacetimedb_schema::schema::{Schema, TableSchema};
13use spacetimedb_schema::type_for_generate::{AlgebraicTypeDef, AlgebraicTypeUse, PrimitiveType};
14use std::collections::BTreeSet;
15use std::fmt::{self, Write};
16use std::ops::Deref;
17use std::path::PathBuf;
18
19/// Pairs of (module_name, TypeName).
20type Imports = BTreeSet<AlgebraicTypeRef>;
21
22const INDENT: &str = "    ";
23
24pub struct Rust;
25
26impl Lang for Rust {
27    fn table_filename(
28        &self,
29        _module: &spacetimedb_schema::def::ModuleDef,
30        table: &spacetimedb_schema::def::TableDef,
31    ) -> String {
32        table_module_name(&table.name) + ".rs"
33    }
34
35    fn type_filename(&self, type_name: &ScopedTypeName) -> String {
36        type_module_name(type_name) + ".rs"
37    }
38
39    fn reducer_filename(&self, reducer_name: &Identifier) -> String {
40        reducer_module_name(reducer_name) + ".rs"
41    }
42
43    fn format_files(&self, generated_files: BTreeSet<PathBuf>) -> anyhow::Result<()> {
44        if !has_rust_fmt() {
45            if has_rust_up() {
46                cmd!("rustup", "component", "add", "rustfmt")
47                    .run()
48                    .context("Failed to install rustfmt with Rustup")?;
49            } else {
50                anyhow::bail!("rustfmt is not installed. Please install it.");
51            }
52        }
53        cmd!("rustfmt", "--edition", "2021")
54            .before_spawn(move |cmd| {
55                cmd.args(&generated_files);
56                Ok(())
57            })
58            .run()?;
59        Ok(())
60    }
61
62    fn generate_type(&self, module: &ModuleDef, typ: &TypeDef) -> String {
63        let type_name = collect_case(Case::Pascal, typ.name.name_segments());
64
65        let mut output = CodeIndenter::new(String::new(), INDENT);
66        let out = &mut output;
67
68        print_file_header(out);
69        out.newline();
70
71        match &module.typespace_for_generate()[typ.ty] {
72            AlgebraicTypeDef::Product(product) => {
73                gen_and_print_imports(module, out, &product.elements, &[typ.ty]);
74                out.newline();
75                define_struct_for_product(module, out, &type_name, &product.elements, "pub");
76            }
77            AlgebraicTypeDef::Sum(sum) => {
78                gen_and_print_imports(module, out, &sum.variants, &[typ.ty]);
79                out.newline();
80                define_enum_for_sum(module, out, &type_name, &sum.variants, false);
81            }
82            AlgebraicTypeDef::PlainEnum(plain_enum) => {
83                let variants = plain_enum
84                    .variants
85                    .iter()
86                    .cloned()
87                    .map(|var| (var, AlgebraicTypeUse::Unit))
88                    .collect::<Vec<_>>();
89                define_enum_for_sum(module, out, &type_name, &variants, true);
90            }
91        }
92        out.newline();
93
94        writeln!(
95            out,
96            "
97impl __sdk::InModule for {type_name} {{
98    type Module = super::RemoteModule;
99}}
100",
101        );
102
103        output.into_inner()
104    }
105    fn generate_table(&self, module: &ModuleDef, table: &TableDef) -> String {
106        let schema = TableSchema::from_module_def(module, table, (), 0.into())
107            .validated()
108            .expect("Failed to generate table due to validation errors");
109
110        let type_ref = table.product_type_ref;
111
112        let mut output = CodeIndenter::new(String::new(), INDENT);
113        let out = &mut output;
114
115        print_file_header(out);
116
117        let row_type = type_ref_name(module, type_ref);
118        let row_type_module = type_ref_module_name(module, type_ref);
119
120        writeln!(out, "use super::{row_type_module}::{row_type};");
121
122        let product_def = module.typespace_for_generate()[type_ref].as_product().unwrap();
123
124        // Import the types of all fields.
125        // We only need to import fields which have indices or unique constraints,
126        // but it's easier to just import all of 'em, since we have `#![allow(unused)]` anyway.
127        gen_and_print_imports(
128            module,
129            out,
130            &product_def.elements,
131            &[], // No need to skip any imports; we're not defining a type, so there's no chance of circular imports.
132        );
133
134        let table_name = table.name.deref();
135        let table_name_pascalcase = table.name.deref().to_case(Case::Pascal);
136        let table_handle = table_name_pascalcase.clone() + "TableHandle";
137        let insert_callback_id = table_name_pascalcase.clone() + "InsertCallbackId";
138        let delete_callback_id = table_name_pascalcase.clone() + "DeleteCallbackId";
139        let accessor_trait = table_access_trait_name(&table.name);
140        let accessor_method = table_method_name(&table.name);
141
142        write!(
143            out,
144            "
145/// Table handle for the table `{table_name}`.
146///
147/// Obtain a handle from the [`{accessor_trait}::{accessor_method}`] method on [`super::RemoteTables`],
148/// like `ctx.db.{accessor_method}()`.
149///
150/// Users are encouraged not to explicitly reference this type,
151/// but to directly chain method calls,
152/// like `ctx.db.{accessor_method}().on_insert(...)`.
153pub struct {table_handle}<'ctx> {{
154    imp: __sdk::TableHandle<{row_type}>,
155    ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
156}}
157
158#[allow(non_camel_case_types)]
159/// Extension trait for access to the table `{table_name}`.
160///
161/// Implemented for [`super::RemoteTables`].
162pub trait {accessor_trait} {{
163    #[allow(non_snake_case)]
164    /// Obtain a [`{table_handle}`], which mediates access to the table `{table_name}`.
165    fn {accessor_method}(&self) -> {table_handle}<'_>;
166}}
167
168impl {accessor_trait} for super::RemoteTables {{
169    fn {accessor_method}(&self) -> {table_handle}<'_> {{
170        {table_handle} {{
171            imp: self.imp.get_table::<{row_type}>({table_name:?}),
172            ctx: std::marker::PhantomData,
173        }}
174    }}
175}}
176
177pub struct {insert_callback_id}(__sdk::CallbackId);
178pub struct {delete_callback_id}(__sdk::CallbackId);
179
180impl<'ctx> __sdk::Table for {table_handle}<'ctx> {{
181    type Row = {row_type};
182    type EventContext = super::EventContext;
183
184    fn count(&self) -> u64 {{ self.imp.count() }}
185    fn iter(&self) -> impl Iterator<Item = {row_type}> + '_ {{ self.imp.iter() }}
186
187    type InsertCallbackId = {insert_callback_id};
188
189    fn on_insert(
190        &self,
191        callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
192    ) -> {insert_callback_id} {{
193        {insert_callback_id}(self.imp.on_insert(Box::new(callback)))
194    }}
195
196    fn remove_on_insert(&self, callback: {insert_callback_id}) {{
197        self.imp.remove_on_insert(callback.0)
198    }}
199
200    type DeleteCallbackId = {delete_callback_id};
201
202    fn on_delete(
203        &self,
204        callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
205    ) -> {delete_callback_id} {{
206        {delete_callback_id}(self.imp.on_delete(Box::new(callback)))
207    }}
208
209    fn remove_on_delete(&self, callback: {delete_callback_id}) {{
210        self.imp.remove_on_delete(callback.0)
211    }}
212}}
213"
214        );
215
216        out.delimited_block(
217            "
218#[doc(hidden)]
219pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
220",
221            |out| {
222                writeln!(out, "let _table = client_cache.get_or_make_table::<{row_type}>({table_name:?});");
223                for (unique_field_ident, unique_field_type_use) in iter_unique_cols(module.typespace_for_generate(), &schema, product_def) {
224                    let unique_field_name = unique_field_ident.deref().to_case(Case::Snake);
225                    let unique_field_type = type_name(module, unique_field_type_use);
226                    writeln!(
227                        out,
228                        "_table.add_unique_constraint::<{unique_field_type}>({unique_field_name:?}, |row| &row.{unique_field_name});",
229                    );
230                }
231            },
232            "}",
233        );
234
235        if schema.pk().is_some() {
236            let update_callback_id = table_name_pascalcase.clone() + "UpdateCallbackId";
237            write!(
238                out,
239                "
240pub struct {update_callback_id}(__sdk::CallbackId);
241
242impl<'ctx> __sdk::TableWithPrimaryKey for {table_handle}<'ctx> {{
243    type UpdateCallbackId = {update_callback_id};
244
245    fn on_update(
246        &self,
247        callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
248    ) -> {update_callback_id} {{
249        {update_callback_id}(self.imp.on_update(Box::new(callback)))
250    }}
251
252    fn remove_on_update(&self, callback: {update_callback_id}) {{
253        self.imp.remove_on_update(callback.0)
254    }}
255}}
256"
257            );
258        }
259
260        out.newline();
261
262        write!(
263            out,
264            "
265#[doc(hidden)]
266pub(super) fn parse_table_update(
267    raw_updates: __ws::TableUpdate<__ws::BsatnFormat>,
268) -> __sdk::Result<__sdk::TableUpdate<{row_type}>> {{
269    __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {{
270        __sdk::InternalError::failed_parse(
271            \"TableUpdate<{row_type}>\",
272            \"TableUpdate\",
273        ).with_cause(e).into()
274    }})
275}}
276"
277        );
278
279        for (unique_field_ident, unique_field_type_use) in
280            iter_unique_cols(module.typespace_for_generate(), &schema, product_def)
281        {
282            let unique_field_name = unique_field_ident.deref().to_case(Case::Snake);
283            let unique_field_name_pascalcase = unique_field_name.to_case(Case::Pascal);
284
285            let unique_constraint = table_name_pascalcase.clone() + &unique_field_name_pascalcase + "Unique";
286            let unique_field_type = type_name(module, unique_field_type_use);
287
288            write!(
289                out,
290                "
291        /// Access to the `{unique_field_name}` unique index on the table `{table_name}`,
292        /// which allows point queries on the field of the same name
293        /// via the [`{unique_constraint}::find`] method.
294        ///
295        /// Users are encouraged not to explicitly reference this type,
296        /// but to directly chain method calls,
297        /// like `ctx.db.{accessor_method}().{unique_field_name}().find(...)`.
298        pub struct {unique_constraint}<'ctx> {{
299            imp: __sdk::UniqueConstraintHandle<{row_type}, {unique_field_type}>,
300            phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
301        }}
302
303        impl<'ctx> {table_handle}<'ctx> {{
304            /// Get a handle on the `{unique_field_name}` unique index on the table `{table_name}`.
305            pub fn {unique_field_name}(&self) -> {unique_constraint}<'ctx> {{
306                {unique_constraint} {{
307                    imp: self.imp.get_unique_constraint::<{unique_field_type}>({unique_field_name:?}),
308                    phantom: std::marker::PhantomData,
309                }}
310            }}
311        }}
312
313        impl<'ctx> {unique_constraint}<'ctx> {{
314            /// Find the subscribed row whose `{unique_field_name}` column value is equal to `col_val`,
315            /// if such a row is present in the client cache.
316            pub fn find(&self, col_val: &{unique_field_type}) -> Option<{row_type}> {{
317                self.imp.find(col_val)
318            }}
319        }}
320        "
321            );
322        }
323
324        // TODO: expose non-unique indices.
325
326        output.into_inner()
327    }
328    fn generate_reducer(&self, module: &ModuleDef, reducer: &ReducerDef) -> String {
329        let mut output = CodeIndenter::new(String::new(), INDENT);
330        let out = &mut output;
331
332        print_file_header(out);
333
334        out.newline();
335
336        gen_and_print_imports(
337            module,
338            out,
339            &reducer.params_for_generate.elements,
340            // No need to skip any imports; we're not emitting a type that other modules can import.
341            &[],
342        );
343
344        out.newline();
345
346        let reducer_name = reducer.name.deref();
347        let func_name = reducer_function_name(reducer);
348        let set_reducer_flags_trait = reducer_flags_trait_name(reducer);
349        let args_type = reducer_args_type_name(&reducer.name);
350        let enum_variant_name = reducer_variant_name(&reducer.name);
351
352        // Define an "args struct" for the reducer.
353        // This is not user-facing (note the `pub(super)` visibility);
354        // it is an internal helper for serialization and deserialization.
355        // We actually want to ser/de instances of `enum Reducer`, but:
356        // - `Reducer` will have struct-like variants, which SATS ser/de does not support.
357        // - The WS format does not contain a BSATN-serialized `Reducer` instance;
358        //   it holds the reducer name or ID separately from the argument bytes.
359        //   We could work up some magic with `DeserializeSeed`
360        //   and/or custom `Serializer` and `Deserializer` types
361        //   to account for this, but it's much easier to just use an intermediate struct per reducer.
362        define_struct_for_product(
363            module,
364            out,
365            &args_type,
366            &reducer.params_for_generate.elements,
367            "pub(super)",
368        );
369
370        out.newline();
371
372        let callback_id = reducer_callback_id_name(&reducer.name);
373
374        // The reducer arguments as `ident: ty, ident: ty, ident: ty,`,
375        // like an argument list.
376        let mut arglist = String::new();
377        write_arglist_no_delimiters(module, &mut arglist, &reducer.params_for_generate.elements, None).unwrap();
378
379        // The reducer argument types as `&ty, &ty, &ty`,
380        // for use as the params in a `FnMut` closure type.
381        let mut arg_types_ref_list = String::new();
382        // The reducer argument names as `ident, ident, ident`,
383        // for passing to function call and struct literal expressions.
384        let mut arg_names_list = String::new();
385        for (arg_ident, arg_ty) in &reducer.params_for_generate.elements[..] {
386            arg_types_ref_list += "&";
387            write_type(module, &mut arg_types_ref_list, arg_ty).unwrap();
388            arg_types_ref_list += ", ";
389
390            let arg_name = arg_ident.deref().to_case(Case::Snake);
391            arg_names_list += &arg_name;
392            arg_names_list += ", ";
393        }
394
395        write!(out, "impl From<{args_type}> for super::Reducer ");
396        out.delimited_block(
397            "{",
398            |out| {
399                write!(out, "fn from(args: {args_type}) -> Self ");
400                out.delimited_block(
401                    "{",
402                    |out| {
403                        write!(out, "Self::{enum_variant_name}");
404                        if !reducer.params_for_generate.elements.is_empty() {
405                            // We generate "struct variants" for reducers with arguments,
406                            // but "unit variants" for reducers of no arguments.
407                            // These use different constructor syntax.
408                            out.delimited_block(
409                                " {",
410                                |out| {
411                                    for (arg_ident, _ty) in &reducer.params_for_generate.elements[..] {
412                                        let arg_name = arg_ident.deref().to_case(Case::Snake);
413                                        writeln!(out, "{arg_name}: args.{arg_name},");
414                                    }
415                                },
416                                "}",
417                            );
418                        }
419                        out.newline();
420                    },
421                    "}\n",
422                );
423            },
424            "}\n",
425        );
426
427        // TODO: check for lifecycle reducers and do not generate the invoke method.
428
429        writeln!(
430            out,
431            "
432impl __sdk::InModule for {args_type} {{
433    type Module = super::RemoteModule;
434}}
435
436pub struct {callback_id}(__sdk::CallbackId);
437
438#[allow(non_camel_case_types)]
439/// Extension trait for access to the reducer `{reducer_name}`.
440///
441/// Implemented for [`super::RemoteReducers`].
442pub trait {func_name} {{
443    /// Request that the remote module invoke the reducer `{reducer_name}` to run as soon as possible.
444    ///
445    /// This method returns immediately, and errors only if we are unable to send the request.
446    /// The reducer will run asynchronously in the future,
447    ///  and its status can be observed by listening for [`Self::on_{func_name}`] callbacks.
448    fn {func_name}(&self, {arglist}) -> __sdk::Result<()>;
449    /// Register a callback to run whenever we are notified of an invocation of the reducer `{reducer_name}`.
450    ///
451    /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`]
452    /// to determine the reducer's status.
453    ///
454    /// The returned [`{callback_id}`] can be passed to [`Self::remove_on_{func_name}`]
455    /// to cancel the callback.
456    fn on_{func_name}(&self, callback: impl FnMut(&super::ReducerEventContext, {arg_types_ref_list}) + Send + 'static) -> {callback_id};
457    /// Cancel a callback previously registered by [`Self::on_{func_name}`],
458    /// causing it not to run in the future.
459    fn remove_on_{func_name}(&self, callback: {callback_id});
460}}
461
462impl {func_name} for super::RemoteReducers {{
463    fn {func_name}(&self, {arglist}) -> __sdk::Result<()> {{
464        self.imp.call_reducer({reducer_name:?}, {args_type} {{ {arg_names_list} }})
465    }}
466    fn on_{func_name}(
467        &self,
468        mut callback: impl FnMut(&super::ReducerEventContext, {arg_types_ref_list}) + Send + 'static,
469    ) -> {callback_id} {{
470        {callback_id}(self.imp.on_reducer(
471            {reducer_name:?},
472            Box::new(move |ctx: &super::ReducerEventContext| {{
473                let super::ReducerEventContext {{
474                    event: __sdk::ReducerEvent {{
475                        reducer: super::Reducer::{enum_variant_name} {{
476                            {arg_names_list}
477                        }},
478                        ..
479                    }},
480                    ..
481                }} = ctx else {{ unreachable!() }};
482                callback(ctx, {arg_names_list})
483            }}),
484        ))
485    }}
486    fn remove_on_{func_name}(&self, callback: {callback_id}) {{
487        self.imp.remove_on_reducer({reducer_name:?}, callback.0)
488    }}
489}}
490
491#[allow(non_camel_case_types)]
492#[doc(hidden)]
493/// Extension trait for setting the call-flags for the reducer `{reducer_name}`.
494///
495/// Implemented for [`super::SetReducerFlags`].
496///
497/// This type is currently unstable and may be removed without a major version bump.
498pub trait {set_reducer_flags_trait} {{
499    /// Set the call-reducer flags for the reducer `{reducer_name}` to `flags`.
500    ///
501    /// This type is currently unstable and may be removed without a major version bump.
502    fn {func_name}(&self, flags: __ws::CallReducerFlags);
503}}
504
505impl {set_reducer_flags_trait} for super::SetReducerFlags {{
506    fn {func_name}(&self, flags: __ws::CallReducerFlags) {{
507        self.imp.set_call_reducer_flags({reducer_name:?}, flags);
508    }}
509}}
510"
511        );
512
513        output.into_inner()
514    }
515
516    fn generate_globals(&self, module: &ModuleDef) -> Vec<(String, String)> {
517        let mut output = CodeIndenter::new(String::new(), INDENT);
518        let out = &mut output;
519
520        print_file_header(out);
521
522        out.newline();
523
524        // Declare `pub mod` for each of the files generated.
525        print_module_decls(module, out);
526
527        out.newline();
528
529        // Re-export all the modules for the generated files.
530        print_module_reexports(module, out);
531
532        out.newline();
533
534        // Define `enum Reducer`.
535        print_reducer_enum_defn(module, out);
536
537        out.newline();
538
539        // Define `DbUpdate`.
540        print_db_update_defn(module, out);
541
542        out.newline();
543
544        // Define `AppliedDiff`.
545        print_applied_diff_defn(module, out);
546
547        out.newline();
548
549        // Define `RemoteModule`, `DbConnection`, `EventContext`, `RemoteTables`, `RemoteReducers` and `SubscriptionHandle`.
550        // Note that these do not change based on the module.
551        print_const_db_context_types(out);
552
553        out.newline();
554
555        // Implement `SpacetimeModule` for `RemoteModule`.
556        // This includes a method for initializing the tables in the client cache.
557        print_impl_spacetime_module(module, out);
558
559        vec![("mod.rs".to_string(), (output.into_inner()))]
560    }
561
562    fn clap_value() -> clap::builder::PossibleValue {
563        clap::builder::PossibleValue::new("rust").aliases(["rs", "RS"])
564    }
565}
566
567pub fn write_type<W: Write>(module: &ModuleDef, out: &mut W, ty: &AlgebraicTypeUse) -> fmt::Result {
568    match ty {
569        AlgebraicTypeUse::Unit => write!(out, "()")?,
570        AlgebraicTypeUse::Never => write!(out, "std::convert::Infallible")?,
571        AlgebraicTypeUse::Identity => write!(out, "__sdk::Identity")?,
572        AlgebraicTypeUse::ConnectionId => write!(out, "__sdk::ConnectionId")?,
573        AlgebraicTypeUse::Timestamp => write!(out, "__sdk::Timestamp")?,
574        AlgebraicTypeUse::TimeDuration => write!(out, "__sdk::TimeDuration")?,
575        AlgebraicTypeUse::ScheduleAt => write!(out, "__sdk::ScheduleAt")?,
576        AlgebraicTypeUse::Option(inner_ty) => {
577            write!(out, "Option::<")?;
578            write_type(module, out, inner_ty)?;
579            write!(out, ">")?;
580        }
581        AlgebraicTypeUse::Primitive(prim) => match prim {
582            PrimitiveType::Bool => write!(out, "bool")?,
583            PrimitiveType::I8 => write!(out, "i8")?,
584            PrimitiveType::U8 => write!(out, "u8")?,
585            PrimitiveType::I16 => write!(out, "i16")?,
586            PrimitiveType::U16 => write!(out, "u16")?,
587            PrimitiveType::I32 => write!(out, "i32")?,
588            PrimitiveType::U32 => write!(out, "u32")?,
589            PrimitiveType::I64 => write!(out, "i64")?,
590            PrimitiveType::U64 => write!(out, "u64")?,
591            PrimitiveType::I128 => write!(out, "i128")?,
592            PrimitiveType::U128 => write!(out, "u128")?,
593            PrimitiveType::I256 => write!(out, "__sats::i256")?,
594            PrimitiveType::U256 => write!(out, "__sats::u256")?,
595            PrimitiveType::F32 => write!(out, "f32")?,
596            PrimitiveType::F64 => write!(out, "f64")?,
597        },
598        AlgebraicTypeUse::String => write!(out, "String")?,
599        AlgebraicTypeUse::Array(elem_ty) => {
600            write!(out, "Vec::<")?;
601            write_type(module, out, elem_ty)?;
602            write!(out, ">")?;
603        }
604        AlgebraicTypeUse::Ref(r) => {
605            write!(out, "{}", type_ref_name(module, *r))?;
606        }
607    }
608    Ok(())
609}
610
611pub fn type_name(module: &ModuleDef, ty: &AlgebraicTypeUse) -> String {
612    let mut s = String::new();
613    write_type(module, &mut s, ty).unwrap();
614    s
615}
616
617const ALLOW_LINTS: &str = "#![allow(unused, clippy::all)]";
618
619const SPACETIMEDB_IMPORTS: &[&str] = &[
620    "use spacetimedb_sdk::__codegen::{",
621    "\tself as __sdk,",
622    "\t__lib,",
623    "\t__sats,",
624    "\t__ws,",
625    "};",
626];
627
628fn print_spacetimedb_imports(output: &mut Indenter) {
629    print_lines(output, SPACETIMEDB_IMPORTS);
630}
631
632fn print_file_header(output: &mut Indenter) {
633    print_auto_generated_file_comment(output);
634    writeln!(output, "{ALLOW_LINTS}");
635    print_spacetimedb_imports(output);
636}
637
638// TODO: figure out if/when sum types should derive:
639// - Clone
640// - Debug
641// - Copy
642// - PartialEq, Eq
643// - Hash
644//    - Complicated because `HashMap` is not `Hash`.
645// - others?
646
647const ENUM_DERIVES: &[&str] = &[
648    "#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]",
649    "#[sats(crate = __lib)]",
650];
651
652fn print_enum_derives(output: &mut Indenter) {
653    print_lines(output, ENUM_DERIVES);
654}
655
656const PLAIN_ENUM_EXTRA_DERIVES: &[&str] = &["#[derive(Copy, Eq, Hash)]"];
657
658fn print_plain_enum_extra_derives(output: &mut Indenter) {
659    print_lines(output, PLAIN_ENUM_EXTRA_DERIVES);
660}
661
662/// Generate a file which defines an `enum` corresponding to the `sum_type`.
663pub fn define_enum_for_sum(
664    module: &ModuleDef,
665    out: &mut Indenter,
666    name: &str,
667    variants: &[(Identifier, AlgebraicTypeUse)],
668    is_plain: bool,
669) {
670    print_enum_derives(out);
671    if is_plain {
672        print_plain_enum_extra_derives(out);
673    }
674    write!(out, "pub enum {name} ");
675
676    out.delimited_block(
677        "{",
678        |out| {
679            for (ident, ty) in variants {
680                write_enum_variant(module, out, ident, ty);
681                out.newline();
682            }
683        },
684        "}\n",
685    );
686
687    out.newline()
688}
689
690fn write_enum_variant(module: &ModuleDef, out: &mut Indenter, ident: &Identifier, ty: &AlgebraicTypeUse) {
691    let name = ident.deref().to_case(Case::Pascal);
692    write!(out, "{name}");
693
694    // If the contained type is the unit type, i.e. this variant has no members,
695    // write it without parens or braces, like
696    // ```
697    // Foo,
698    // ```
699    if !matches!(ty, AlgebraicTypeUse::Unit) {
700        // If the contained type is not a product, i.e. this variant has a single
701        // member, write it tuple-style, with parens.
702        write!(out, "(");
703        write_type(module, out, ty).unwrap();
704        write!(out, ")");
705    }
706    writeln!(out, ",");
707}
708
709fn write_struct_type_fields_in_braces(
710    module: &ModuleDef,
711    out: &mut Indenter,
712    elements: &[(Identifier, AlgebraicTypeUse)],
713
714    // Whether to print a `pub` qualifier on the fields. Necessary for `struct` defns,
715    // disallowed for `enum` defns.
716    pub_qualifier: bool,
717) {
718    out.delimited_block(
719        "{",
720        |out| write_arglist_no_delimiters(module, out, elements, pub_qualifier.then_some("pub")).unwrap(),
721        "}",
722    );
723}
724
725fn write_arglist_no_delimiters(
726    module: &ModuleDef,
727    out: &mut impl Write,
728    elements: &[(Identifier, AlgebraicTypeUse)],
729
730    // Written before each line. Useful for `pub`.
731    prefix: Option<&str>,
732) -> anyhow::Result<()> {
733    for (ident, ty) in elements {
734        if let Some(prefix) = prefix {
735            write!(out, "{prefix} ")?;
736        }
737
738        let name = ident.deref().to_case(Case::Snake);
739
740        write!(out, "{name}: ")?;
741        write_type(module, out, ty)?;
742        writeln!(out, ",")?;
743    }
744
745    Ok(())
746}
747
748// TODO: figure out if/when product types should derive:
749// - Clone
750// - Debug
751// - Copy
752// - PartialEq, Eq
753// - Hash
754//    - Complicated because `HashMap` is not `Hash`.
755// - others?
756
757const STRUCT_DERIVES: &[&str] = &[
758    "#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]",
759    "#[sats(crate = __lib)]",
760];
761
762fn print_struct_derives(output: &mut Indenter) {
763    print_lines(output, STRUCT_DERIVES);
764}
765
766fn define_struct_for_product(
767    module: &ModuleDef,
768    out: &mut Indenter,
769    name: &str,
770    elements: &[(Identifier, AlgebraicTypeUse)],
771    vis: &str,
772) {
773    print_struct_derives(out);
774
775    write!(out, "{vis} struct {name} ");
776
777    // TODO: if elements is empty, define a unit struct with no brace-delimited list of fields.
778    write_struct_type_fields_in_braces(
779        module, out, elements, true, // `pub`-qualify fields.
780    );
781
782    out.newline();
783}
784
785fn type_ref_module_name(module: &ModuleDef, type_ref: AlgebraicTypeRef) -> String {
786    let (name, _) = module.type_def_from_ref(type_ref).unwrap();
787    type_module_name(name)
788}
789
790fn type_module_name(type_name: &ScopedTypeName) -> String {
791    collect_case(Case::Snake, type_name.name_segments()) + "_type"
792}
793
794fn table_module_name(table_name: &Identifier) -> String {
795    table_name.deref().to_case(Case::Snake) + "_table"
796}
797
798fn table_method_name(table_name: &Identifier) -> String {
799    table_name.deref().to_case(Case::Snake)
800}
801
802fn table_access_trait_name(table_name: &Identifier) -> String {
803    table_name.deref().to_case(Case::Pascal) + "TableAccess"
804}
805
806fn reducer_args_type_name(reducer_name: &Identifier) -> String {
807    reducer_name.deref().to_case(Case::Pascal) + "Args"
808}
809
810fn reducer_variant_name(reducer_name: &Identifier) -> String {
811    reducer_name.deref().to_case(Case::Pascal)
812}
813
814fn reducer_callback_id_name(reducer_name: &Identifier) -> String {
815    reducer_name.deref().to_case(Case::Pascal) + "CallbackId"
816}
817
818fn reducer_module_name(reducer_name: &Identifier) -> String {
819    reducer_name.deref().to_case(Case::Snake) + "_reducer"
820}
821
822fn reducer_function_name(reducer: &ReducerDef) -> String {
823    reducer.name.deref().to_case(Case::Snake)
824}
825
826fn reducer_flags_trait_name(reducer: &ReducerDef) -> String {
827    format!("set_flags_for_{}", reducer_function_name(reducer))
828}
829
830/// Iterate over all of the Rust `mod`s for types, reducers and tables in the `module`.
831fn iter_module_names(module: &ModuleDef) -> impl Iterator<Item = String> + '_ {
832    itertools::chain!(
833        iter_types(module).map(|ty| type_module_name(&ty.name)),
834        iter_reducers(module).map(|r| reducer_module_name(&r.name)),
835        iter_tables(module).map(|tbl| table_module_name(&tbl.name)),
836    )
837}
838
839/// Print `pub mod` declarations for all the files that will be generated for `items`.
840fn print_module_decls(module: &ModuleDef, out: &mut Indenter) {
841    for module_name in iter_module_names(module) {
842        writeln!(out, "pub mod {module_name};");
843    }
844}
845
846/// Print appropriate reexports for all the files that will be generated for `items`.
847fn print_module_reexports(module: &ModuleDef, out: &mut Indenter) {
848    for ty in iter_types(module) {
849        let mod_name = type_module_name(&ty.name);
850        let type_name = collect_case(Case::Pascal, ty.name.name_segments());
851        writeln!(out, "pub use {mod_name}::{type_name};")
852    }
853    for table in iter_tables(module) {
854        let mod_name = table_module_name(&table.name);
855        // TODO: More precise reexport: we want:
856        // - The trait name.
857        // - The insert, delete and possibly update callback ids.
858        // We do not want:
859        // - The table handle.
860        writeln!(out, "pub use {mod_name}::*;");
861    }
862    for reducer in iter_reducers(module) {
863        let mod_name = reducer_module_name(&reducer.name);
864        let reducer_trait_name = reducer_function_name(reducer);
865        let flags_trait_name = reducer_flags_trait_name(reducer);
866        let callback_id_name = reducer_callback_id_name(&reducer.name);
867        writeln!(
868            out,
869            "pub use {mod_name}::{{{reducer_trait_name}, {flags_trait_name}, {callback_id_name}}};"
870        );
871    }
872}
873
874fn print_reducer_enum_defn(module: &ModuleDef, out: &mut Indenter) {
875    // Don't derive ser/de on this enum;
876    // it's not a proper SATS enum and the derive will fail.
877    writeln!(out, "#[derive(Clone, PartialEq, Debug)]");
878    writeln!(
879        out,
880        "
881/// One of the reducers defined by this module.
882///
883/// Contained within a [`__sdk::ReducerEvent`] in [`EventContext`]s for reducer events
884/// to indicate which reducer caused the event.
885",
886    );
887    out.delimited_block(
888        "pub enum Reducer {",
889        |out| {
890            for reducer in iter_reducers(module) {
891                write!(out, "{} ", reducer_variant_name(&reducer.name));
892                if !reducer.params_for_generate.elements.is_empty() {
893                    // If the reducer has any arguments, generate a "struct variant,"
894                    // like `Foo { bar: Baz, }`.
895                    // If it doesn't, generate a "unit variant" instead,
896                    // like `Foo,`.
897                    write_struct_type_fields_in_braces(module, out, &reducer.params_for_generate.elements, false);
898                }
899                writeln!(out, ",");
900            }
901        },
902        "}\n",
903    );
904    out.newline();
905    writeln!(
906        out,
907        "
908impl __sdk::InModule for Reducer {{
909    type Module = RemoteModule;
910}}
911",
912    );
913
914    out.delimited_block(
915        "impl __sdk::Reducer for Reducer {",
916        |out| {
917            out.delimited_block(
918                "fn reducer_name(&self) -> &'static str {",
919                |out| {
920                    out.delimited_block(
921                        "match self {",
922                        |out| {
923                            for reducer in iter_reducers(module) {
924                                write!(out, "Reducer::{}", reducer_variant_name(&reducer.name));
925                                if !reducer.params_for_generate.elements.is_empty() {
926                                    // Because we're emitting unit variants when the payload is empty,
927                                    // we will emit different patterns for empty vs non-empty variants.
928                                    // This is not strictly required;
929                                    // Rust allows matching a struct-like pattern
930                                    // against a unit-like enum variant,
931                                    // but we prefer the clarity of not including the braces for unit variants.
932                                    write!(out, " {{ .. }}");
933                                }
934                                writeln!(out, " => {:?},", reducer.name.deref());
935                            }
936                        },
937                        "}\n",
938                    );
939                },
940                "}\n",
941            );
942        },
943        "}\n",
944    );
945
946    out.delimited_block(
947        "impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer {",
948        |out| {
949            writeln!(out, "type Error = __sdk::Error;");
950            // We define an "args struct" for each reducer in `generate_reducer`.
951            // This is not user-facing, and is not exported past the "root" `mod.rs`;
952            // it is an internal helper for serialization and deserialization.
953            // We actually want to ser/de instances of `enum Reducer`, but:
954            //
955            // - `Reducer` will have struct-like variants, which SATS ser/de does not support.
956            // - The WS format does not contain a BSATN-serialized `Reducer` instance;
957            //   it holds the reducer name or ID separately from the argument bytes.
958            //   We could work up some magic with `DeserializeSeed`
959            //   and/or custom `Serializer` and `Deserializer` types
960            //   to account for this, but it's much easier to just use an intermediate struct per reducer.
961            //
962            // As such, we deserialize from the `value.args` bytes into that "args struct,"
963            // then convert it into a `Reducer` variant via `Into::into`,
964            // which we also implement in `generate_reducer`.
965            out.delimited_block(
966                "fn try_from(value: __ws::ReducerCallInfo<__ws::BsatnFormat>) -> __sdk::Result<Self> {",
967                |out| {
968                    out.delimited_block(
969                        "match &value.reducer_name[..] {",
970                        |out| {
971                            for reducer in iter_reducers(module) {
972                                writeln!(
973                                    out,
974                                    "{:?} => Ok(__sdk::parse_reducer_args::<{}::{}>({:?}, &value.args)?.into()),",
975                                    reducer.name.deref(),
976                                    reducer_module_name(&reducer.name),
977                                    reducer_args_type_name(&reducer.name),
978                                    reducer.name.deref(),
979                                );
980                            }
981                            writeln!(
982                                out,
983                                "unknown => Err(__sdk::InternalError::unknown_name(\"reducer\", unknown, \"ReducerCallInfo\").into()),",
984                            );
985                        },
986                        "}\n",
987                    )
988                },
989                "}\n",
990            );
991        },
992        "}\n",
993    )
994}
995
996fn print_db_update_defn(module: &ModuleDef, out: &mut Indenter) {
997    writeln!(out, "#[derive(Default)]");
998    writeln!(out, "#[allow(non_snake_case)]");
999    writeln!(out, "#[doc(hidden)]");
1000    out.delimited_block(
1001        "pub struct DbUpdate {",
1002        |out| {
1003            for table in iter_tables(module) {
1004                writeln!(
1005                    out,
1006                    "{}: __sdk::TableUpdate<{}>,",
1007                    table_method_name(&table.name),
1008                    type_ref_name(module, table.product_type_ref),
1009                );
1010            }
1011        },
1012        "}\n",
1013    );
1014
1015    out.newline();
1016
1017    out.delimited_block(
1018        "
1019impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate {
1020    type Error = __sdk::Error;
1021    fn try_from(raw: __ws::DatabaseUpdate<__ws::BsatnFormat>) -> Result<Self, Self::Error> {
1022        let mut db_update = DbUpdate::default();
1023        for table_update in raw.tables {
1024            match &table_update.table_name[..] {
1025",
1026        |out| {
1027            for table in iter_tables(module) {
1028                writeln!(
1029                    out,
1030                    "{:?} => db_update.{} = {}::parse_table_update(table_update)?,",
1031                    table.name.deref(),
1032                    table_method_name(&table.name),
1033                    table_module_name(&table.name),
1034                );
1035            }
1036        },
1037        "
1038                unknown => {
1039                    return Err(__sdk::InternalError::unknown_name(
1040                        \"table\",
1041                        unknown,
1042                        \"DatabaseUpdate\",
1043                    ).into());
1044                }
1045            }
1046        }
1047        Ok(db_update)
1048    }
1049}",
1050    );
1051
1052    out.newline();
1053
1054    writeln!(
1055        out,
1056        "
1057impl __sdk::InModule for DbUpdate {{
1058    type Module = RemoteModule;
1059}}
1060",
1061    );
1062
1063    out.delimited_block(
1064        "impl __sdk::DbUpdate for DbUpdate {",
1065        |out| {
1066            out.delimited_block(
1067                "fn apply_to_client_cache(&self, cache: &mut __sdk::ClientCache<RemoteModule>) -> AppliedDiff<'_> {
1068                    let mut diff = AppliedDiff::default();
1069                ",
1070                |out| {
1071                    for table in iter_tables(module) {
1072                        let with_updates = table
1073                            .primary_key
1074                            .map(|col| {
1075                                let pk_field = table.get_column(col).unwrap().name.deref().to_case(Case::Snake);
1076                                format!(".with_updates_by_pk(|row| &row.{pk_field})")
1077                            })
1078                            .unwrap_or_default();
1079
1080                        let field_name = table_method_name(&table.name);
1081                        writeln!(
1082                            out,
1083                            "diff.{field_name} = cache.apply_diff_to_table::<{}>({:?}, &self.{field_name}){with_updates};",
1084                            type_ref_name(module, table.product_type_ref),
1085                            table.name.deref(),
1086                        );
1087                    }
1088                },
1089                "
1090                    diff
1091                }\n",
1092            );
1093        },
1094        "}\n",
1095    );
1096}
1097
1098fn print_applied_diff_defn(module: &ModuleDef, out: &mut Indenter) {
1099    writeln!(out, "#[derive(Default)]");
1100    writeln!(out, "#[allow(non_snake_case)]");
1101    writeln!(out, "#[doc(hidden)]");
1102    out.delimited_block(
1103        "pub struct AppliedDiff<'r> {",
1104        |out| {
1105            for table in iter_tables(module) {
1106                writeln!(
1107                    out,
1108                    "{}: __sdk::TableAppliedDiff<'r, {}>,",
1109                    table_method_name(&table.name),
1110                    type_ref_name(module, table.product_type_ref),
1111                );
1112            }
1113        },
1114        "}\n",
1115    );
1116
1117    out.newline();
1118
1119    writeln!(
1120        out,
1121        "
1122impl __sdk::InModule for AppliedDiff<'_> {{
1123    type Module = RemoteModule;
1124}}
1125",
1126    );
1127
1128    out.delimited_block(
1129        "impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {",
1130        |out| {
1131            out.delimited_block(
1132                "fn invoke_row_callbacks(&self, event: &EventContext, callbacks: &mut __sdk::DbCallbacks<RemoteModule>) {",
1133                |out| {
1134                    for table in iter_tables(module) {
1135                        writeln!(
1136                            out,
1137                            "callbacks.invoke_table_row_callbacks::<{}>({:?}, &self.{}, event);",
1138                            type_ref_name(module, table.product_type_ref),
1139                            table.name.deref(),
1140                            table_method_name(&table.name),
1141                        );
1142                    }
1143                },
1144                "}\n",
1145            );
1146        },
1147        "}\n",
1148    );
1149}
1150
1151fn print_impl_spacetime_module(module: &ModuleDef, out: &mut Indenter) {
1152    out.delimited_block(
1153        "impl __sdk::SpacetimeModule for RemoteModule {",
1154        |out| {
1155            writeln!(
1156                out,
1157                "
1158type DbConnection = DbConnection;
1159type EventContext = EventContext;
1160type ReducerEventContext = ReducerEventContext;
1161type SubscriptionEventContext = SubscriptionEventContext;
1162type ErrorContext = ErrorContext;
1163type Reducer = Reducer;
1164type DbView = RemoteTables;
1165type Reducers = RemoteReducers;
1166type SetReducerFlags = SetReducerFlags;
1167type DbUpdate = DbUpdate;
1168type AppliedDiff<'r> = AppliedDiff<'r>;
1169type SubscriptionHandle = SubscriptionHandle;
1170"
1171            );
1172            out.delimited_block(
1173                "fn register_tables(client_cache: &mut __sdk::ClientCache<Self>) {",
1174                |out| {
1175                    for table in iter_tables(module) {
1176                        writeln!(out, "{}::register_table(client_cache);", table_module_name(&table.name));
1177                    }
1178                },
1179                "}\n",
1180            );
1181        },
1182        "}\n",
1183    );
1184}
1185
1186fn print_const_db_context_types(out: &mut Indenter) {
1187    writeln!(
1188        out,
1189        "
1190#[doc(hidden)]
1191pub struct RemoteModule;
1192
1193impl __sdk::InModule for RemoteModule {{
1194    type Module = Self;
1195}}
1196
1197/// The `reducers` field of [`EventContext`] and [`DbConnection`],
1198/// with methods provided by extension traits for each reducer defined by the module.
1199pub struct RemoteReducers {{
1200    imp: __sdk::DbContextImpl<RemoteModule>,
1201}}
1202
1203impl __sdk::InModule for RemoteReducers {{
1204    type Module = RemoteModule;
1205}}
1206
1207#[doc(hidden)]
1208/// The `set_reducer_flags` field of [`DbConnection`],
1209/// with methods provided by extension traits for each reducer defined by the module.
1210/// Each method sets the flags for the reducer with the same name.
1211///
1212/// This type is currently unstable and may be removed without a major version bump.
1213pub struct SetReducerFlags {{
1214    imp: __sdk::DbContextImpl<RemoteModule>,
1215}}
1216
1217impl __sdk::InModule for SetReducerFlags {{
1218    type Module = RemoteModule;
1219}}
1220
1221/// The `db` field of [`EventContext`] and [`DbConnection`],
1222/// with methods provided by extension traits for each table defined by the module.
1223pub struct RemoteTables {{
1224    imp: __sdk::DbContextImpl<RemoteModule>,
1225}}
1226
1227impl __sdk::InModule for RemoteTables {{
1228    type Module = RemoteModule;
1229}}
1230
1231/// A connection to a remote module, including a materialized view of a subset of the database.
1232///
1233/// Connect to a remote module by calling [`DbConnection::builder`]
1234/// and using the [`__sdk::DbConnectionBuilder`] builder-pattern constructor.
1235///
1236/// You must explicitly advance the connection by calling any one of:
1237///
1238/// - [`DbConnection::frame_tick`].
1239/// - [`DbConnection::run_threaded`].
1240/// - [`DbConnection::run_async`].
1241/// - [`DbConnection::advance_one_message`].
1242/// - [`DbConnection::advance_one_message_blocking`].
1243/// - [`DbConnection::advance_one_message_async`].
1244///
1245/// Which of these methods you should call depends on the specific needs of your application,
1246/// but you must call one of them, or else the connection will never progress.
1247pub struct DbConnection {{
1248    /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
1249    pub db: RemoteTables,
1250    /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
1251    pub reducers: RemoteReducers,
1252    #[doc(hidden)]
1253    /// Access to setting the call-flags of each reducer defined for each reducer defined by the module
1254    /// via extension traits implemented for [`SetReducerFlags`].
1255    ///
1256    /// This type is currently unstable and may be removed without a major version bump.
1257    pub set_reducer_flags: SetReducerFlags,
1258
1259    imp: __sdk::DbContextImpl<RemoteModule>,
1260}}
1261
1262impl __sdk::InModule for DbConnection {{
1263    type Module = RemoteModule;
1264}}
1265
1266impl __sdk::DbContext for DbConnection {{
1267    type DbView = RemoteTables;
1268    type Reducers = RemoteReducers;
1269    type SetReducerFlags = SetReducerFlags;
1270
1271    fn db(&self) -> &Self::DbView {{
1272        &self.db
1273    }}
1274    fn reducers(&self) -> &Self::Reducers {{
1275        &self.reducers
1276    }}
1277    fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{
1278        &self.set_reducer_flags
1279    }}
1280
1281    fn is_active(&self) -> bool {{
1282        self.imp.is_active()
1283    }}
1284
1285    fn disconnect(&self) -> __sdk::Result<()> {{
1286        self.imp.disconnect()
1287    }}
1288
1289    type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
1290
1291    fn subscription_builder(&self) -> Self::SubscriptionBuilder {{
1292        __sdk::SubscriptionBuilder::new(&self.imp)
1293    }}
1294
1295    fn try_identity(&self) -> Option<__sdk::Identity> {{
1296        self.imp.try_identity()
1297    }}
1298    fn connection_id(&self) -> __sdk::ConnectionId {{
1299        self.imp.connection_id()
1300    }}
1301}}
1302
1303impl DbConnection {{
1304    /// Builder-pattern constructor for a connection to a remote module.
1305    ///
1306    /// See [`__sdk::DbConnectionBuilder`] for required and optional configuration for the new connection.
1307    pub fn builder() -> __sdk::DbConnectionBuilder<RemoteModule> {{
1308        __sdk::DbConnectionBuilder::new()
1309    }}
1310
1311    /// If any WebSocket messages are waiting, process one of them.
1312    ///
1313    /// Returns `true` if a message was processed, or `false` if the queue is empty.
1314    /// Callers should invoke this message in a loop until it returns `false`
1315    /// or for as much time is available to process messages.
1316    ///
1317    /// Returns an error if the connection is disconnected.
1318    /// If the disconnection in question was normal,
1319    ///  i.e. the result of a call to [`__sdk::DbContext::disconnect`],
1320    /// the returned error will be downcastable to [`__sdk::DisconnectedError`].
1321    ///
1322    /// This is a low-level primitive exposed for power users who need significant control over scheduling.
1323    /// Most applications should call [`Self::frame_tick`] each frame
1324    /// to fully exhaust the queue whenever time is available.
1325    pub fn advance_one_message(&self) -> __sdk::Result<bool> {{
1326        self.imp.advance_one_message()
1327    }}
1328
1329    /// Process one WebSocket message, potentially blocking the current thread until one is received.
1330    ///
1331    /// Returns an error if the connection is disconnected.
1332    /// If the disconnection in question was normal,
1333    ///  i.e. the result of a call to [`__sdk::DbContext::disconnect`],
1334    /// the returned error will be downcastable to [`__sdk::DisconnectedError`].
1335    ///
1336    /// This is a low-level primitive exposed for power users who need significant control over scheduling.
1337    /// Most applications should call [`Self::run_threaded`] to spawn a thread
1338    /// which advances the connection automatically.
1339    pub fn advance_one_message_blocking(&self) -> __sdk::Result<()> {{
1340        self.imp.advance_one_message_blocking()
1341    }}
1342
1343    /// Process one WebSocket message, `await`ing until one is received.
1344    ///
1345    /// Returns an error if the connection is disconnected.
1346    /// If the disconnection in question was normal,
1347    ///  i.e. the result of a call to [`__sdk::DbContext::disconnect`],
1348    /// the returned error will be downcastable to [`__sdk::DisconnectedError`].
1349    ///
1350    /// This is a low-level primitive exposed for power users who need significant control over scheduling.
1351    /// Most applications should call [`Self::run_async`] to run an `async` loop
1352    /// which advances the connection when polled.
1353    pub async fn advance_one_message_async(&self) -> __sdk::Result<()> {{
1354        self.imp.advance_one_message_async().await
1355    }}
1356
1357    /// Process all WebSocket messages waiting in the queue,
1358    /// then return without `await`ing or blocking the current thread.
1359    pub fn frame_tick(&self) -> __sdk::Result<()> {{
1360        self.imp.frame_tick()
1361    }}
1362
1363    /// Spawn a thread which processes WebSocket messages as they are received.
1364    pub fn run_threaded(&self) -> std::thread::JoinHandle<()> {{
1365        self.imp.run_threaded()
1366    }}
1367
1368    /// Run an `async` loop which processes WebSocket messages when polled.
1369    pub async fn run_async(&self) -> __sdk::Result<()> {{
1370        self.imp.run_async().await
1371    }}
1372}}
1373
1374impl __sdk::DbConnection for DbConnection {{
1375    fn new(imp: __sdk::DbContextImpl<RemoteModule>) -> Self {{
1376        Self {{
1377            db: RemoteTables {{ imp: imp.clone() }},
1378            reducers: RemoteReducers {{ imp: imp.clone() }},
1379            set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }},
1380            imp,
1381        }}
1382    }}
1383}}
1384
1385/// A handle on a subscribed query.
1386// TODO: Document this better after implementing the new subscription API.
1387#[derive(Clone)]
1388pub struct SubscriptionHandle {{
1389    imp: __sdk::SubscriptionHandleImpl<RemoteModule>,
1390}}
1391
1392impl __sdk::InModule for SubscriptionHandle {{
1393    type Module = RemoteModule;
1394}}
1395
1396impl __sdk::SubscriptionHandle for SubscriptionHandle {{
1397    fn new(imp: __sdk::SubscriptionHandleImpl<RemoteModule>) -> Self {{
1398        Self {{ imp }}
1399    }}
1400
1401    /// Returns true if this subscription has been terminated due to an unsubscribe call or an error.
1402    fn is_ended(&self) -> bool {{
1403        self.imp.is_ended()
1404    }}
1405
1406    /// Returns true if this subscription has been applied and has not yet been unsubscribed.
1407    fn is_active(&self) -> bool {{
1408        self.imp.is_active()
1409    }}
1410
1411    /// Unsubscribe from the query controlled by this `SubscriptionHandle`,
1412    /// then run `on_end` when its rows are removed from the client cache.
1413    fn unsubscribe_then(self, on_end: __sdk::OnEndedCallback<RemoteModule>) -> __sdk::Result<()> {{
1414        self.imp.unsubscribe_then(Some(on_end))
1415    }}
1416
1417    fn unsubscribe(self) -> __sdk::Result<()> {{
1418        self.imp.unsubscribe_then(None)
1419    }}
1420
1421}}
1422
1423/// Alias trait for a [`__sdk::DbContext`] connected to this module,
1424/// with that trait's associated types bounded to this module's concrete types.
1425///
1426/// Users can use this trait as a boundary on definitions which should accept
1427/// either a [`DbConnection`] or an [`EventContext`] and operate on either.
1428pub trait RemoteDbContext: __sdk::DbContext<
1429    DbView = RemoteTables,
1430    Reducers = RemoteReducers,
1431    SetReducerFlags = SetReducerFlags,
1432    SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
1433> {{}}
1434impl<Ctx: __sdk::DbContext<
1435    DbView = RemoteTables,
1436    Reducers = RemoteReducers,
1437    SetReducerFlags = SetReducerFlags,
1438    SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
1439>> RemoteDbContext for Ctx {{}}
1440",
1441    );
1442
1443    define_event_context(
1444        out,
1445        "EventContext",
1446        Some("__sdk::Event<Reducer>"),
1447        "[`__sdk::Table::on_insert`], [`__sdk::Table::on_delete`] and [`__sdk::TableWithPrimaryKey::on_update`] callbacks",
1448        Some("[`__sdk::Event`]"),
1449    );
1450
1451    define_event_context(
1452        out,
1453        "ReducerEventContext",
1454        Some("__sdk::ReducerEvent<Reducer>"),
1455        "on-reducer callbacks", // There's no single trait or method for reducer callbacks, so we can't usefully link to them.
1456        Some("[`__sdk::ReducerEvent`]"),
1457    );
1458
1459    define_event_context(
1460        out,
1461        "SubscriptionEventContext",
1462        None, // SubscriptionEventContexts have no additional `event` info, so they don't even get that field.
1463        "[`__sdk::SubscriptionBuilder::on_applied`] and [`SubscriptionHandle::unsubscribe_then`] callbacks",
1464        None,
1465    );
1466
1467    define_event_context(
1468        out,
1469        "ErrorContext",
1470        Some("Option<__sdk::Error>"),
1471        "[`__sdk::DbConnectionBuilder::on_disconnect`], [`__sdk::DbConnectionBuilder::on_connect_error`] and [`__sdk::SubscriptionBuilder::on_error`] callbacks",
1472        Some("[`__sdk::Error`]"),
1473    );
1474}
1475
1476/// Define a type that implements `AbstractEventContext` and one of its concrete subtraits.
1477///
1478/// `struct_and_trait_name` should be the name of an event context trait,
1479/// and will also be used as the new struct's name.
1480///
1481/// `event_type`, if `Some`, should be a Rust type which will be the type of the new struct's `event` field.
1482/// If `None`, the new struct will not have such a field.
1483/// The `SubscriptionEventContext` will pass `None`, since there is no useful information to add.
1484///
1485/// `passed_to_callbacks_doc_link` should be a rustdoc-formatted phrase
1486/// which links to the callback-registering functions for the callbacks which accept this event context type.
1487/// It should be of the form "foo callbacks" or "foo, bar and baz callbacks",
1488/// with link formatting where appropriate, and no trailing punctuation.
1489///
1490/// If `event_type` is `Some`, `event_type_doc_link` should be as well.
1491/// It should be a rustdoc-formatted link (including square brackets and all) to the `event_type`.
1492/// This may differ (in the `strcmp` sense) from `event_type` because it should not inlcude generic parameters.
1493fn define_event_context(
1494    out: &mut Indenter,
1495    struct_and_trait_name: &str,
1496    event_type: Option<&str>,
1497    passed_to_callbacks_doc_link: &str,
1498    event_type_doc_link: Option<&str>,
1499) {
1500    if let (Some(event_type), Some(event_type_doc_link)) = (event_type, event_type_doc_link) {
1501        write!(
1502            out,
1503            "
1504/// An [`__sdk::DbContext`] augmented with a {event_type_doc_link},
1505/// passed to {passed_to_callbacks_doc_link}.
1506pub struct {struct_and_trait_name} {{
1507    /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
1508    pub db: RemoteTables,
1509    /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
1510    pub reducers: RemoteReducers,
1511    /// Access to setting the call-flags of each reducer defined for each reducer defined by the module
1512    /// via extension traits implemented for [`SetReducerFlags`].
1513    ///
1514    /// This type is currently unstable and may be removed without a major version bump.
1515    pub set_reducer_flags: SetReducerFlags,
1516    /// The event which caused these callbacks to run.
1517    pub event: {event_type},
1518    imp: __sdk::DbContextImpl<RemoteModule>,
1519}}
1520
1521impl __sdk::AbstractEventContext for {struct_and_trait_name} {{
1522    type Event = {event_type};
1523    fn event(&self) -> &Self::Event {{
1524        &self.event
1525    }}
1526    fn new(imp: __sdk::DbContextImpl<RemoteModule>, event: Self::Event) -> Self {{
1527        Self {{
1528            db: RemoteTables {{ imp: imp.clone() }},
1529            reducers: RemoteReducers {{ imp: imp.clone() }},
1530            set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }},
1531            event,
1532            imp,
1533        }}
1534    }}
1535}}
1536",
1537        );
1538    } else {
1539        debug_assert!(event_type.is_none() && event_type_doc_link.is_none());
1540        write!(
1541            out,
1542            "
1543/// An [`__sdk::DbContext`] passed to {passed_to_callbacks_doc_link}.
1544pub struct {struct_and_trait_name} {{
1545    /// Access to tables defined by the module via extension traits implemented for [`RemoteTables`].
1546    pub db: RemoteTables,
1547    /// Access to reducers defined by the module via extension traits implemented for [`RemoteReducers`].
1548    pub reducers: RemoteReducers,
1549    /// Access to setting the call-flags of each reducer defined for each reducer defined by the module
1550    /// via extension traits implemented for [`SetReducerFlags`].
1551    ///
1552    /// This type is currently unstable and may be removed without a major version bump.
1553    pub set_reducer_flags: SetReducerFlags,
1554    imp: __sdk::DbContextImpl<RemoteModule>,
1555}}
1556
1557impl __sdk::AbstractEventContext for {struct_and_trait_name} {{
1558    type Event = ();
1559    fn event(&self) -> &Self::Event {{
1560        &()
1561    }}
1562    fn new(imp: __sdk::DbContextImpl<RemoteModule>, _event: Self::Event) -> Self {{
1563        Self {{
1564            db: RemoteTables {{ imp: imp.clone() }},
1565            reducers: RemoteReducers {{ imp: imp.clone() }},
1566            set_reducer_flags: SetReducerFlags {{ imp: imp.clone() }},
1567            imp,
1568        }}
1569    }}
1570}}
1571",
1572        );
1573    }
1574
1575    write!(
1576        out,
1577        "
1578impl __sdk::InModule for {struct_and_trait_name} {{
1579    type Module = RemoteModule;
1580}}
1581
1582impl __sdk::DbContext for {struct_and_trait_name} {{
1583    type DbView = RemoteTables;
1584    type Reducers = RemoteReducers;
1585    type SetReducerFlags = SetReducerFlags;
1586
1587    fn db(&self) -> &Self::DbView {{
1588        &self.db
1589    }}
1590    fn reducers(&self) -> &Self::Reducers {{
1591        &self.reducers
1592    }}
1593    fn set_reducer_flags(&self) -> &Self::SetReducerFlags {{
1594        &self.set_reducer_flags
1595    }}
1596
1597    fn is_active(&self) -> bool {{
1598        self.imp.is_active()
1599    }}
1600
1601    fn disconnect(&self) -> __sdk::Result<()> {{
1602        self.imp.disconnect()
1603    }}
1604
1605    type SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>;
1606
1607    fn subscription_builder(&self) -> Self::SubscriptionBuilder {{
1608        __sdk::SubscriptionBuilder::new(&self.imp)
1609    }}
1610
1611    fn try_identity(&self) -> Option<__sdk::Identity> {{
1612        self.imp.try_identity()
1613    }}
1614    fn connection_id(&self) -> __sdk::ConnectionId {{
1615        self.imp.connection_id()
1616    }}
1617}}
1618
1619impl __sdk::{struct_and_trait_name} for {struct_and_trait_name} {{}}
1620"
1621    );
1622}
1623
1624/// Print `use super::` imports for each of the `imports`.
1625fn print_imports(module: &ModuleDef, out: &mut Indenter, imports: Imports) {
1626    for typeref in imports {
1627        let module_name = type_ref_module_name(module, typeref);
1628        let type_name = type_ref_name(module, typeref);
1629        writeln!(out, "use super::{module_name}::{type_name};");
1630    }
1631}
1632
1633/// Use `search_function` on `roots` to detect required imports, then print them with `print_imports`.
1634///
1635/// `this_file` is passed and excluded for the case of recursive types:
1636/// without it, the definition for a type like `struct Foo { foos: Vec<Foo> }`
1637/// would attempt to include `import super::foo::Foo`, which fails to compile.
1638fn gen_and_print_imports(
1639    module: &ModuleDef,
1640    out: &mut Indenter,
1641    roots: &[(Identifier, AlgebraicTypeUse)],
1642    dont_import: &[AlgebraicTypeRef],
1643) {
1644    let mut imports = BTreeSet::new();
1645
1646    for (_, ty) in roots {
1647        ty.for_each_ref(|r| {
1648            imports.insert(r);
1649        });
1650    }
1651    for skip in dont_import {
1652        imports.remove(skip);
1653    }
1654
1655    print_imports(module, out, imports);
1656}