use crate::db::raw_def::v9::RawModuleDefV9Builder;
use crate::db::raw_def::RawTableDefV8;
use anyhow::Context;
use sats::typespace::TypespaceBuilder;
use spacetimedb_sats::{impl_serialize, WithTypespace};
use std::any::TypeId;
use std::collections::{btree_map, BTreeMap};
pub mod address;
pub mod db;
pub mod error;
pub mod identity;
pub mod operator;
pub mod relation;
pub mod scheduler;
pub mod version;
pub mod type_def {
pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
}
pub mod type_value {
pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
}
pub use address::Address;
pub use identity::Identity;
pub use scheduler::ScheduleAt;
pub use spacetimedb_sats::hash::{self, hash_bytes, Hash};
pub use spacetimedb_sats::SpacetimeType;
pub use spacetimedb_sats::__make_register_reftype;
pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser};
pub use spacetimedb_sats::{AlgebraicType, ProductType, ProductTypeElement, SumType};
pub use spacetimedb_sats::{AlgebraicValue, ProductValue};
pub const MODULE_ABI_MAJOR_VERSION: u16 = 10;
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
pub struct VersionTuple {
pub major: u16,
pub minor: u16,
}
impl VersionTuple {
pub const fn new(major: u16, minor: u16) -> Self {
Self { major, minor }
}
#[inline]
pub const fn eq(self, other: Self) -> bool {
self.major == other.major && self.minor == other.minor
}
#[inline]
pub const fn supports(self, module_version: VersionTuple) -> bool {
self.major == module_version.major && self.minor >= module_version.minor
}
#[inline]
pub const fn from_u32(v: u32) -> Self {
let major = (v >> 16) as u16;
let minor = (v & 0xFF) as u16;
Self { major, minor }
}
#[inline]
pub const fn to_u32(self) -> u32 {
(self.major as u32) << 16 | self.minor as u32
}
}
impl std::fmt::Display for VersionTuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self { major, minor } = *self;
write!(f, "{major}.{minor}")
}
}
extern crate self as spacetimedb_lib;
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, SpacetimeType)]
#[sats(crate = crate)]
pub struct TableDesc {
pub schema: RawTableDefV8,
pub data: sats::AlgebraicTypeRef,
}
impl TableDesc {
pub fn into_table_def(table: WithTypespace<'_, TableDesc>) -> anyhow::Result<RawTableDefV8> {
let schema = table
.map(|t| &t.data)
.resolve_refs()
.context("recursive types not yet supported")?;
let schema = schema.into_product().ok().context("table not a product type?")?;
let table = table.ty();
anyhow::ensure!(
table.schema.columns.len() == schema.elements.len(),
"mismatched number of columns"
);
Ok(table.schema.clone())
}
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub struct ReducerDef {
pub name: Box<str>,
pub args: Vec<ProductTypeElement>,
}
impl ReducerDef {
pub fn encode(&self, writer: &mut impl buffer::BufWriter) {
bsatn::to_writer(writer, self).unwrap()
}
pub fn serialize_args<'a>(ty: sats::WithTypespace<'a, Self>, value: &'a ProductValue) -> impl ser::Serialize + 'a {
ReducerArgsWithSchema { value, ty }
}
pub fn deserialize(
ty: sats::WithTypespace<'_, Self>,
) -> impl for<'de> de::DeserializeSeed<'de, Output = ProductValue> + '_ {
ReducerDeserialize(ty)
}
}
struct ReducerDeserialize<'a>(sats::WithTypespace<'a, ReducerDef>);
impl<'de> de::DeserializeSeed<'de> for ReducerDeserialize<'_> {
type Output = ProductValue;
fn deserialize<D: de::Deserializer<'de>>(self, deserializer: D) -> Result<Self::Output, D::Error> {
deserializer.deserialize_product(self)
}
}
impl<'de> de::ProductVisitor<'de> for ReducerDeserialize<'_> {
type Output = ProductValue;
fn product_name(&self) -> Option<&str> {
Some(&self.0.ty().name)
}
fn product_len(&self) -> usize {
self.0.ty().args.len()
}
fn product_kind(&self) -> de::ProductKind {
de::ProductKind::ReducerArgs
}
fn visit_seq_product<A: de::SeqProductAccess<'de>>(self, tup: A) -> Result<Self::Output, A::Error> {
de::visit_seq_product(self.0.map(|r| &*r.args), &self, tup)
}
fn visit_named_product<A: de::NamedProductAccess<'de>>(self, tup: A) -> Result<Self::Output, A::Error> {
de::visit_named_product(self.0.map(|r| &*r.args), &self, tup)
}
}
struct ReducerArgsWithSchema<'a> {
value: &'a ProductValue,
ty: sats::WithTypespace<'a, ReducerDef>,
}
impl_serialize!([] ReducerArgsWithSchema<'_>, (self, ser) => {
use itertools::Itertools;
use ser::SerializeSeqProduct;
let mut seq = ser.serialize_seq_product(self.value.elements.len())?;
for (value, elem) in self.value.elements.iter().zip_eq(&self.ty.ty().args) {
seq.serialize_element(&self.ty.with(&elem.algebraic_type).with_value(value))?;
}
seq.end()
});
#[derive(Debug, Clone, Default, SpacetimeType)]
#[sats(crate = crate)]
pub struct RawModuleDefV8 {
pub typespace: sats::Typespace,
pub tables: Vec<TableDesc>,
pub reducers: Vec<ReducerDef>,
pub misc_exports: Vec<MiscModuleExport>,
}
impl RawModuleDefV8 {
pub fn builder() -> ModuleDefBuilder {
ModuleDefBuilder::default()
}
pub fn with_builder(f: impl FnOnce(&mut ModuleDefBuilder)) -> Self {
let mut builder = Self::builder();
f(&mut builder);
builder.finish()
}
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
#[non_exhaustive]
pub enum RawModuleDef {
V8BackCompat(RawModuleDefV8),
V9(db::raw_def::v9::RawModuleDefV9),
}
#[derive(Default)]
pub struct ModuleDefBuilder {
module: RawModuleDefV8,
type_map: BTreeMap<TypeId, sats::AlgebraicTypeRef>,
}
impl ModuleDefBuilder {
pub fn add_type<T: SpacetimeType>(&mut self) -> AlgebraicType {
TypespaceBuilder::add_type::<T>(self)
}
#[cfg(feature = "test")]
pub fn add_type_for_tests(&mut self, name: &str, ty: AlgebraicType) -> spacetimedb_sats::AlgebraicTypeRef {
let slot_ref = self.module.typespace.add(ty);
self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
name: name.to_owned(),
ty: slot_ref,
}));
slot_ref
}
#[cfg(feature = "test")]
pub fn add_table_for_tests(&mut self, schema: RawTableDefV8) -> spacetimedb_sats::AlgebraicTypeRef {
let ty: ProductType = schema
.columns
.iter()
.map(|c| ProductTypeElement {
name: Some(c.col_name.clone()),
algebraic_type: c.col_type.clone(),
})
.collect();
let data = self.module.typespace.add(ty.into());
self.add_type_alias(TypeAlias {
name: schema.table_name.clone().into(),
ty: data,
});
self.add_table(TableDesc { schema, data });
data
}
pub fn add_table(&mut self, table: TableDesc) {
self.module.tables.push(table)
}
pub fn add_reducer(&mut self, reducer: ReducerDef) {
self.module.reducers.push(reducer)
}
#[cfg(feature = "test")]
pub fn add_reducer_for_tests(&mut self, name: impl Into<Box<str>>, args: ProductType) {
self.add_reducer(ReducerDef {
name: name.into(),
args: args.elements.to_vec(),
});
}
pub fn add_misc_export(&mut self, misc_export: MiscModuleExport) {
self.module.misc_exports.push(misc_export)
}
pub fn add_type_alias(&mut self, type_alias: TypeAlias) {
self.add_misc_export(MiscModuleExport::TypeAlias(type_alias))
}
pub fn typespace(&self) -> &sats::Typespace {
&self.module.typespace
}
pub fn finish(self) -> RawModuleDefV8 {
self.module
}
}
impl TypespaceBuilder for ModuleDefBuilder {
fn add(
&mut self,
typeid: TypeId,
name: Option<&'static str>,
make_ty: impl FnOnce(&mut Self) -> AlgebraicType,
) -> AlgebraicType {
let r = match self.type_map.entry(typeid) {
btree_map::Entry::Occupied(o) => *o.get(),
btree_map::Entry::Vacant(v) => {
let slot_ref = self.module.typespace.add(AlgebraicType::unit());
v.insert(slot_ref);
if let Some(name) = name {
self.module.misc_exports.push(MiscModuleExport::TypeAlias(TypeAlias {
name: name.to_owned(),
ty: slot_ref,
}));
}
let ty = make_ty(self);
self.module.typespace[slot_ref] = ty;
slot_ref
}
};
AlgebraicType::Ref(r)
}
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub enum MiscModuleExport {
TypeAlias(TypeAlias),
}
#[derive(Debug, Clone, SpacetimeType)]
#[sats(crate = crate)]
pub struct TypeAlias {
pub name: String,
pub ty: sats::AlgebraicTypeRef,
}
pub fn from_hex_pad<R: hex::FromHex<Error = hex::FromHexError>, T: AsRef<[u8]>>(
hex: T,
) -> Result<R, hex::FromHexError> {
let hex = hex.as_ref();
let hex = if hex.starts_with(b"0x") {
&hex[2..]
} else if hex.starts_with(b"X'") {
&hex[2..hex.len()]
} else {
hex
};
hex::FromHex::from_hex(hex)
}
pub fn resolved_type_via_v9<T: SpacetimeType>() -> AlgebraicType {
let mut builder = RawModuleDefV9Builder::new();
let ty = T::make_type(&mut builder);
let module = builder.finish();
WithTypespace::new(&module.typespace, &ty)
.resolve_refs()
.expect("recursive types not supported")
}