spacetimedb/host/
mod.rs

1use anyhow::Context;
2use bytes::Bytes;
3use bytestring::ByteString;
4use derive_more::Display;
5use enum_map::Enum;
6use once_cell::sync::OnceCell;
7use spacetimedb_lib::bsatn;
8use spacetimedb_lib::de::serde::SeedWrapper;
9use spacetimedb_lib::de::DeserializeSeed;
10use spacetimedb_lib::ProductValue;
11use spacetimedb_schema::def::deserialize::ReducerArgsDeserializeSeed;
12
13mod disk_storage;
14mod host_controller;
15mod module_common;
16#[allow(clippy::too_many_arguments)]
17pub mod module_host;
18pub mod scheduler;
19pub mod wasmtime;
20
21// Visible for integration testing.
22pub mod instance_env;
23pub mod v8; // only pub for testing
24mod wasm_common;
25
26pub use disk_storage::DiskStorage;
27pub use host_controller::{
28    extract_schema, DurabilityProvider, ExternalDurability, ExternalStorage, HostController, ProgramStorage,
29    ReducerCallResult, ReducerOutcome, StartSnapshotWatcher,
30};
31pub use module_host::{ModuleHost, NoSuchModule, ReducerCallError, UpdateDatabaseResult};
32pub use scheduler::Scheduler;
33
34#[derive(Debug)]
35pub enum ReducerArgs {
36    Json(ByteString),
37    Bsatn(Bytes),
38    Nullary,
39}
40
41impl ReducerArgs {
42    fn into_tuple(self, seed: ReducerArgsDeserializeSeed) -> Result<ArgsTuple, InvalidReducerArguments> {
43        self._into_tuple(seed).map_err(|err| InvalidReducerArguments {
44            err,
45            reducer: (*seed.reducer_def().name).into(),
46        })
47    }
48    fn _into_tuple(self, seed: ReducerArgsDeserializeSeed) -> anyhow::Result<ArgsTuple> {
49        Ok(match self {
50            ReducerArgs::Json(json) => ArgsTuple {
51                tuple: from_json_seed(&json, SeedWrapper(seed))?,
52                bsatn: OnceCell::new(),
53                json: OnceCell::with_value(json),
54            },
55            ReducerArgs::Bsatn(bytes) => ArgsTuple {
56                tuple: seed.deserialize(bsatn::Deserializer::new(&mut &bytes[..]))?,
57                bsatn: OnceCell::with_value(bytes),
58                json: OnceCell::new(),
59            },
60            ReducerArgs::Nullary => {
61                anyhow::ensure!(
62                    seed.reducer_def().params.elements.is_empty(),
63                    "failed to typecheck args"
64                );
65                ArgsTuple::nullary()
66            }
67        })
68    }
69}
70
71#[derive(Debug, Clone)]
72pub struct ArgsTuple {
73    tuple: ProductValue,
74    bsatn: OnceCell<Bytes>,
75    json: OnceCell<ByteString>,
76}
77
78impl ArgsTuple {
79    pub fn nullary() -> Self {
80        ArgsTuple {
81            tuple: spacetimedb_sats::product![],
82            bsatn: OnceCell::with_value(Bytes::new()),
83            json: OnceCell::with_value(ByteString::from_static("[]")),
84        }
85    }
86
87    pub fn get_bsatn(&self) -> &Bytes {
88        self.bsatn.get_or_init(|| bsatn::to_vec(&self.tuple).unwrap().into())
89    }
90    pub fn get_json(&self) -> &ByteString {
91        use spacetimedb_sats::ser::serde::SerializeWrapper;
92        self.json.get_or_init(|| {
93            serde_json::to_string(SerializeWrapper::from_ref(&self.tuple))
94                .unwrap()
95                .into()
96        })
97    }
98}
99
100impl Default for ArgsTuple {
101    fn default() -> Self {
102        Self::nullary()
103    }
104}
105
106// TODO(noa): replace imports from this module with imports straight from primitives.
107pub use spacetimedb_primitives::ReducerId;
108
109#[derive(thiserror::Error, Debug)]
110#[error("invalid arguments for reducer {reducer}: {err}")]
111pub struct InvalidReducerArguments {
112    #[source]
113    err: anyhow::Error,
114    reducer: Box<str>,
115}
116
117fn from_json_seed<'de, T: serde::de::DeserializeSeed<'de>>(s: &'de str, seed: T) -> anyhow::Result<T::Value> {
118    let mut de = serde_json::Deserializer::from_str(s);
119    let mut track = serde_path_to_error::Track::new();
120    let out = seed
121        .deserialize(serde_path_to_error::Deserializer::new(&mut de, &mut track))
122        .context(track.path())?;
123    de.end()?;
124    Ok(out)
125}
126
127/// Tags for each call that a `WasmInstanceEnv` can make.
128#[derive(Debug, Display, Enum, Clone, Copy, strum::AsRefStr)]
129pub enum AbiCall {
130    TableIdFromName,
131    IndexIdFromName,
132    DatastoreTableRowCount,
133    DatastoreTableScanBsatn,
134    DatastoreIndexScanRangeBsatn,
135    RowIterBsatnAdvance,
136    RowIterBsatnClose,
137    DatastoreInsertBsatn,
138    DatastoreUpdateBsatn,
139    DatastoreDeleteByIndexScanRangeBsatn,
140    DatastoreDeleteAllByEqBsatn,
141    BytesSourceRead,
142    BytesSinkWrite,
143    ConsoleLog,
144    ConsoleTimerStart,
145    ConsoleTimerEnd,
146    Identity,
147
148    VolatileNonatomicScheduleImmediate,
149}