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