1use crate::db::raw_def::v9::RawModuleDefV9Builder;
2use crate::db::raw_def::RawTableDefV8;
3use anyhow::Context;
4use sats::typespace::TypespaceBuilder;
5use spacetimedb_sats::WithTypespace;
6use std::any::TypeId;
7use std::collections::{btree_map, BTreeMap};
8
9pub mod connection_id;
10pub mod db;
11mod direct_index_key;
12pub mod error;
13mod filterable_value;
14pub mod identity;
15pub mod metrics;
16pub mod operator;
17pub mod query;
18pub mod scheduler;
19pub mod st_var;
20pub mod version;
21
22pub mod type_def {
23    pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
24}
25pub mod type_value {
26    pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
27}
28
29pub use connection_id::ConnectionId;
30pub use direct_index_key::{assert_column_type_valid_for_direct_index, DirectIndexKey};
31#[doc(hidden)]
32pub use filterable_value::Private;
33pub use filterable_value::{FilterableValue, IndexScanRangeBoundsTerminator, TermBound};
34pub use identity::Identity;
35pub use scheduler::ScheduleAt;
36pub use spacetimedb_sats::hash::{self, hash_bytes, Hash};
37pub use spacetimedb_sats::time_duration::TimeDuration;
38pub use spacetimedb_sats::timestamp::Timestamp;
39pub use spacetimedb_sats::SpacetimeType;
40pub use spacetimedb_sats::__make_register_reftype;
41pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser};
42pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
43pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
44
45pub const MODULE_ABI_MAJOR_VERSION: u16 = 10;
46
47#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
49pub struct VersionTuple {
50    pub major: u16,
52    pub minor: u16,
55}
56
57impl VersionTuple {
58    pub const fn new(major: u16, minor: u16) -> Self {
59        Self { major, minor }
60    }
61
62    #[inline]
63    pub const fn eq(self, other: Self) -> bool {
64        self.major == other.major && self.minor == other.minor
65    }
66
67    #[inline]
69    pub const fn supports(self, module_version: VersionTuple) -> bool {
70        self.major == module_version.major && self.minor >= module_version.minor
71    }
72
73    #[inline]
74    pub const fn from_u32(v: u32) -> Self {
75        let major = (v >> 16) as u16;
76        let minor = (v & 0xFF) as u16;
77        Self { major, minor }
78    }
79
80    #[inline]
81    pub const fn to_u32(self) -> u32 {
82        (self.major as u32) << 16 | self.minor as u32
83    }
84}
85
86impl std::fmt::Display for VersionTuple {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        let Self { major, minor } = *self;
89        write!(f, "{major}.{minor}")
90    }
91}
92
93extern crate self as spacetimedb_lib;
94
95#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
97#[sats(crate = crate)]
98pub struct TableDesc {
99    pub schema: RawTableDefV8,
100    pub data: sats::AlgebraicTypeRef,
102}
103
104impl TableDesc {
105    pub fn into_table_def(table: WithTypespace<'_, TableDesc>) -> anyhow::Result<RawTableDefV8> {
106        let schema = table
107            .map(|t| &t.data)
108            .resolve_refs()
109            .context("recursive types not yet supported")?;
110        let schema = schema.into_product().ok().context("table not a product type?")?;
111        let table = table.ty();
112        anyhow::ensure!(
113            table.schema.columns.len() == schema.elements.len(),
114            "mismatched number of columns"
115        );
116
117        Ok(table.schema.clone())
118    }
119}
120
121#[derive(Debug, Clone, SpacetimeType)]
122#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
123#[sats(crate = crate)]
124pub struct ReducerDef {
125    pub name: Box<str>,
126    pub args: Vec<ProductTypeElement>,
127}
128
129#[derive(Debug, Clone, Default, SpacetimeType)]
131#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
132#[sats(crate = crate)]
133pub struct RawModuleDefV8 {
134    pub typespace: sats::Typespace,
135    pub tables: Vec<TableDesc>,
136    pub reducers: Vec<ReducerDef>,
137    pub misc_exports: Vec<MiscModuleExport>,
138}
139
140impl RawModuleDefV8 {
141    pub fn builder() -> ModuleDefBuilder {
142        ModuleDefBuilder::default()
143    }
144
145    pub fn with_builder(f: impl FnOnce(&mut ModuleDefBuilder)) -> Self {
146        let mut builder = Self::builder();
147        f(&mut builder);
148        builder.finish()
149    }
150}
151
152#[derive(Debug, Clone, SpacetimeType)]
156#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
157#[sats(crate = crate)]
158#[non_exhaustive]
159pub enum RawModuleDef {
160    V8BackCompat(RawModuleDefV8),
161    V9(db::raw_def::v9::RawModuleDefV9),
162    }
165
166#[derive(Default)]
169pub struct ModuleDefBuilder {
170    module: RawModuleDefV8,
172    type_map: BTreeMap<TypeId, sats::AlgebraicTypeRef>,
174}
175
176impl ModuleDefBuilder {
177    pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
178        TypespaceBuilder::add_type::<T>(self)
179    }
180
181    #[cfg(feature = "test")]
184    pub fn add_type_for_tests(&mut self, name: &str, ty: AlgebraicType) -> spacetimedb_sats::AlgebraicTypeRef {
185        let slot_ref = self.module.typespace.add(ty);
186        self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
187            name: name.to_owned(),
188            ty: slot_ref,
189        }));
190        slot_ref
191    }
192
193    #[cfg(feature = "test")]
198    pub fn add_table_for_tests(&mut self, schema: RawTableDefV8) -> spacetimedb_sats::AlgebraicTypeRef {
199        let ty: ProductType = schema
200            .columns
201            .iter()
202            .map(|c| ProductTypeElement {
203                name: Some(c.col_name.clone()),
204                algebraic_type: c.col_type.clone(),
205            })
206            .collect();
207        let data = self.module.typespace.add(ty.into());
208        self.add_type_alias(TypeAlias {
209            name: schema.table_name.clone().into(),
210            ty: data,
211        });
212        self.add_table(TableDesc { schema, data });
213        data
214    }
215
216    pub fn add_table(&mut self, table: TableDesc) {
217        self.module.tables.push(table)
218    }
219
220    pub fn add_reducer(&mut self, reducer: ReducerDef) {
221        self.module.reducers.push(reducer)
222    }
223
224    #[cfg(feature = "test")]
225    pub fn add_reducer_for_tests(&mut self, name: impl Into<Box<str>>, args: ProductType) {
226        self.add_reducer(ReducerDef {
227            name: name.into(),
228            args: args.elements.to_vec(),
229        });
230    }
231
232    pub fn add_misc_export(&mut self, misc_export: MiscModuleExport) {
233        self.module.misc_exports.push(misc_export)
234    }
235
236    pub fn add_type_alias(&mut self, type_alias: TypeAlias) {
237        self.add_misc_export(MiscModuleExport::TypeAlias(type_alias))
238    }
239
240    pub fn typespace(&self) -> &sats::Typespace {
241        &self.module.typespace
242    }
243
244    pub fn finish(self) -> RawModuleDefV8 {
245        self.module
246    }
247}
248
249impl TypespaceBuilder for ModuleDefBuilder {
250    fn add(
251        &mut self,
252        typeid: TypeId,
253        name: Option<&'static str>,
254        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
255    ) -> AlgebraicType {
256        let r = match self.type_map.entry(typeid) {
257            btree_map::Entry::Occupied(o) => *o.get(),
258            btree_map::Entry::Vacant(v) => {
259                let slot_ref = self.module.typespace.add(AlgebraicType::unit());
261                v.insert(slot_ref);
263
264                if let Some(name) = name {
266                    self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
267                        name: name.to_owned(),
268                        ty: slot_ref,
269                    }));
270                }
271
272                let ty = make_ty(self);
274                self.module.typespace[slot_ref] = ty;
275                slot_ref
276            }
277        };
278        AlgebraicType::Ref(r)
279    }
280}
281
282#[derive(Debug, Clone, SpacetimeType)]
284#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
285#[sats(crate = crate)]
286pub enum MiscModuleExport {
287    TypeAlias(TypeAlias),
288}
289
290#[derive(Debug, Clone, SpacetimeType)]
291#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
292#[sats(crate = crate)]
293pub struct TypeAlias {
294    pub name: String,
295    pub ty: sats::AlgebraicTypeRef,
296}
297
298pub fn from_hex_pad<R: hex::FromHex<Error = hex::FromHexError>, T: AsRef<[u8]>>(
304    hex: T,
305) -> Result<R, hex::FromHexError> {
306    let hex = hex.as_ref();
307    let hex = if hex.starts_with(b"0x") {
308        &hex[2..]
309    } else if hex.starts_with(b"X'") {
310        &hex[2..hex.len()]
311    } else {
312        hex
313    };
314    hex::FromHex::from_hex(hex)
315}
316
317pub fn resolved_type_via_v9<T: SpacetimeType>() -> AlgebraicType {
323    let mut builder = RawModuleDefV9Builder::new();
324    let ty = T::make_type(&mut builder);
325    let module = builder.finish();
326
327    WithTypespace::new(&module.typespace, &ty)
328        .resolve_refs()
329        .expect("recursive types not supported")
330}