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