Skip to main content

spacetimedb_lib/db/raw_def/
v10.rs

1//! ABI Version 10 of the raw module definitions.
2//!
3//! This is a refactored version of V9 with a section-based structure.
4//! V10 moves schedules, lifecycle reducers, and default values out of their V9 locations
5//! into dedicated sections for cleaner organization.
6//! It allows easier future extensibility to add new kinds of definitions.
7
8use crate::db::raw_def::v9::{Lifecycle, RawIndexAlgorithm, TableAccess, TableType};
9use core::fmt;
10use spacetimedb_primitives::{ColId, ColList};
11use spacetimedb_sats::raw_identifier::RawIdentifier;
12use spacetimedb_sats::typespace::TypespaceBuilder;
13use spacetimedb_sats::{AlgebraicType, AlgebraicTypeRef, AlgebraicValue, ProductType, SpacetimeType, Typespace};
14use std::any::TypeId;
15use std::collections::{btree_map, BTreeMap};
16
17/// A possibly-invalid raw module definition.
18///
19/// ABI Version 10.
20///
21/// These "raw definitions" may contain invalid data, and are validated by the `validate` module
22/// into a proper `spacetimedb_schema::ModuleDef`, or a collection of errors.
23///
24/// The module definition maintains the same logical global namespace as V9, mapping `Identifier`s to:
25///
26/// - database-level objects:
27///     - logical schema objects: tables, constraints, sequence definitions
28///     - physical schema objects: indexes
29/// - module-level objects: reducers, procedures, schedule definitions
30/// - binding-level objects: type aliases
31///
32/// All of these types of objects must have unique names within the module.
33/// The exception is columns, which need unique names only within a table.
34#[derive(Default, Debug, Clone, SpacetimeType)]
35#[sats(crate = crate)]
36#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
37pub struct RawModuleDefV10 {
38    /// The sections comprising this module definition.
39    ///
40    /// Sections can appear in any order and are optional.
41    pub sections: Vec<RawModuleDefV10Section>,
42}
43
44/// A section of a V10 module definition.
45///
46/// New variants MUST be added to the END of this enum, to maintain ABI compatibility.
47#[derive(Debug, Clone, SpacetimeType)]
48#[sats(crate = crate)]
49#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
50#[non_exhaustive]
51pub enum RawModuleDefV10Section {
52    /// The `Typespace` used by the module.
53    ///
54    /// `AlgebraicTypeRef`s in other sections refer to this typespace.
55    /// See [`crate::db::raw_def::v9::RawModuleDefV9::typespace`] for validation requirements.
56    Typespace(Typespace),
57
58    /// Type definitions exported by the module.
59    Types(Vec<RawTypeDefV10>),
60
61    /// Table definitions.
62    Tables(Vec<RawTableDefV10>),
63
64    /// Reducer definitions.
65    Reducers(Vec<RawReducerDefV10>),
66
67    /// Procedure definitions.
68    Procedures(Vec<RawProcedureDefV10>),
69
70    /// View definitions.
71    Views(Vec<RawViewDefV10>),
72
73    /// Schedule definitions.
74    ///
75    /// Unlike V9 where schedules were embedded in table definitions,
76    /// V10 stores them in a dedicated section.
77    Schedules(Vec<RawScheduleDefV10>),
78
79    /// Lifecycle reducer assignments.
80    ///
81    /// Unlike V9 where lifecycle was a field on reducers,
82    /// V10 stores lifecycle-to-reducer mappings separately.
83    LifeCycleReducers(Vec<RawLifeCycleReducerDefV10>),
84
85    RowLevelSecurity(Vec<RawRowLevelSecurityDefV10>), //TODO: Add section for Event tables, and Case conversion before exposing this from module
86
87    /// Case conversion policy for identifiers in this module.
88    CaseConversionPolicy(CaseConversionPolicy),
89
90    /// Names provided explicitly by the user that do not follow from the case conversion policy.
91    ExplicitNames(ExplicitNames),
92
93    /// HTTP handler function definitions.
94    HttpHandlers(Vec<RawHttpHandlerDefV10>),
95
96    /// HTTP route definitions.
97    HttpRoutes(Vec<RawHttpRouteDefV10>),
98
99    /// Primary key metadata for views.
100    ViewPrimaryKeys(Vec<RawViewPrimaryKeyDefV10>),
101
102    /// Submodules, keyed by the namespace they are registered under.
103    Submodules(Vec<RawSubmoduleV10>),
104}
105
106#[derive(Debug, Clone, SpacetimeType)]
107#[sats(crate = crate)]
108#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
109pub struct RawHttpHandlerDefV10 {
110    pub source_name: RawIdentifier,
111}
112
113#[derive(Debug, Clone, SpacetimeType)]
114#[sats(crate = crate)]
115#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
116pub struct RawHttpRouteDefV10 {
117    pub handler_function: RawIdentifier,
118    pub method: MethodOrAny,
119    pub path: RawIdentifier,
120}
121
122#[derive(Debug, Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
123#[sats(crate = crate)]
124#[non_exhaustive]
125pub enum MethodOrAny {
126    Any,
127    Method(crate::http::Method),
128}
129
130#[derive(Debug, Clone, SpacetimeType)]
131#[sats(crate = crate)]
132#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
133pub struct RawSubmoduleV10 {
134    pub namespace: String,
135    pub module: RawModuleDefV10,
136}
137
138#[derive(Debug, Clone, Copy, Default, SpacetimeType)]
139#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
140#[sats(crate = crate)]
141#[non_exhaustive]
142pub enum CaseConversionPolicy {
143    /// No conversion - names used verbatim as canonical names
144    None,
145    /// Convert to snake_case (SpacetimeDB default)
146    #[default]
147    SnakeCase,
148}
149
150#[derive(Debug, Clone, SpacetimeType)]
151#[sats(crate = crate)]
152#[cfg_attr(feature = "test", derive(PartialEq, Eq, Ord, PartialOrd))]
153#[non_exhaustive]
154pub struct NameMapping {
155    /// The original name as defined or generated inside module.
156    ///
157    /// Generated as:
158    /// - Tables: value from `#[spacetimedb::table(accessor = ...)]`.
159    /// - Reducers/Procedures/Views: function name
160    /// - Indexes: `{table_name}_{column_names}_idx_{algorithm}`
161    ///
162    /// During validation, this may be replaced by `canonical_name`
163    /// if an explicit or policy-based name is applied.
164    pub source_name: RawIdentifier,
165
166    /// The canonical identifier used in system tables and client code generation.
167    ///
168    /// Set via:
169    /// - `#[spacetimedb::table(name = "...")]` for tables
170    /// - `#[spacetimedb::reducer(name = "...")]` for reducers
171    /// - `#[name("...")]` for other entities
172    ///
173    /// If not explicitly provided, this defaults to `source_name`
174    /// after validation. No particular format should be assumed.
175    pub canonical_name: RawIdentifier,
176}
177
178#[derive(Debug, Clone, SpacetimeType)]
179#[sats(crate = crate)]
180#[cfg_attr(feature = "test", derive(PartialEq, Eq, Ord, PartialOrd))]
181#[non_exhaustive]
182pub enum ExplicitNameEntry {
183    Table(NameMapping),
184    Function(NameMapping),
185    Index(NameMapping),
186}
187
188#[derive(Debug, Default, Clone, SpacetimeType)]
189#[sats(crate = crate)]
190#[cfg_attr(feature = "test", derive(PartialEq, Eq, Ord, PartialOrd))]
191#[non_exhaustive]
192pub struct ExplicitNames {
193    /// Explicit name mappings defined in the module.
194    ///
195    /// These override policy-based or auto-generated names
196    /// during schema validation.
197    entries: Vec<ExplicitNameEntry>,
198}
199
200impl ExplicitNames {
201    fn insert(&mut self, entry: ExplicitNameEntry) {
202        self.entries.push(entry);
203    }
204
205    pub fn insert_table(&mut self, source_name: impl Into<RawIdentifier>, canonical_name: impl Into<RawIdentifier>) {
206        self.insert(ExplicitNameEntry::Table(NameMapping {
207            source_name: source_name.into(),
208            canonical_name: canonical_name.into(),
209        }));
210    }
211
212    pub fn insert_function(&mut self, source_name: impl Into<RawIdentifier>, canonical_name: impl Into<RawIdentifier>) {
213        self.insert(ExplicitNameEntry::Function(NameMapping {
214            source_name: source_name.into(),
215            canonical_name: canonical_name.into(),
216        }));
217    }
218
219    pub fn insert_index(&mut self, source_name: impl Into<RawIdentifier>, canonical_name: impl Into<RawIdentifier>) {
220        self.insert(ExplicitNameEntry::Index(NameMapping {
221            source_name: source_name.into(),
222            canonical_name: canonical_name.into(),
223        }));
224    }
225
226    pub fn merge(&mut self, other: ExplicitNames) {
227        self.entries.extend(other.entries);
228    }
229
230    pub fn into_entries(self) -> Vec<ExplicitNameEntry> {
231        self.entries
232    }
233}
234
235pub type RawRowLevelSecurityDefV10 = crate::db::raw_def::v9::RawRowLevelSecurityDefV9;
236
237/// The definition of a database table.
238///
239/// This struct holds information about the table, including its name, columns, indexes,
240/// constraints, sequences, type, and access rights.
241///
242/// Validation rules are the same as V9, except:
243/// - Default values are stored inline rather than in `MiscModuleExport`
244/// - Schedules are stored in a separate section rather than embedded here
245#[derive(Debug, Clone, SpacetimeType)]
246#[sats(crate = crate)]
247#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
248pub struct RawTableDefV10 {
249    /// The name of the table.
250    /// Unique within a module, acts as the table's identifier.
251    /// Must be a valid `spacetimedb_schema::identifier::Identifier`.
252    pub source_name: RawIdentifier,
253
254    /// A reference to a `ProductType` containing the columns of this table.
255    /// This is the single source of truth for the table's columns.
256    /// All elements of the `ProductType` must have names.
257    ///
258    /// Like all types in the module, this must have the [default element ordering](crate::db::default_element_ordering),
259    /// UNLESS a custom ordering is declared via a `RawTypeDefV10` for this type.
260    pub product_type_ref: AlgebraicTypeRef,
261
262    /// The primary key of the table, if present. Must refer to a valid column.
263    ///
264    /// Currently, there must be a unique constraint and an index corresponding to the primary key.
265    /// Eventually, we may remove the requirement for an index.
266    ///
267    /// The database engine does not actually care about this, but client code generation does.
268    ///
269    /// A list of length 0 means no primary key. Currently, a list of length >1 is not supported.
270    pub primary_key: ColList,
271
272    /// The indices of the table.
273    pub indexes: Vec<RawIndexDefV10>,
274
275    /// Any unique constraints on the table.
276    pub constraints: Vec<RawConstraintDefV10>,
277
278    /// The sequences for the table.
279    pub sequences: Vec<RawSequenceDefV10>,
280
281    /// Whether this is a system- or user-created table.
282    pub table_type: TableType,
283
284    /// Whether this table is public or private.
285    pub table_access: TableAccess,
286
287    /// Default values for columns in this table.
288    pub default_values: Vec<RawColumnDefaultValueV10>,
289
290    /// Whether this is an event table.
291    ///
292    /// Event tables are write-only: their rows are persisted to the commitlog
293    /// but are NOT merged into committed state. They are only visible to V2
294    /// subscribers in the transaction that inserted them.
295    pub is_event: bool,
296}
297
298/// Marks a particular table column as having a particular default value.
299#[derive(Debug, Clone, SpacetimeType)]
300#[sats(crate = crate)]
301#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
302pub struct RawColumnDefaultValueV10 {
303    /// Identifies which column has the default value.
304    pub col_id: ColId,
305
306    /// A BSATN-encoded [`AlgebraicValue`] valid at the column's type.
307    /// (We cannot use `AlgebraicValue` directly as it isn't `SpacetimeType`.)
308    pub value: Box<[u8]>,
309}
310
311/// A reducer definition.
312#[derive(Debug, Clone, SpacetimeType)]
313#[sats(crate = crate)]
314#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
315pub struct RawReducerDefV10 {
316    /// The name of the reducer.
317    pub source_name: RawIdentifier,
318
319    /// The types and optional names of the parameters, in order.
320    /// This `ProductType` need not be registered in the typespace.
321    pub params: ProductType,
322
323    /// Whether this reducer is callable from clients or is internal-only.
324    pub visibility: FunctionVisibility,
325
326    /// The type of the `Ok` return value.
327    pub ok_return_type: AlgebraicType,
328
329    /// The type of the `Err` return value.
330    pub err_return_type: AlgebraicType,
331}
332
333/// The visibility of a function (reducer or procedure).
334#[derive(Debug, Copy, Clone, SpacetimeType)]
335#[sats(crate = crate)]
336#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
337pub enum FunctionVisibility {
338    /// Not callable by arbitrary clients.
339    ///
340    /// Still callable by the module owner, collaborators,
341    /// and internal module code.
342    ///
343    /// Enabled for lifecycle reducers and scheduled functions by default.
344    Private,
345
346    /// Callable from client code.
347    ClientCallable,
348}
349
350/// A schedule definition.
351#[derive(Debug, Clone, SpacetimeType)]
352#[sats(crate = crate)]
353#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
354pub struct RawScheduleDefV10 {
355    /// In the future, the user may FOR SOME REASON want to override this.
356    /// Even though there is ABSOLUTELY NO REASON TO.
357    /// If `None`, a nicely-formatted unique default will be chosen.
358    pub source_name: Option<RawIdentifier>,
359
360    /// The name of the table containing the schedule.
361    pub table_name: RawIdentifier,
362
363    /// The column of the `scheduled_at` field in the table.
364    pub schedule_at_col: ColId,
365
366    /// The name of the reducer or procedure to call.
367    pub function_name: RawIdentifier,
368}
369
370/// A lifecycle reducer assignment.
371#[derive(Debug, Clone, SpacetimeType)]
372#[sats(crate = crate)]
373#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
374pub struct RawLifeCycleReducerDefV10 {
375    /// Which lifecycle event this reducer handles.
376    pub lifecycle_spec: Lifecycle,
377
378    /// The name of the reducer to call for this lifecycle event.
379    pub function_name: RawIdentifier,
380}
381
382/// A procedure definition.
383#[derive(Debug, Clone, SpacetimeType)]
384#[sats(crate = crate)]
385#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
386pub struct RawProcedureDefV10 {
387    /// The name of the procedure.
388    pub source_name: RawIdentifier,
389
390    /// The types and optional names of the parameters, in order.
391    /// This `ProductType` need not be registered in the typespace.
392    pub params: ProductType,
393
394    /// The type of the return value.
395    ///
396    /// If this is a user-defined product or sum type,
397    /// it should be registered in the typespace and indirected through an [`AlgebraicType::Ref`].
398    pub return_type: AlgebraicType,
399
400    /// Whether this procedure is callable from clients or is internal-only.
401    pub visibility: FunctionVisibility,
402}
403
404/// A sequence definition for a database table column.
405#[derive(Debug, Clone, SpacetimeType)]
406#[sats(crate = crate)]
407#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
408pub struct RawSequenceDefV10 {
409    /// In the future, the user may FOR SOME REASON want to override this.
410    /// Even though there is ABSOLUTELY NO REASON TO.
411    /// If `None`, a nicely-formatted unique default will be chosen.
412    pub source_name: Option<RawIdentifier>,
413
414    /// The position of the column associated with this sequence.
415    /// This refers to a column in the same `RawTableDef` that contains this `RawSequenceDef`.
416    /// The column must have integral type.
417    /// This must be the unique `RawSequenceDef` for this column.
418    pub column: ColId,
419
420    /// The value to start assigning to this column.
421    /// Will be incremented by 1 for each new row.
422    /// If not present, an arbitrary start point may be selected.
423    pub start: Option<i128>,
424
425    /// The minimum allowed value in this column.
426    /// If not present, no minimum.
427    pub min_value: Option<i128>,
428
429    /// The maximum allowed value in this column.
430    /// If not present, no maximum.
431    pub max_value: Option<i128>,
432
433    /// The increment used when updating the SequenceDef.
434    pub increment: i128,
435}
436
437/// The definition of a database index.
438#[derive(Debug, Clone, SpacetimeType)]
439#[sats(crate = crate)]
440#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
441pub struct RawIndexDefV10 {
442    /// Must be supplied as `{table_name}_{column_names}_idx_{algorithm}`.
443    /// Where `{table_name}` is the name of the table containing in `RawTableDefV10`.
444    pub source_name: Option<RawIdentifier>,
445
446    /// `accessor_name` is the name of the index accessor function that is used inside the module
447    /// code.
448    pub accessor_name: Option<RawIdentifier>,
449
450    /// The algorithm parameters for the index.
451    pub algorithm: RawIndexAlgorithm,
452}
453
454/// A constraint definition attached to a table.
455#[derive(Debug, Clone, SpacetimeType)]
456#[sats(crate = crate)]
457#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
458pub struct RawConstraintDefV10 {
459    /// In the future, the user may FOR SOME REASON want to override this.
460    /// Even though there is ABSOLUTELY NO REASON TO.
461    pub source_name: Option<RawIdentifier>,
462
463    /// The data for the constraint.
464    pub data: RawConstraintDataV10,
465}
466
467type RawConstraintDataV10 = crate::db::raw_def::v9::RawConstraintDataV9;
468type RawUniqueConstraintDataV10 = crate::db::raw_def::v9::RawUniqueConstraintDataV9;
469
470/// A type declaration.
471///
472/// Exactly of these must be attached to every `Product` and `Sum` type used by a module.
473#[derive(Debug, Clone, SpacetimeType)]
474#[sats(crate = crate)]
475#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
476pub struct RawTypeDefV10 {
477    /// The name of the type declaration.
478    pub source_name: RawScopedTypeNameV10,
479
480    /// The type to which the declaration refers.
481    /// This must point to an `AlgebraicType::Product` or an `AlgebraicType::Sum` in the module's typespace.
482    pub ty: AlgebraicTypeRef,
483
484    /// Whether this type has a custom ordering.
485    pub custom_ordering: bool,
486}
487
488/// A scoped type name, in the form `scope0::scope1::...::scopeN::name`.
489///
490/// These are the names that will be used *in client code generation*, NOT the names used for types
491/// in the module source code.
492#[derive(Clone, SpacetimeType, PartialEq, Eq, PartialOrd, Ord)]
493#[sats(crate = crate)]
494pub struct RawScopedTypeNameV10 {
495    /// The scope for this type.
496    ///
497    /// Empty unless a sats `name` attribute is used, e.g.
498    /// `#[sats(name = "namespace.name")]` in Rust.
499    pub scope: Box<[RawIdentifier]>,
500
501    /// The name of the type. This must be unique within the module.
502    ///
503    /// Eventually, we may add more information to this, such as generic arguments.
504    pub source_name: RawIdentifier,
505}
506
507impl fmt::Debug for RawScopedTypeNameV10 {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        for module in self.scope.iter() {
510            fmt::Debug::fmt(module, f)?;
511            f.write_str("::")?;
512        }
513        fmt::Debug::fmt(&self.source_name, f)?;
514        Ok(())
515    }
516}
517
518/// A view definition.
519#[derive(Debug, Clone, SpacetimeType)]
520#[sats(crate = crate)]
521#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
522pub struct RawViewDefV10 {
523    /// The name of the view function as defined in the module
524    pub source_name: RawIdentifier,
525
526    /// The index of the view in the module's list of views.
527    pub index: u32,
528
529    /// Is this a public or a private view?
530    /// Currently only public views are supported.
531    /// Private views may be supported in the future.
532    pub is_public: bool,
533
534    /// Is this view anonymous?
535    /// An anonymous view does not know who called it.
536    /// Specifically, it is a view that has an `AnonymousViewContext` as its first argument.
537    /// This type does not have access to the `Identity` of the caller.
538    pub is_anonymous: bool,
539
540    /// The types and optional names of the parameters, in order.
541    /// This `ProductType` need not be registered in the typespace.
542    pub params: ProductType,
543
544    /// The return type of the view.
545    /// Either `T`, `Option<T>`, or `Vec<T>` where `T` is a `SpacetimeType`.
546    ///
547    /// More strictly `T` must be a SATS `ProductType`,
548    /// however this will be validated by the server on publish.
549    ///
550    /// This is the single source of truth for the views's columns.
551    /// All elements of the inner `ProductType` must have names.
552    /// This again will be validated by the server on publish.
553    pub return_type: AlgebraicType,
554}
555
556/// Primary key metadata for a view.
557#[derive(Debug, Clone, SpacetimeType)]
558#[sats(crate = crate)]
559#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
560pub struct RawViewPrimaryKeyDefV10 {
561    /// The source/accessor name of the view this primary key applies to.
562    pub view_source_name: RawIdentifier,
563
564    /// The source/accessor names of the columns that make up the primary key.
565    ///
566    /// Currently only a single column is supported, but this is a vector to keep
567    /// the raw definition compatible with future composite view primary keys.
568    pub columns: Vec<RawIdentifier>,
569}
570
571impl RawModuleDefV10 {
572    /// Get the submodules for this module definition.
573    pub fn submodules(&self) -> Option<&Vec<RawSubmoduleV10>> {
574        self.sections.iter().find_map(|s| match s {
575            RawModuleDefV10Section::Submodules(submodules) => Some(submodules),
576            _ => None,
577        })
578    }
579
580    /// Get the types section, if present.
581    pub fn types(&self) -> Option<&Vec<RawTypeDefV10>> {
582        self.sections.iter().find_map(|s| match s {
583            RawModuleDefV10Section::Types(types) => Some(types),
584            _ => None,
585        })
586    }
587
588    /// Get the tables section, if present.
589    pub fn tables(&self) -> Option<&Vec<RawTableDefV10>> {
590        self.sections.iter().find_map(|s| match s {
591            RawModuleDefV10Section::Tables(tables) => Some(tables),
592            _ => None,
593        })
594    }
595
596    /// Get the typespace section, if present.
597    pub fn typespace(&self) -> Option<&Typespace> {
598        self.sections.iter().find_map(|s| match s {
599            RawModuleDefV10Section::Typespace(ts) => Some(ts),
600            _ => None,
601        })
602    }
603
604    /// Get the reducers section, if present.
605    pub fn reducers(&self) -> Option<&Vec<RawReducerDefV10>> {
606        self.sections.iter().find_map(|s| match s {
607            RawModuleDefV10Section::Reducers(reducers) => Some(reducers),
608            _ => None,
609        })
610    }
611
612    /// Get the procedures section, if present.
613    pub fn procedures(&self) -> Option<&Vec<RawProcedureDefV10>> {
614        self.sections.iter().find_map(|s| match s {
615            RawModuleDefV10Section::Procedures(procedures) => Some(procedures),
616            _ => None,
617        })
618    }
619
620    /// Get the views section, if present.
621    pub fn views(&self) -> Option<&Vec<RawViewDefV10>> {
622        self.sections.iter().find_map(|s| match s {
623            RawModuleDefV10Section::Views(views) => Some(views),
624            _ => None,
625        })
626    }
627
628    /// Get the view primary keys section, if present.
629    pub fn view_primary_keys(&self) -> Option<&Vec<RawViewPrimaryKeyDefV10>> {
630        self.sections.iter().find_map(|s| match s {
631            RawModuleDefV10Section::ViewPrimaryKeys(primary_keys) => Some(primary_keys),
632            _ => None,
633        })
634    }
635
636    /// Get the schedules section, if present.
637    pub fn schedules(&self) -> Option<&Vec<RawScheduleDefV10>> {
638        self.sections.iter().find_map(|s| match s {
639            RawModuleDefV10Section::Schedules(schedules) => Some(schedules),
640            _ => None,
641        })
642    }
643
644    /// Get the lifecycle reducers section, if present.
645    pub fn lifecycle_reducers(&self) -> Option<&Vec<RawLifeCycleReducerDefV10>> {
646        self.sections.iter().find_map(|s| match s {
647            RawModuleDefV10Section::LifeCycleReducers(lcrs) => Some(lcrs),
648            _ => None,
649        })
650    }
651
652    pub fn tables_mut_for_tests(&mut self) -> &mut Vec<RawTableDefV10> {
653        self.sections
654            .iter_mut()
655            .find_map(|s| match s {
656                RawModuleDefV10Section::Tables(tables) => Some(tables),
657                _ => None,
658            })
659            .expect("Tables section must exist for tests")
660    }
661
662    // Get the row-level security section, if present.
663    pub fn row_level_security(&self) -> Option<&Vec<RawRowLevelSecurityDefV10>> {
664        self.sections.iter().find_map(|s| match s {
665            RawModuleDefV10Section::RowLevelSecurity(rls) => Some(rls),
666            _ => None,
667        })
668    }
669
670    pub fn case_conversion_policy(&self) -> CaseConversionPolicy {
671        self.sections
672            .iter()
673            .find_map(|s| match s {
674                RawModuleDefV10Section::CaseConversionPolicy(policy) => Some(*policy),
675                _ => None,
676            })
677            .unwrap_or_default()
678    }
679
680    pub fn explicit_names(&self) -> Option<&ExplicitNames> {
681        self.sections.iter().find_map(|s| match s {
682            RawModuleDefV10Section::ExplicitNames(names) => Some(names),
683            _ => None,
684        })
685    }
686
687    pub fn http_handlers(&self) -> Option<&Vec<RawHttpHandlerDefV10>> {
688        self.sections.iter().find_map(|s| match s {
689            RawModuleDefV10Section::HttpHandlers(handlers) => Some(handlers),
690            _ => None,
691        })
692    }
693
694    pub fn http_routes(&self) -> Option<&Vec<RawHttpRouteDefV10>> {
695        self.sections.iter().find_map(|s| match s {
696            RawModuleDefV10Section::HttpRoutes(routes) => Some(routes),
697            _ => None,
698        })
699    }
700}
701
702/// A builder for a [`RawModuleDefV10`].
703#[derive(Default)]
704pub struct RawModuleDefV10Builder {
705    /// The module definition being built.
706    module: RawModuleDefV10,
707
708    /// The type map from `T: 'static` Rust types to sats types.
709    type_map: BTreeMap<TypeId, AlgebraicTypeRef>,
710}
711
712impl RawModuleDefV10Builder {
713    /// Create a new, empty `RawModuleDefV10Builder`.
714    pub fn new() -> Self {
715        Default::default()
716    }
717
718    /// Get mutable access to the typespace section, creating it if missing.
719    fn typespace_mut(&mut self) -> &mut Typespace {
720        let idx = self
721            .module
722            .sections
723            .iter()
724            .position(|s| matches!(s, RawModuleDefV10Section::Typespace(_)))
725            .unwrap_or_else(|| {
726                self.module
727                    .sections
728                    .push(RawModuleDefV10Section::Typespace(Typespace::EMPTY.clone()));
729                self.module.sections.len() - 1
730            });
731
732        match &mut self.module.sections[idx] {
733            RawModuleDefV10Section::Typespace(ts) => ts,
734            _ => unreachable!("Just ensured Typespace section exists"),
735        }
736    }
737
738    /// Get mutable access to the reducers section, creating it if missing.
739    fn reducers_mut(&mut self) -> &mut Vec<RawReducerDefV10> {
740        let idx = self
741            .module
742            .sections
743            .iter()
744            .position(|s| matches!(s, RawModuleDefV10Section::Reducers(_)))
745            .unwrap_or_else(|| {
746                self.module.sections.push(RawModuleDefV10Section::Reducers(Vec::new()));
747                self.module.sections.len() - 1
748            });
749
750        match &mut self.module.sections[idx] {
751            RawModuleDefV10Section::Reducers(reducers) => reducers,
752            _ => unreachable!("Just ensured Reducers section exists"),
753        }
754    }
755
756    /// Get mutable access to the procedures section, creating it if missing.
757    fn procedures_mut(&mut self) -> &mut Vec<RawProcedureDefV10> {
758        let idx = self
759            .module
760            .sections
761            .iter()
762            .position(|s| matches!(s, RawModuleDefV10Section::Procedures(_)))
763            .unwrap_or_else(|| {
764                self.module
765                    .sections
766                    .push(RawModuleDefV10Section::Procedures(Vec::new()));
767                self.module.sections.len() - 1
768            });
769
770        match &mut self.module.sections[idx] {
771            RawModuleDefV10Section::Procedures(procedures) => procedures,
772            _ => unreachable!("Just ensured Procedures section exists"),
773        }
774    }
775
776    /// Get mutable access to the views section, creating it if missing.
777    fn views_mut(&mut self) -> &mut Vec<RawViewDefV10> {
778        let idx = self
779            .module
780            .sections
781            .iter()
782            .position(|s| matches!(s, RawModuleDefV10Section::Views(_)))
783            .unwrap_or_else(|| {
784                self.module.sections.push(RawModuleDefV10Section::Views(Vec::new()));
785                self.module.sections.len() - 1
786            });
787
788        match &mut self.module.sections[idx] {
789            RawModuleDefV10Section::Views(views) => views,
790            _ => unreachable!("Just ensured Views section exists"),
791        }
792    }
793
794    /// Get mutable access to the view primary keys section, creating it if missing.
795    fn view_primary_keys_mut(&mut self) -> &mut Vec<RawViewPrimaryKeyDefV10> {
796        let idx = self
797            .module
798            .sections
799            .iter()
800            .position(|s| matches!(s, RawModuleDefV10Section::ViewPrimaryKeys(_)))
801            .unwrap_or_else(|| {
802                self.module
803                    .sections
804                    .push(RawModuleDefV10Section::ViewPrimaryKeys(Vec::new()));
805                self.module.sections.len() - 1
806            });
807
808        match &mut self.module.sections[idx] {
809            RawModuleDefV10Section::ViewPrimaryKeys(primary_keys) => primary_keys,
810            _ => unreachable!("Just ensured ViewPrimaryKeys section exists"),
811        }
812    }
813
814    /// Get mutable access to the schedules section, creating it if missing.
815    fn schedules_mut(&mut self) -> &mut Vec<RawScheduleDefV10> {
816        let idx = self
817            .module
818            .sections
819            .iter()
820            .position(|s| matches!(s, RawModuleDefV10Section::Schedules(_)))
821            .unwrap_or_else(|| {
822                self.module.sections.push(RawModuleDefV10Section::Schedules(Vec::new()));
823                self.module.sections.len() - 1
824            });
825
826        match &mut self.module.sections[idx] {
827            RawModuleDefV10Section::Schedules(schedules) => schedules,
828            _ => unreachable!("Just ensured Schedules section exists"),
829        }
830    }
831
832    /// Get mutable access to the lifecycle reducers section, creating it if missing.
833    fn lifecycle_reducers_mut(&mut self) -> &mut Vec<RawLifeCycleReducerDefV10> {
834        let idx = self
835            .module
836            .sections
837            .iter()
838            .position(|s| matches!(s, RawModuleDefV10Section::LifeCycleReducers(_)))
839            .unwrap_or_else(|| {
840                self.module
841                    .sections
842                    .push(RawModuleDefV10Section::LifeCycleReducers(Vec::new()));
843                self.module.sections.len() - 1
844            });
845
846        match &mut self.module.sections[idx] {
847            RawModuleDefV10Section::LifeCycleReducers(lcrs) => lcrs,
848            _ => unreachable!("Just ensured LifeCycleReducers section exists"),
849        }
850    }
851
852    /// Get mutable access to the types section, creating it if missing.
853    fn types_mut(&mut self) -> &mut Vec<RawTypeDefV10> {
854        let idx = self
855            .module
856            .sections
857            .iter()
858            .position(|s| matches!(s, RawModuleDefV10Section::Types(_)))
859            .unwrap_or_else(|| {
860                self.module.sections.push(RawModuleDefV10Section::Types(Vec::new()));
861                self.module.sections.len() - 1
862            });
863
864        match &mut self.module.sections[idx] {
865            RawModuleDefV10Section::Types(types) => types,
866            _ => unreachable!("Just ensured Types section exists"),
867        }
868    }
869
870    /// Add a type to the in-progress module.
871    ///
872    /// The returned type must satisfy `AlgebraicType::is_valid_for_client_type_definition` or `AlgebraicType::is_valid_for_client_type_use`.
873    pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
874        TypespaceBuilder::add_type::<T>(self)
875    }
876
877    /// Get mutable access to the row-level security section, creating it if missing.
878    fn row_level_security_mut(&mut self) -> &mut Vec<RawRowLevelSecurityDefV10> {
879        let idx = self
880            .module
881            .sections
882            .iter()
883            .position(|s| matches!(s, RawModuleDefV10Section::RowLevelSecurity(_)))
884            .unwrap_or_else(|| {
885                self.module
886                    .sections
887                    .push(RawModuleDefV10Section::RowLevelSecurity(Vec::new()));
888                self.module.sections.len() - 1
889            });
890
891        match &mut self.module.sections[idx] {
892            RawModuleDefV10Section::RowLevelSecurity(rls) => rls,
893            _ => unreachable!("Just ensured RowLevelSecurity section exists"),
894        }
895    }
896
897    /// Get mutable access to the case conversion policy, creating it if missing.
898    fn explicit_names_mut(&mut self) -> &mut ExplicitNames {
899        let idx = self
900            .module
901            .sections
902            .iter()
903            .position(|s| matches!(s, RawModuleDefV10Section::ExplicitNames(_)))
904            .unwrap_or_else(|| {
905                self.module
906                    .sections
907                    .push(RawModuleDefV10Section::ExplicitNames(ExplicitNames::default()));
908                self.module.sections.len() - 1
909            });
910
911        match &mut self.module.sections[idx] {
912            RawModuleDefV10Section::ExplicitNames(names) => names,
913            _ => unreachable!("Just ensured ExplicitNames section exists"),
914        }
915    }
916
917    /// Get mutable access to the HTTP handlers section, creating it if missing.
918    fn http_handlers_mut(&mut self) -> &mut Vec<RawHttpHandlerDefV10> {
919        let idx = self
920            .module
921            .sections
922            .iter()
923            .position(|s| matches!(s, RawModuleDefV10Section::HttpHandlers(_)))
924            .unwrap_or_else(|| {
925                self.module
926                    .sections
927                    .push(RawModuleDefV10Section::HttpHandlers(Vec::new()));
928                self.module.sections.len() - 1
929            });
930
931        match &mut self.module.sections[idx] {
932            RawModuleDefV10Section::HttpHandlers(handlers) => handlers,
933            _ => unreachable!("Just ensured HttpHandlers section exists"),
934        }
935    }
936
937    /// Get mutable access to the HTTP routes section, creating it if missing.
938    fn http_routes_mut(&mut self) -> &mut Vec<RawHttpRouteDefV10> {
939        let idx = self
940            .module
941            .sections
942            .iter()
943            .position(|s| matches!(s, RawModuleDefV10Section::HttpRoutes(_)))
944            .unwrap_or_else(|| {
945                self.module
946                    .sections
947                    .push(RawModuleDefV10Section::HttpRoutes(Vec::new()));
948                self.module.sections.len() - 1
949            });
950
951        match &mut self.module.sections[idx] {
952            RawModuleDefV10Section::HttpRoutes(routes) => routes,
953            _ => unreachable!("Just ensured HttpRoutes section exists"),
954        }
955    }
956
957    /// Create a table builder.
958    ///
959    /// Does not validate that the product_type_ref is valid; this is left to the module validation code.
960    pub fn build_table(
961        &mut self,
962        source_name: impl Into<RawIdentifier>,
963        product_type_ref: AlgebraicTypeRef,
964    ) -> RawTableDefBuilderV10<'_> {
965        let source_name = source_name.into();
966        RawTableDefBuilderV10 {
967            module: &mut self.module,
968            table: RawTableDefV10 {
969                source_name,
970                product_type_ref,
971                indexes: vec![],
972                constraints: vec![],
973                sequences: vec![],
974                primary_key: ColList::empty(),
975                table_type: TableType::User,
976                table_access: TableAccess::Public,
977                default_values: vec![],
978                is_event: false,
979            },
980        }
981    }
982
983    /// Build a new table with a product type.
984    /// Adds the type to the module.
985    pub fn build_table_with_new_type(
986        &mut self,
987        table_name: impl Into<RawIdentifier>,
988        product_type: impl Into<ProductType>,
989        custom_ordering: bool,
990    ) -> RawTableDefBuilderV10<'_> {
991        let table_name = table_name.into();
992
993        let product_type_ref = self.add_algebraic_type(
994            [],
995            table_name.clone(),
996            AlgebraicType::from(product_type.into()),
997            custom_ordering,
998        );
999
1000        self.build_table(table_name, product_type_ref)
1001    }
1002
1003    /// Build a new table with a product type, for testing.
1004    /// Adds the type to the module.
1005    pub fn build_table_with_new_type_for_tests(
1006        &mut self,
1007        table_name: impl Into<RawIdentifier>,
1008        mut product_type: ProductType,
1009        custom_ordering: bool,
1010    ) -> RawTableDefBuilderV10<'_> {
1011        self.add_expand_product_type_for_tests(&mut 0, &mut product_type);
1012        self.build_table_with_new_type(table_name, product_type, custom_ordering)
1013    }
1014
1015    fn add_expand_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut AlgebraicType) {
1016        if ty.is_valid_for_client_type_use() {
1017            return;
1018        }
1019
1020        match ty {
1021            AlgebraicType::Product(prod_ty) => self.add_expand_product_type_for_tests(name_gen, prod_ty),
1022            AlgebraicType::Sum(sum_type) => {
1023                if let Some(wrapped) = sum_type.as_option_mut() {
1024                    self.add_expand_type_for_tests(name_gen, wrapped);
1025                } else {
1026                    for elem in sum_type.variants.iter_mut() {
1027                        self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
1028                    }
1029                }
1030            }
1031            AlgebraicType::Array(ty) => {
1032                self.add_expand_type_for_tests(name_gen, &mut ty.elem_ty);
1033                return;
1034            }
1035            _ => return,
1036        }
1037
1038        // Make the type into a ref.
1039        let name = *name_gen;
1040        let add_ty = core::mem::replace(ty, AlgebraicType::U8);
1041        *ty = AlgebraicType::Ref(self.add_algebraic_type([], RawIdentifier::new(format!("gen_{name}")), add_ty, true));
1042        *name_gen += 1;
1043    }
1044
1045    fn add_expand_product_type_for_tests(&mut self, name_gen: &mut usize, ty: &mut ProductType) {
1046        for elem in ty.elements.iter_mut() {
1047            self.add_expand_type_for_tests(name_gen, &mut elem.algebraic_type);
1048        }
1049    }
1050
1051    /// Add a type to the typespace, along with a type alias declaring its name.
1052    /// This method should only be used for `AlgebraicType`s not corresponding to a Rust
1053    /// type that implements `SpacetimeType`.
1054    ///
1055    /// Returns a reference to the newly-added type.
1056    ///
1057    /// NOT idempotent, calling this twice with the same name will cause errors during validation.
1058    ///
1059    /// You must set `custom_ordering` if you're not using the default element ordering.
1060    pub fn add_algebraic_type(
1061        &mut self,
1062        scope: impl IntoIterator<Item = RawIdentifier>,
1063        source_name: impl Into<RawIdentifier>,
1064        ty: AlgebraicType,
1065        custom_ordering: bool,
1066    ) -> AlgebraicTypeRef {
1067        let ty_ref = self.typespace_mut().add(ty);
1068        let scope = scope.into_iter().collect();
1069        let source_name = source_name.into();
1070        self.types_mut().push(RawTypeDefV10 {
1071            source_name: RawScopedTypeNameV10 { source_name, scope },
1072            ty: ty_ref,
1073            custom_ordering,
1074        });
1075        // We don't add a `TypeId` to `self.type_map`, because there may not be a corresponding Rust type!
1076        // e.g. if we are randomly generating types in proptests.
1077        ty_ref
1078    }
1079
1080    /// Add a reducer to the in-progress module.
1081    /// Accepts a `ProductType` of reducer arguments for convenience.
1082    /// The `ProductType` need not be registered in the typespace.
1083    ///
1084    /// Importantly, if the reducer's first argument is a `ReducerContext`, that
1085    /// information should not be provided to this method.
1086    /// That is an implementation detail handled by the module bindings and can be ignored.
1087    /// As far as the module definition is concerned, the reducer's arguments
1088    /// start with the first non-`ReducerContext` argument.
1089    ///
1090    /// (It is impossible, with the current implementation of `ReducerContext`, to
1091    /// have more than one `ReducerContext` argument, at least in Rust.
1092    /// This is because `SpacetimeType` is not implemented for `ReducerContext`,
1093    /// so it can never act like an ordinary argument.)
1094    pub fn add_reducer(&mut self, source_name: impl Into<RawIdentifier>, params: ProductType) {
1095        self.reducers_mut().push(RawReducerDefV10 {
1096            source_name: source_name.into(),
1097            params,
1098            visibility: FunctionVisibility::ClientCallable,
1099            ok_return_type: reducer_default_ok_return_type(),
1100            err_return_type: reducer_default_err_return_type(),
1101        });
1102    }
1103
1104    /// Add a procedure to the in-progress module.
1105    ///
1106    /// Accepts a `ProductType` of arguments.
1107    /// The arguments `ProductType` need not be registered in the typespace.
1108    ///
1109    /// Also accepts an `AlgebraicType` return type.
1110    /// If this is a user-defined product or sum type,
1111    /// it should be registered in the typespace and indirected through an `AlgebraicType::Ref`.
1112    ///
1113    /// The `&mut ProcedureContext` first argument to the procedure should not be included in the `params`.
1114    pub fn add_procedure(
1115        &mut self,
1116        source_name: impl Into<RawIdentifier>,
1117        params: ProductType,
1118        return_type: AlgebraicType,
1119    ) {
1120        self.procedures_mut().push(RawProcedureDefV10 {
1121            source_name: source_name.into(),
1122            params,
1123            return_type,
1124            visibility: FunctionVisibility::ClientCallable,
1125        })
1126    }
1127
1128    /// Add a view to the in-progress module.
1129    pub fn add_view(
1130        &mut self,
1131        source_name: impl Into<RawIdentifier>,
1132        index: usize,
1133        is_public: bool,
1134        is_anonymous: bool,
1135        params: ProductType,
1136        return_type: AlgebraicType,
1137    ) {
1138        self.views_mut().push(RawViewDefV10 {
1139            source_name: source_name.into(),
1140            index: index as u32,
1141            is_public,
1142            is_anonymous,
1143            params,
1144            return_type,
1145        });
1146    }
1147
1148    /// Add primary key metadata for a view.
1149    pub fn add_view_primary_key<C, I>(&mut self, view_source_name: impl Into<RawIdentifier>, columns: I)
1150    where
1151        C: Into<RawIdentifier>,
1152        I: IntoIterator<Item = C>,
1153    {
1154        self.view_primary_keys_mut().push(RawViewPrimaryKeyDefV10 {
1155            view_source_name: view_source_name.into(),
1156            columns: columns.into_iter().map(Into::into).collect(),
1157        });
1158    }
1159
1160    /// Add a lifecycle reducer assignment to the module.
1161    ///
1162    /// The function must be a previously-added reducer.
1163    pub fn add_lifecycle_reducer(
1164        &mut self,
1165        lifecycle_spec: Lifecycle,
1166        function_name: impl Into<RawIdentifier>,
1167        params: ProductType,
1168    ) {
1169        let function_name = function_name.into();
1170        self.lifecycle_reducers_mut().push(RawLifeCycleReducerDefV10 {
1171            lifecycle_spec,
1172            function_name: function_name.clone(),
1173        });
1174
1175        self.reducers_mut().push(RawReducerDefV10 {
1176            source_name: function_name,
1177            params,
1178            visibility: FunctionVisibility::Private,
1179            ok_return_type: reducer_default_ok_return_type(),
1180            err_return_type: reducer_default_err_return_type(),
1181        });
1182    }
1183
1184    /// Add a schedule definition to the module.
1185    ///
1186    /// The `function_name` should name a reducer or procedure
1187    /// which accepts one argument, a row of the specified table.
1188    ///
1189    /// The table must have the appropriate columns for a scheduled table.
1190    pub fn add_schedule(
1191        &mut self,
1192        table: impl Into<RawIdentifier>,
1193        column: impl Into<ColId>,
1194        function: impl Into<RawIdentifier>,
1195    ) {
1196        self.schedules_mut().push(RawScheduleDefV10 {
1197            source_name: None,
1198            table_name: table.into(),
1199            schedule_at_col: column.into(),
1200            function_name: function.into(),
1201        });
1202    }
1203
1204    /// Add a row-level security policy to the module.
1205    ///
1206    /// The `sql` expression should be a valid SQL expression that will be used to filter rows.
1207    ///
1208    /// **NOTE**: The `sql` expression must be unique within the module.
1209    pub fn add_row_level_security(&mut self, sql: &str) {
1210        self.row_level_security_mut()
1211            .push(RawRowLevelSecurityDefV10 { sql: sql.into() });
1212    }
1213
1214    /// Add an HTTP handler to the module.
1215    pub fn add_http_handler(&mut self, source_name: impl Into<RawIdentifier>) {
1216        self.http_handlers_mut().push(RawHttpHandlerDefV10 {
1217            source_name: source_name.into(),
1218        });
1219    }
1220
1221    /// Add an HTTP route to the module.
1222    pub fn add_http_route(
1223        &mut self,
1224        handler_function: impl Into<RawIdentifier>,
1225        method: MethodOrAny,
1226        path: impl Into<RawIdentifier>,
1227    ) {
1228        self.http_routes_mut().push(RawHttpRouteDefV10 {
1229            handler_function: handler_function.into(),
1230            method,
1231            path: path.into(),
1232        });
1233    }
1234
1235    pub fn add_explicit_names(&mut self, names: ExplicitNames) {
1236        self.explicit_names_mut().merge(names);
1237    }
1238
1239    /// Set the case conversion policy for this module.
1240    ///
1241    /// By default, SpacetimeDB applies `SnakeCase` conversion to table names,
1242    /// column names, reducer names, etc. Use `CaseConversionPolicy::None` to
1243    /// disable all case conversion (useful for modules with existing data that
1244    /// was stored under the original naming convention).
1245    pub fn set_case_conversion_policy(&mut self, policy: CaseConversionPolicy) {
1246        // Remove any existing policy section.
1247        self.module
1248            .sections
1249            .retain(|s| !matches!(s, RawModuleDefV10Section::CaseConversionPolicy(_)));
1250        self.module
1251            .sections
1252            .push(RawModuleDefV10Section::CaseConversionPolicy(policy));
1253    }
1254
1255    /// Finish building, consuming the builder and returning the module.
1256    /// The module should be validated before use.
1257    pub fn finish(self) -> RawModuleDefV10 {
1258        self.module
1259    }
1260}
1261
1262/// Implement TypespaceBuilder for V10
1263impl TypespaceBuilder for RawModuleDefV10Builder {
1264    fn add(
1265        &mut self,
1266        typeid: TypeId,
1267        source_name: Option<&'static str>,
1268        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
1269    ) -> AlgebraicType {
1270        if let btree_map::Entry::Occupied(o) = self.type_map.entry(typeid) {
1271            AlgebraicType::Ref(*o.get())
1272        } else {
1273            let slot_ref = {
1274                let ts = self.typespace_mut();
1275                // Bind a fresh alias to the unit type.
1276                let slot_ref = ts.add(AlgebraicType::unit());
1277                // Relate `typeid -> fresh alias`.
1278                self.type_map.insert(typeid, slot_ref);
1279
1280                // Alias provided? Relate `name -> slot_ref`.
1281                if let Some(sats_name) = source_name {
1282                    let source_name = sats_name_to_scoped_name_v10(sats_name);
1283
1284                    self.types_mut().push(RawTypeDefV10 {
1285                        source_name,
1286                        ty: slot_ref,
1287                        // TODO(1.0): we need to update the `TypespaceBuilder` trait to include
1288                        // a `custom_ordering` parameter.
1289                        // For now, we assume all types have custom orderings, since the derive
1290                        // macro doesn't know about the default ordering yet.
1291                        custom_ordering: true,
1292                    });
1293                }
1294                slot_ref
1295            };
1296
1297            // Borrow of `v` has ended here, so we can now convince the borrow checker.
1298            let ty = make_ty(self);
1299            self.typespace_mut()[slot_ref] = ty;
1300            AlgebraicType::Ref(slot_ref)
1301        }
1302    }
1303}
1304
1305pub fn reducer_default_ok_return_type() -> AlgebraicType {
1306    AlgebraicType::unit()
1307}
1308
1309pub fn reducer_default_err_return_type() -> AlgebraicType {
1310    AlgebraicType::String
1311}
1312
1313/// Convert a string from a sats type-name annotation like `#[sats(name = "namespace.name")]` to a `RawScopedTypeNameV9`.
1314/// We split the input on the strings `"::"` and `"."` to split up module paths.
1315///
1316pub fn sats_name_to_scoped_name_v10(sats_name: &str) -> RawScopedTypeNameV10 {
1317    // We can't use `&[char]: Pattern` for `split` here because "::" is not a char :/
1318    let mut scope: Vec<RawIdentifier> = sats_name
1319        .split("::")
1320        .flat_map(|s| s.split('.'))
1321        .map(RawIdentifier::new)
1322        .collect();
1323    // Unwrapping to "" will result in a validation error down the line, which is exactly what we want.
1324    let source_name = scope.pop().unwrap_or_default();
1325    RawScopedTypeNameV10 {
1326        scope: scope.into(),
1327        source_name,
1328    }
1329}
1330
1331/// Builder for a `RawTableDefV10`.
1332pub struct RawTableDefBuilderV10<'a> {
1333    module: &'a mut RawModuleDefV10,
1334    table: RawTableDefV10,
1335}
1336
1337impl RawTableDefBuilderV10<'_> {
1338    /// Set the table type.
1339    ///
1340    /// This is not about column algebraic types, but about whether the table
1341    /// was created by the system or the user.
1342    pub fn with_type(mut self, table_type: TableType) -> Self {
1343        self.table.table_type = table_type;
1344        self
1345    }
1346
1347    /// Sets the access rights for the table and return it.
1348    pub fn with_access(mut self, table_access: TableAccess) -> Self {
1349        self.table.table_access = table_access;
1350        self
1351    }
1352
1353    /// Sets whether this table is an event table.
1354    pub fn with_event(mut self, is_event: bool) -> Self {
1355        self.table.is_event = is_event;
1356        self
1357    }
1358
1359    /// Generates a `RawConstraintDefV10` using the supplied `columns`.
1360    pub fn with_unique_constraint(mut self, columns: impl Into<ColList>) -> Self {
1361        let columns = columns.into();
1362        self.table.constraints.push(RawConstraintDefV10 {
1363            source_name: None,
1364            data: RawConstraintDataV10::Unique(RawUniqueConstraintDataV10 { columns }),
1365        });
1366
1367        self
1368    }
1369
1370    /// Adds a primary key to the table.
1371    /// You must also add a unique constraint on the primary key column.
1372    pub fn with_primary_key(mut self, column: impl Into<ColId>) -> Self {
1373        self.table.primary_key = ColList::new(column.into());
1374        self
1375    }
1376
1377    /// Adds a primary key to the table, with corresponding unique constraint and sequence definitions.
1378    /// You will also need to call [`Self::with_index`] to create an index on `column`.
1379    pub fn with_auto_inc_primary_key(self, column: impl Into<ColId>) -> Self {
1380        let column = column.into();
1381        self.with_primary_key(column)
1382            .with_unique_constraint(column)
1383            .with_column_sequence(column)
1384    }
1385
1386    /// Generates a [RawIndexDefV10] using the supplied `columns`.
1387    pub fn with_index(
1388        mut self,
1389        algorithm: RawIndexAlgorithm,
1390        source_name: impl Into<RawIdentifier>,
1391        accessor_name: impl Into<RawIdentifier>,
1392    ) -> Self {
1393        self.table.indexes.push(RawIndexDefV10 {
1394            source_name: Some(source_name.into()),
1395            accessor_name: Some(accessor_name.into()),
1396            algorithm,
1397        });
1398        self
1399    }
1400
1401    /// Generates a [RawIndexDefV10] using the supplied `columns`.
1402    pub fn with_index_no_accessor_name(
1403        mut self,
1404        algorithm: RawIndexAlgorithm,
1405        source_name: impl Into<RawIdentifier>,
1406    ) -> Self {
1407        self.table.indexes.push(RawIndexDefV10 {
1408            source_name: Some(source_name.into()),
1409            accessor_name: None,
1410            algorithm,
1411        });
1412        self
1413    }
1414
1415    /// Adds a [RawSequenceDefV10] on the supplied `column`.
1416    pub fn with_column_sequence(mut self, column: impl Into<ColId>) -> Self {
1417        let column = column.into();
1418        self.table.sequences.push(RawSequenceDefV10 {
1419            source_name: None,
1420            column,
1421            start: None,
1422            min_value: None,
1423            max_value: None,
1424            increment: 1,
1425        });
1426
1427        self
1428    }
1429
1430    /// Adds a default value for a column.
1431    pub fn with_default_column_value(mut self, column: impl Into<ColId>, value: AlgebraicValue) -> Self {
1432        let col_id = column.into();
1433        self.table.default_values.push(RawColumnDefaultValueV10 {
1434            col_id,
1435            value: spacetimedb_sats::bsatn::to_vec(&value).unwrap().into(),
1436        });
1437
1438        self
1439    }
1440
1441    /// Build the table and add it to the module, returning the `product_type_ref` of the table.
1442    pub fn finish(self) -> AlgebraicTypeRef {
1443        let product_type_ref = self.table.product_type_ref;
1444
1445        let tables = match self
1446            .module
1447            .sections
1448            .iter_mut()
1449            .find(|s| matches!(s, RawModuleDefV10Section::Tables(_)))
1450        {
1451            Some(RawModuleDefV10Section::Tables(t)) => t,
1452            _ => {
1453                self.module.sections.push(RawModuleDefV10Section::Tables(Vec::new()));
1454                match self.module.sections.last_mut().expect("Just pushed Tables section") {
1455                    RawModuleDefV10Section::Tables(t) => t,
1456                    _ => unreachable!(),
1457                }
1458            }
1459        };
1460
1461        tables.push(self.table);
1462        product_type_ref
1463    }
1464
1465    /// Find a column position by its name in the table's product type.
1466    pub fn find_col_pos_by_name(&self, column: impl AsRef<str>) -> Option<ColId> {
1467        let column = column.as_ref();
1468
1469        let typespace = self.module.sections.iter().find_map(|s| {
1470            if let RawModuleDefV10Section::Typespace(ts) = s {
1471                Some(ts)
1472            } else {
1473                None
1474            }
1475        })?;
1476
1477        typespace
1478            .get(self.table.product_type_ref)?
1479            .as_product()?
1480            .elements
1481            .iter()
1482            .position(|x| x.has_name(column.as_ref()))
1483            .map(|i| ColId(i as u16))
1484    }
1485}