Skip to main content

spacetimedb_lib/
lib.rs

1use crate::db::raw_def::v9::RawModuleDefV9Builder;
2use crate::db::raw_def::RawTableDefV8;
3
4#[doc(hidden)]
5pub use crate::db::raw_def::v10::ExplicitNames;
6use anyhow::Context;
7use sats::typespace::TypespaceBuilder;
8use spacetimedb_sats::raw_identifier::RawIdentifier;
9use spacetimedb_sats::WithTypespace;
10use std::any::TypeId;
11use std::collections::{btree_map, BTreeMap};
12
13pub mod connection_id;
14pub mod db;
15mod direct_index_key;
16pub mod error;
17mod filterable_value;
18pub mod http;
19pub mod identity;
20pub mod metrics;
21pub mod operator;
22pub mod query;
23pub mod scheduler;
24pub mod st_var;
25pub mod version;
26pub mod view_args;
27
28pub mod type_def {
29    pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
30}
31pub mod type_value {
32    pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
33}
34
35pub use connection_id::ConnectionId;
36pub use direct_index_key::{assert_column_type_valid_for_direct_index, DirectIndexKey};
37#[doc(hidden)]
38pub use filterable_value::Private;
39pub use filterable_value::{FilterableValue, IndexScanRangeBoundsTerminator, TermBound, ViewPrimaryKeyColumn};
40pub use identity::Identity;
41pub use scheduler::ScheduleAt;
42pub use spacetimedb_sats::hash::{self, hash_bytes, Hash};
43pub use spacetimedb_sats::time_duration::TimeDuration;
44pub use spacetimedb_sats::timestamp::Timestamp;
45pub use spacetimedb_sats::uuid::Uuid;
46pub use spacetimedb_sats::SpacetimeType;
47pub use spacetimedb_sats::__make_register_reftype;
48pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser};
49pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
50pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
51pub use view_args::{
52    empty_view_arg_hash_value, hash_empty_view_args, hash_sender_view_args, hash_view_args, sender_view_arg_hash_value,
53    VIEW_ARGS_HASH_DOMAIN,
54};
55
56pub const MODULE_ABI_MAJOR_VERSION: u16 = 10;
57
58// if it ends up we need more fields in the future, we can split one of them in two
59#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
60pub struct VersionTuple {
61    /// Breaking change; different major versions are not at all compatible with each other.
62    pub major: u16,
63    /// Non-breaking change; a host can run a module that requests an older minor version than the
64    /// host implements, but not the other way around
65    pub minor: u16,
66}
67
68impl VersionTuple {
69    pub const fn new(major: u16, minor: u16) -> Self {
70        Self { major, minor }
71    }
72
73    #[inline]
74    pub const fn eq(self, other: Self) -> bool {
75        self.major == other.major && self.minor == other.minor
76    }
77
78    /// Checks if a host implementing this version can run a module that expects `module_version`
79    #[inline]
80    pub const fn supports(self, module_version: VersionTuple) -> bool {
81        self.major == module_version.major && self.minor >= module_version.minor
82    }
83
84    #[inline]
85    pub const fn from_u32(v: u32) -> Self {
86        let major = (v >> 16) as u16;
87        let minor = (v & 0xFF) as u16;
88        Self { major, minor }
89    }
90
91    #[inline]
92    pub const fn to_u32(self) -> u32 {
93        (self.major as u32) << 16 | self.minor as u32
94    }
95}
96
97impl std::fmt::Display for VersionTuple {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        let Self { major, minor } = *self;
100        write!(f, "{major}.{minor}")
101    }
102}
103
104extern crate self as spacetimedb_lib;
105
106//WARNING: Change this structure(or any of their members) is an ABI change.
107#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
108#[sats(crate = crate)]
109pub struct TableDesc {
110    pub schema: RawTableDefV8,
111    /// data should always point to a ProductType in the typespace
112    pub data: sats::AlgebraicTypeRef,
113}
114
115impl TableDesc {
116    pub fn into_table_def(table: WithTypespace<'_, TableDesc>) -> anyhow::Result<RawTableDefV8> {
117        let schema = table
118            .map(|t| &t.data)
119            .resolve_refs()
120            .context("recursive types not yet supported")?;
121        let schema = schema.into_product().ok().context("table not a product type?")?;
122        let table = table.ty();
123        anyhow::ensure!(
124            table.schema.columns.len() == schema.elements.len(),
125            "mismatched number of columns"
126        );
127
128        Ok(table.schema.clone())
129    }
130}
131
132#[derive(Debug, Clone, SpacetimeType)]
133#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
134#[sats(crate = crate)]
135pub struct ReducerDef {
136    pub name: RawIdentifier,
137    pub args: Vec<ProductTypeElement>,
138}
139
140//WARNING: Change this structure (or any of their members) is an ABI change.
141#[derive(Debug, Clone, Default, SpacetimeType)]
142#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
143#[sats(crate = crate)]
144pub struct RawModuleDefV8 {
145    pub typespace: sats::Typespace,
146    pub tables: Vec<TableDesc>,
147    pub reducers: Vec<ReducerDef>,
148    pub misc_exports: Vec<MiscModuleExport>,
149}
150
151impl RawModuleDefV8 {
152    pub fn builder() -> ModuleDefBuilder {
153        ModuleDefBuilder::default()
154    }
155
156    pub fn with_builder(f: impl FnOnce(&mut ModuleDefBuilder)) -> Self {
157        let mut builder = Self::builder();
158        f(&mut builder);
159        builder.finish()
160    }
161}
162
163/// A versioned raw module definition.
164///
165/// This is what is actually returned by the module when `__describe_module__` is called, serialized to BSATN.
166#[derive(Debug, Clone, SpacetimeType)]
167#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
168#[sats(crate = crate)]
169#[non_exhaustive]
170pub enum RawModuleDef {
171    V8BackCompat(RawModuleDefV8),
172    V9(db::raw_def::v9::RawModuleDefV9),
173    V10(db::raw_def::v10::RawModuleDefV10),
174    // TODO(jgilles): It would be nice to have a custom error message if this fails with an unknown variant,
175    // but I'm not sure if that can be done via the Deserialize trait.
176}
177
178/// A builder for a [`RawModuleDefV8`].
179/// Deprecated.
180#[derive(Default)]
181pub struct ModuleDefBuilder {
182    /// The module definition.
183    module: RawModuleDefV8,
184    /// The type map from `T: 'static` Rust types to sats types.
185    type_map: BTreeMap<TypeId, sats::AlgebraicTypeRef>,
186}
187
188impl ModuleDefBuilder {
189    pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
190        TypespaceBuilder::add_type::<T>(self)
191    }
192
193    /// Add a type that may not correspond to a Rust type.
194    /// Used only in tests.
195    #[cfg(feature = "test")]
196    pub fn add_type_for_tests(&mut self, name: &str, ty: AlgebraicType) -> spacetimedb_sats::AlgebraicTypeRef {
197        let slot_ref = self.module.typespace.add(ty);
198        self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
199            name: RawIdentifier::new(name),
200            ty: slot_ref,
201        }));
202        slot_ref
203    }
204
205    /// Add a table that may not correspond to a Rust type.
206    /// Wraps it in a `TableDesc` and generates a corresponding `ProductType` in the typespace.
207    /// Used only in tests.
208    /// Returns the `AlgebraicTypeRef` of the generated `ProductType`.
209    #[cfg(feature = "test")]
210    pub fn add_table_for_tests(&mut self, schema: RawTableDefV8) -> spacetimedb_sats::AlgebraicTypeRef {
211        let ty: ProductType = schema
212            .columns
213            .iter()
214            .map(|c| ProductTypeElement {
215                name: Some(c.col_name.clone()),
216                algebraic_type: c.col_type.clone(),
217            })
218            .collect();
219        let data = self.module.typespace.add(ty.into());
220        self.add_type_alias(TypeAlias {
221            name: schema.table_name.clone(),
222            ty: data,
223        });
224        self.add_table(TableDesc { schema, data });
225        data
226    }
227
228    pub fn add_table(&mut self, table: TableDesc) {
229        self.module.tables.push(table)
230    }
231
232    pub fn add_reducer(&mut self, reducer: ReducerDef) {
233        self.module.reducers.push(reducer)
234    }
235
236    #[cfg(feature = "test")]
237    pub fn add_reducer_for_tests(&mut self, name: impl AsRef<str>, args: ProductType) {
238        self.add_reducer(ReducerDef {
239            name: RawIdentifier::new(name.as_ref()),
240            args: args.elements.to_vec(),
241        });
242    }
243
244    pub fn add_misc_export(&mut self, misc_export: MiscModuleExport) {
245        self.module.misc_exports.push(misc_export)
246    }
247
248    pub fn add_type_alias(&mut self, type_alias: TypeAlias) {
249        self.add_misc_export(MiscModuleExport::TypeAlias(type_alias))
250    }
251
252    pub fn typespace(&self) -> &sats::Typespace {
253        &self.module.typespace
254    }
255
256    pub fn finish(self) -> RawModuleDefV8 {
257        self.module
258    }
259}
260
261impl TypespaceBuilder for ModuleDefBuilder {
262    fn add(
263        &mut self,
264        typeid: TypeId,
265        name: Option<&'static str>,
266        make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
267    ) -> AlgebraicType {
268        let r = match self.type_map.entry(typeid) {
269            btree_map::Entry::Occupied(o) => *o.get(),
270            btree_map::Entry::Vacant(v) => {
271                // Bind a fresh alias to the unit type.
272                let slot_ref = self.module.typespace.add(AlgebraicType::unit());
273                // Relate `typeid -> fresh alias`.
274                v.insert(slot_ref);
275
276                // Alias provided? Relate `name -> slot_ref`.
277                if let Some(name) = name {
278                    self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
279                        name: name.into(),
280                        ty: slot_ref,
281                    }));
282                }
283
284                // Borrow of `v` has ended here, so we can now convince the borrow checker.
285                let ty = make_ty(self);
286                self.module.typespace[slot_ref] = ty;
287                slot_ref
288            }
289        };
290        AlgebraicType::Ref(r)
291    }
292}
293
294// an enum to keep it extensible without breaking abi
295#[derive(Debug, Clone, SpacetimeType)]
296#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
297#[sats(crate = crate)]
298pub enum MiscModuleExport {
299    TypeAlias(TypeAlias),
300}
301
302#[derive(Debug, Clone, SpacetimeType)]
303#[cfg_attr(feature = "test", derive(PartialEq, Eq, PartialOrd, Ord))]
304#[sats(crate = crate)]
305pub struct TypeAlias {
306    pub name: RawIdentifier,
307    pub ty: sats::AlgebraicTypeRef,
308}
309
310/// Converts a hexadecimal string reference to a byte array.
311///
312/// This function takes a reference to a hexadecimal string and attempts to convert it into a byte array.
313///
314/// If the hexadecimal string starts with "0x", these characters are ignored.
315pub fn from_hex_pad<R: hex::FromHex<Error = hex::FromHexError>, T: AsRef<[u8]>>(
316    hex: T,
317) -> Result<R, hex::FromHexError> {
318    let hex = hex.as_ref();
319    let hex = if hex.starts_with(b"0x") {
320        &hex[2..]
321    } else if hex.starts_with(b"X'") {
322        &hex[2..hex.len()]
323    } else {
324        hex
325    };
326    hex::FromHex::from_hex(hex)
327}
328
329/// Returns a resolved `AlgebraicType` (containing no `AlgebraicTypeRefs`) for a given `SpacetimeType`,
330/// using the v9 moduledef infrastructure.
331/// Panics if the type is recursive.
332///
333/// TODO: we could implement something like this in `sats` itself, but would need a lightweight `TypespaceBuilder` implementation there.
334pub fn resolved_type_via_v9<T: SpacetimeType>() -> AlgebraicType {
335    let mut builder = RawModuleDefV9Builder::new();
336    let ty = T::make_type(&mut builder);
337    let module = builder.finish();
338
339    WithTypespace::new(&module.typespace, &ty)
340        .resolve_refs()
341        .expect("recursive types not supported")
342}