1use crate::db::raw_def::v9::RawModuleDefV9Builder;
2use crate::db::raw_def::RawTableDefV8;
3use anyhow::Context;
4use sats::typespace::TypespaceBuilder;
5use spacetimedb_sats::{impl_serialize, 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 relation;
19pub mod scheduler;
20pub mod st_var;
21pub mod version;
22
23pub mod type_def {
24 pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
25}
26pub mod type_value {
27 pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
28}
29
30pub use connection_id::ConnectionId;
31pub use direct_index_key::{assert_column_type_valid_for_direct_index, DirectIndexKey};
32#[doc(hidden)]
33pub use filterable_value::Private;
34pub use filterable_value::{FilterableValue, IndexScanRangeBoundsTerminator, TermBound};
35pub use identity::Identity;
36pub use scheduler::ScheduleAt;
37pub use spacetimedb_sats::hash::{self, hash_bytes, Hash};
38pub use spacetimedb_sats::time_duration::TimeDuration;
39pub use spacetimedb_sats::timestamp::Timestamp;
40pub use spacetimedb_sats::SpacetimeType;
41pub use spacetimedb_sats::__make_register_reftype;
42pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser};
43pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
44pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
45
46pub const MODULE_ABI_MAJOR_VERSION: u16 = 10;
47
48#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
50pub struct VersionTuple {
51 pub major: u16,
53 pub minor: u16,
56}
57
58impl VersionTuple {
59 pub const fn new(major: u16, minor: u16) -> Self {
60 Self { major, minor }
61 }
62
63 #[inline]
64 pub const fn eq(self, other: Self) -> bool {
65 self.major == other.major && self.minor == other.minor
66 }
67
68 #[inline]
70 pub const fn supports(self, module_version: VersionTuple) -> bool {
71 self.major == module_version.major && self.minor >= module_version.minor
72 }
73
74 #[inline]
75 pub const fn from_u32(v: u32) -> Self {
76 let major = (v >> 16) as u16;
77 let minor = (v & 0xFF) as u16;
78 Self { major, minor }
79 }
80
81 #[inline]
82 pub const fn to_u32(self) -> u32 {
83 (self.major as u32) << 16 | self.minor as u32
84 }
85}
86
87impl std::fmt::Display for VersionTuple {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 let Self { major, minor } = *self;
90 write!(f, "{major}.{minor}")
91 }
92}
93
94extern crate self as spacetimedb_lib;
95
96#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
98#[sats(crate = crate)]
99pub struct TableDesc {
100 pub schema: RawTableDefV8,
101 pub data: sats::AlgebraicTypeRef,
103}
104
105impl TableDesc {
106 pub fn into_table_def(table: WithTypespace<'_, TableDesc>) -> anyhow::Result<RawTableDefV8> {
107 let schema = table
108 .map(|t| &t.data)
109 .resolve_refs()
110 .context("recursive types not yet supported")?;
111 let schema = schema.into_product().ok().context("table not a product type?")?;
112 let table = table.ty();
113 anyhow::ensure!(
114 table.schema.columns.len() == schema.elements.len(),
115 "mismatched number of columns"
116 );
117
118 Ok(table.schema.clone())
119 }
120}
121
122#[derive(Debug, Clone, SpacetimeType)]
123#[sats(crate = crate)]
124pub struct ReducerDef {
125 pub name: Box<str>,
126 pub args: Vec<ProductTypeElement>,
127}
128
129impl ReducerDef {
130 pub fn encode(&self, writer: &mut impl buffer::BufWriter) {
131 bsatn::to_writer(writer, self).unwrap()
132 }
133
134 pub fn serialize_args<'a>(ty: sats::WithTypespace<'a, Self>, value: &'a ProductValue) -> impl ser::Serialize + 'a {
135 ReducerArgsWithSchema { value, ty }
136 }
137
138 pub fn deserialize(
139 ty: sats::WithTypespace<'_, Self>,
140 ) -> impl for<'de> de::DeserializeSeed<'de, Output = ProductValue> + '_ {
141 ReducerDeserialize(ty)
142 }
143}
144
145struct ReducerDeserialize<'a>(sats::WithTypespace<'a, ReducerDef>);
146
147impl<'de> de::DeserializeSeed<'de> for ReducerDeserialize<'_> {
148 type Output = ProductValue;
149
150 fn deserialize<D: de::Deserializer<'de>>(self, deserializer: D) -> Result<Self::Output, D::Error> {
151 deserializer.deserialize_product(self)
152 }
153}
154
155impl<'de> de::ProductVisitor<'de> for ReducerDeserialize<'_> {
156 type Output = ProductValue;
157
158 fn product_name(&self) -> Option<&str> {
159 Some(&self.0.ty().name)
160 }
161 fn product_len(&self) -> usize {
162 self.0.ty().args.len()
163 }
164 fn product_kind(&self) -> de::ProductKind {
165 de::ProductKind::ReducerArgs
166 }
167
168 fn visit_seq_product<A: de::SeqProductAccess<'de>>(self, tup: A) -> Result<Self::Output, A::Error> {
169 de::visit_seq_product(self.0.map(|r| &*r.args), &self, tup)
170 }
171
172 fn visit_named_product<A: de::NamedProductAccess<'de>>(self, tup: A) -> Result<Self::Output, A::Error> {
173 de::visit_named_product(self.0.map(|r| &*r.args), &self, tup)
174 }
175}
176
177struct ReducerArgsWithSchema<'a> {
178 value: &'a ProductValue,
179 ty: sats::WithTypespace<'a, ReducerDef>,
180}
181impl_serialize!([] ReducerArgsWithSchema<'_>, (self, ser) => {
182 use itertools::Itertools;
183 use ser::SerializeSeqProduct;
184 let mut seq = ser.serialize_seq_product(self.value.elements.len())?;
185 for (value, elem) in self.value.elements.iter().zip_eq(&self.ty.ty().args) {
186 seq.serialize_element(&self.ty.with(&elem.algebraic_type).with_value(value))?;
187 }
188 seq.end()
189});
190
191#[derive(Debug, Clone, Default, SpacetimeType)]
193#[sats(crate = crate)]
194pub struct RawModuleDefV8 {
195 pub typespace: sats::Typespace,
196 pub tables: Vec<TableDesc>,
197 pub reducers: Vec<ReducerDef>,
198 pub misc_exports: Vec<MiscModuleExport>,
199}
200
201impl RawModuleDefV8 {
202 pub fn builder() -> ModuleDefBuilder {
203 ModuleDefBuilder::default()
204 }
205
206 pub fn with_builder(f: impl FnOnce(&mut ModuleDefBuilder)) -> Self {
207 let mut builder = Self::builder();
208 f(&mut builder);
209 builder.finish()
210 }
211}
212
213#[derive(Debug, Clone, SpacetimeType)]
217#[sats(crate = crate)]
218#[non_exhaustive]
219pub enum RawModuleDef {
220 V8BackCompat(RawModuleDefV8),
221 V9(db::raw_def::v9::RawModuleDefV9),
222 }
225
226#[derive(Default)]
229pub struct ModuleDefBuilder {
230 module: RawModuleDefV8,
232 type_map: BTreeMap<TypeId, sats::AlgebraicTypeRef>,
234}
235
236impl ModuleDefBuilder {
237 pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
238 TypespaceBuilder::add_type::<T>(self)
239 }
240
241 #[cfg(feature = "test")]
244 pub fn add_type_for_tests(&mut self, name: &str, ty: AlgebraicType) -> spacetimedb_sats::AlgebraicTypeRef {
245 let slot_ref = self.module.typespace.add(ty);
246 self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
247 name: name.to_owned(),
248 ty: slot_ref,
249 }));
250 slot_ref
251 }
252
253 #[cfg(feature = "test")]
258 pub fn add_table_for_tests(&mut self, schema: RawTableDefV8) -> spacetimedb_sats::AlgebraicTypeRef {
259 let ty: ProductType = schema
260 .columns
261 .iter()
262 .map(|c| ProductTypeElement {
263 name: Some(c.col_name.clone()),
264 algebraic_type: c.col_type.clone(),
265 })
266 .collect();
267 let data = self.module.typespace.add(ty.into());
268 self.add_type_alias(TypeAlias {
269 name: schema.table_name.clone().into(),
270 ty: data,
271 });
272 self.add_table(TableDesc { schema, data });
273 data
274 }
275
276 pub fn add_table(&mut self, table: TableDesc) {
277 self.module.tables.push(table)
278 }
279
280 pub fn add_reducer(&mut self, reducer: ReducerDef) {
281 self.module.reducers.push(reducer)
282 }
283
284 #[cfg(feature = "test")]
285 pub fn add_reducer_for_tests(&mut self, name: impl Into<Box<str>>, args: ProductType) {
286 self.add_reducer(ReducerDef {
287 name: name.into(),
288 args: args.elements.to_vec(),
289 });
290 }
291
292 pub fn add_misc_export(&mut self, misc_export: MiscModuleExport) {
293 self.module.misc_exports.push(misc_export)
294 }
295
296 pub fn add_type_alias(&mut self, type_alias: TypeAlias) {
297 self.add_misc_export(MiscModuleExport::TypeAlias(type_alias))
298 }
299
300 pub fn typespace(&self) -> &sats::Typespace {
301 &self.module.typespace
302 }
303
304 pub fn finish(self) -> RawModuleDefV8 {
305 self.module
306 }
307}
308
309impl TypespaceBuilder for ModuleDefBuilder {
310 fn add(
311 &mut self,
312 typeid: TypeId,
313 name: Option<&'static str>,
314 make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
315 ) -> AlgebraicType {
316 let r = match self.type_map.entry(typeid) {
317 btree_map::Entry::Occupied(o) => *o.get(),
318 btree_map::Entry::Vacant(v) => {
319 let slot_ref = self.module.typespace.add(AlgebraicType::unit());
321 v.insert(slot_ref);
323
324 if let Some(name) = name {
326 self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
327 name: name.to_owned(),
328 ty: slot_ref,
329 }));
330 }
331
332 let ty = make_ty(self);
334 self.module.typespace[slot_ref] = ty;
335 slot_ref
336 }
337 };
338 AlgebraicType::Ref(r)
339 }
340}
341
342#[derive(Debug, Clone, SpacetimeType)]
344#[sats(crate = crate)]
345pub enum MiscModuleExport {
346 TypeAlias(TypeAlias),
347}
348
349#[derive(Debug, Clone, SpacetimeType)]
350#[sats(crate = crate)]
351pub struct TypeAlias {
352 pub name: String,
353 pub ty: sats::AlgebraicTypeRef,
354}
355
356pub fn from_hex_pad<R: hex::FromHex<Error = hex::FromHexError>, T: AsRef<[u8]>>(
362 hex: T,
363) -> Result<R, hex::FromHexError> {
364 let hex = hex.as_ref();
365 let hex = if hex.starts_with(b"0x") {
366 &hex[2..]
367 } else if hex.starts_with(b"X'") {
368 &hex[2..hex.len()]
369 } else {
370 hex
371 };
372 hex::FromHex::from_hex(hex)
373}
374
375pub fn resolved_type_via_v9<T: SpacetimeType>() -> AlgebraicType {
381 let mut builder = RawModuleDefV9Builder::new();
382 let ty = T::make_type(&mut builder);
383 let module = builder.finish();
384
385 WithTypespace::new(&module.typespace, &ty)
386 .resolve_refs()
387 .expect("recursive types not supported")
388}