mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-16 04:37:55 -04:00
wip
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid",
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"endOfLine": "auto",
|
||||
"printWidth": 100
|
||||
}
|
||||
Generated
+158
-6
@@ -20,6 +20,12 @@ dependencies = [
|
||||
"gimli 0.31.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
@@ -381,7 +387,7 @@ dependencies = [
|
||||
"addr2line 0.24.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.8",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets 0.52.6",
|
||||
@@ -429,6 +435,26 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
"shlex",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
@@ -712,6 +738,15 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -776,6 +811,17 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.23"
|
||||
@@ -1043,7 +1089,7 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"log",
|
||||
"regalloc2",
|
||||
"rustc-hash",
|
||||
"rustc-hash 2.1.1",
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
]
|
||||
@@ -1828,7 +1874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.8.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1886,6 +1932,16 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fslock"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@@ -2069,6 +2125,15 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "gzip-header"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
@@ -2488,7 +2553,7 @@ dependencies = [
|
||||
"shlex",
|
||||
"tempfile",
|
||||
"version-compare",
|
||||
"which",
|
||||
"which 4.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2953,6 +3018,16 @@ version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
@@ -3168,6 +3243,21 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.8"
|
||||
@@ -3307,6 +3397,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -3901,6 +4001,16 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
@@ -4333,7 +4443,7 @@ checksum = "12908dbeb234370af84d0579b9f68258a0f67e201412dd9a2814e6f45b2fc0f0"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"log",
|
||||
"rustc-hash",
|
||||
"rustc-hash 2.1.1",
|
||||
"slice-group-by",
|
||||
"smallvec",
|
||||
]
|
||||
@@ -4622,6 +4732,12 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -5506,7 +5622,7 @@ dependencies = [
|
||||
"regex",
|
||||
"reqwest 0.12.15",
|
||||
"rustc-demangle",
|
||||
"rustc-hash",
|
||||
"rustc-hash 2.1.1",
|
||||
"scopeguard",
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -5564,6 +5680,7 @@ dependencies = [
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"v8",
|
||||
"wasmtime",
|
||||
]
|
||||
|
||||
@@ -7187,6 +7304,23 @@ dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v8"
|
||||
version = "134.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21c7a224a7eaf3f98c1bad772fbaee56394dce185ef7b19a2e0ca5e3d274165d"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bitflags 2.9.0",
|
||||
"fslock",
|
||||
"gzip-header",
|
||||
"home",
|
||||
"miniz_oxide 0.7.4",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"which 6.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
@@ -7661,6 +7795,18 @@ dependencies = [
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "6.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"home",
|
||||
"rustix 0.38.44",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.6.0"
|
||||
@@ -8143,6 +8289,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
||||
@@ -116,6 +116,8 @@ jwks.workspace = true
|
||||
async_cache = "0.3.1"
|
||||
faststr = "0.2.23"
|
||||
core_affinity = "0.8"
|
||||
v8 = "134"
|
||||
# sourcemap = "9"
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = {workspace = true}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod scheduler;
|
||||
pub mod wasmtime;
|
||||
// Visible for integration testing.
|
||||
pub mod instance_env;
|
||||
pub mod v8; // only pub for testing
|
||||
mod wasm_common;
|
||||
|
||||
pub use disk_storage::DiskStorage;
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use crate::database_logger::{BacktraceProvider, LogLevel, Record};
|
||||
use crate::db::datastore::locking_tx_datastore::MutTxId;
|
||||
use crate::db::datastore::traits::Program;
|
||||
use crate::energy::EnergyMonitor;
|
||||
use crate::module_host_context::ModuleCreationContext;
|
||||
use crate::replica_context::ReplicaContext;
|
||||
|
||||
use super::instance_env::InstanceEnv;
|
||||
use super::module_host::{CallReducerParams, Module, ModuleInfo, ModuleInstance};
|
||||
use super::{ReducerCallResult, Scheduler, UpdateDatabaseResult};
|
||||
|
||||
mod util;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use spacetimedb_lib::db::raw_def::v9::{sats_name_to_scoped_name, RawModuleDefV9, RawReducerDefV9, RawTypeDefV9};
|
||||
use spacetimedb_lib::sats;
|
||||
use util::{
|
||||
ascii_str, iter_array, module, scratch_buf, strings, throw, ErrorOrException, ExcResult, ExceptionOptionExt,
|
||||
ExceptionThrown, ObjectExt, ThrowExceptionResultExt, TypeError,
|
||||
};
|
||||
|
||||
#[allow(unused)]
|
||||
struct V8InstanceEnv {
|
||||
instance_env: InstanceEnv,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct JsModule {
|
||||
replica_context: Arc<ReplicaContext>,
|
||||
scheduler: Scheduler,
|
||||
info: Arc<ModuleInfo>,
|
||||
energy_monitor: Arc<dyn EnergyMonitor>,
|
||||
snapshot: Arc<[u8]>,
|
||||
module_builder: ModuleBuilder,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn compile_real(mcc: ModuleCreationContext) -> anyhow::Result<JsModule> {
|
||||
let program = std::str::from_utf8(&mcc.program.bytes)?;
|
||||
let (snapshot, module_builder) = compile(program, Arc::new(Logger))?;
|
||||
Ok(JsModule {
|
||||
replica_context: mcc.replica_ctx,
|
||||
scheduler: mcc.scheduler,
|
||||
info: todo!(),
|
||||
energy_monitor: mcc.energy_monitor,
|
||||
snapshot,
|
||||
module_builder,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("js error: {msg:?}")]
|
||||
struct JsError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl JsError {
|
||||
fn from_caught(scope: &mut v8::TryCatch<'_, v8::HandleScope<'_>>) -> Self {
|
||||
match scope.message() {
|
||||
Some(msg) => Self {
|
||||
msg: msg.get(scope).to_rust_string_lossy(scope),
|
||||
},
|
||||
None => Self {
|
||||
msg: "unknown error".to_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn catch_exception<'s, T>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
f: impl FnOnce(&mut v8::HandleScope<'s>) -> Result<T, ErrorOrException>,
|
||||
) -> Result<T, ErrorOrException<JsError>> {
|
||||
let scope = &mut v8::TryCatch::new(scope);
|
||||
f(scope).map_err(|e| match e {
|
||||
ErrorOrException::Err(e) => ErrorOrException::Err(e),
|
||||
ErrorOrException::Exception(ExceptionThrown) => ErrorOrException::Exception(JsError::from_caught(scope)),
|
||||
})
|
||||
}
|
||||
|
||||
struct Logger;
|
||||
impl Logger {
|
||||
fn write(&self, level: LogLevel, record: &Record<'_>, _bt: &dyn BacktraceProvider) {
|
||||
eprintln!(
|
||||
"{level:?} [{}] [{}:{}] {}",
|
||||
record.ts,
|
||||
record.filename.unwrap_or(""),
|
||||
record.line_number.unwrap_or(0),
|
||||
record.message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct ModuleBuilder {
|
||||
/// The module definition.
|
||||
inner: RawModuleDefV9,
|
||||
/// The reducers of the module.
|
||||
reducers: IndexMap<String, usize>,
|
||||
}
|
||||
|
||||
#[repr(usize)]
|
||||
enum GlobalInternalField {
|
||||
WrapperModule,
|
||||
Last,
|
||||
}
|
||||
|
||||
// fn builtins_snapshot() -> anyhow::Result<Arc<[u8]>> {
|
||||
fn builtins_snapshot() -> anyhow::Result<(v8::OwnedIsolate, v8::Global<v8::Context>)> {
|
||||
let isolate = v8::Isolate::snapshot_creator(Some(&EXTERN_REFS), None);
|
||||
let mut isolate = scopeguard::guard(isolate, |isolate| {
|
||||
// rusty_v8 panics if we don't call this when dropping isolate
|
||||
isolate.create_blob(v8::FunctionCodeHandling::Clear);
|
||||
});
|
||||
let context = {
|
||||
let isolate = &mut *isolate;
|
||||
let handle_scope = &mut v8::HandleScope::new(isolate);
|
||||
|
||||
let global_template = v8::ObjectTemplate::new(handle_scope);
|
||||
global_template.set_internal_field_count(GlobalInternalField::Last as usize);
|
||||
let context = v8::Context::new(
|
||||
handle_scope,
|
||||
v8::ContextOptions {
|
||||
global_template: Some(global_template),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
||||
scope.set_default_context(context);
|
||||
assert_eq!(scope.add_context(context), 0);
|
||||
let global = context.global(scope);
|
||||
// scope.add_context_data(context, global);
|
||||
// scope.add_context_
|
||||
// scope.get_current_context().set_slot(logger);
|
||||
catch_exception(scope, |scope| {
|
||||
let name = ascii_str!("spacetime:wrapper").string(scope).into();
|
||||
let module = init_module(scope, name, 0, include_str!("./wrapper.js"), resolve_internal_module)?;
|
||||
|
||||
// this is hacky
|
||||
global.set_internal_field(GlobalInternalField::WrapperModule as usize, module.into());
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
v8::Global::new(scope, context)
|
||||
};
|
||||
|
||||
// let snapshot = scopeguard::ScopeGuard::into_inner(isolate)
|
||||
// .create_blob(v8::FunctionCodeHandling::Clear)
|
||||
// .unwrap();
|
||||
|
||||
// Ok((*snapshot).into())
|
||||
|
||||
Ok((scopeguard::ScopeGuard::into_inner(isolate), context))
|
||||
}
|
||||
|
||||
fn compile(program: &str, logger: Arc<Logger>) -> anyhow::Result<(Arc<[u8]>, ModuleBuilder)> {
|
||||
// let builtins = builtins_snapshot()?;
|
||||
// let isolate = v8::Isolate::snapshot_creator_from_existing_snapshot(builtins, Some(&EXTERN_REFS), None);
|
||||
let (isolate, context) = builtins_snapshot()?;
|
||||
let mut isolate = scopeguard::guard(isolate, |isolate| {
|
||||
// rusty_v8 panics if we don't call this when dropping isolate
|
||||
isolate.create_blob(v8::FunctionCodeHandling::Keep);
|
||||
});
|
||||
isolate.set_slot(ModuleBuilder::default());
|
||||
isolate.set_slot(logger.clone());
|
||||
|
||||
{
|
||||
let isolate = &mut *isolate;
|
||||
let handle_scope = &mut v8::HandleScope::new(isolate);
|
||||
// let context = v8::Context::from_snapshot(handle_scope, 0, Default::default()).unwrap();
|
||||
let context = v8::Local::new(handle_scope, context);
|
||||
let scope = &mut v8::ContextScope::new(handle_scope, context);
|
||||
|
||||
// scope.set_prepare_stack_trace_callback(prepare_stack_trace);
|
||||
|
||||
// scope.set_default_context(context);
|
||||
|
||||
catch_exception(scope, |scope| {
|
||||
let name = ascii_str!("spacetime:module").string(scope).into();
|
||||
init_module(scope, name, 1, program, resolve_wrapper_module)?;
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
let module_builder = isolate.remove_slot::<ModuleBuilder>().unwrap();
|
||||
|
||||
let snapshot = scopeguard::ScopeGuard::into_inner(isolate)
|
||||
.create_blob(v8::FunctionCodeHandling::Keep)
|
||||
.unwrap();
|
||||
// d923b61bd4a4a000589af55b9ac5f046e97c4c756c96427fbc24d1253e7c9c77
|
||||
// dbg!(spacetimedb_lib::hash_bytes(&snapshot));
|
||||
let snapshot = <Arc<[u8]>>::from(&*snapshot);
|
||||
|
||||
Ok((snapshot, module_builder))
|
||||
}
|
||||
|
||||
// fn prepare_stack_trace<'s>(
|
||||
// scope: &mut v8::HandleScope<'s>,
|
||||
// error: v8::Local<'_, v8::Value>,
|
||||
// sites: v8::Local<'_, v8::Array>,
|
||||
// ) -> v8::Local<'s, v8::Value> {
|
||||
// error.
|
||||
// todo!();
|
||||
// }
|
||||
|
||||
fn find_source_map(program: &str) -> Option<&str> {
|
||||
let sm_ref = "//# sourceMappingURL=";
|
||||
program.match_indices(sm_ref).find_map(|(i, _)| {
|
||||
let (before, after) = program.split_at(i);
|
||||
(before.is_empty() || before.ends_with(['\r', '\n']))
|
||||
.then(|| &after.lines().next().unwrap_or(after)[sm_ref.len()..])
|
||||
})
|
||||
}
|
||||
|
||||
fn init_module<'s>(
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
resource_name: v8::Local<'s, v8::Value>,
|
||||
script_id: i32,
|
||||
program: &str,
|
||||
resolve_module: impl v8::MapFnTo<v8::ResolveModuleCallback<'s>>,
|
||||
) -> Result<v8::Local<'s, v8::Module>, ErrorOrException> {
|
||||
let source = v8::String::new(scope, program).err()?;
|
||||
let source_map_url = find_source_map(program).map(|r| v8::String::new(scope, r).unwrap().into());
|
||||
let origin = v8::ScriptOrigin::new(
|
||||
scope,
|
||||
resource_name,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
script_id,
|
||||
source_map_url,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
None,
|
||||
);
|
||||
let source = &mut v8::script_compiler::Source::new(source, Some(&origin));
|
||||
let module = v8::script_compiler::compile_module(scope, source).err()?;
|
||||
|
||||
module.instantiate_module(scope, resolve_module).err()?;
|
||||
|
||||
module.evaluate(scope).err()?;
|
||||
|
||||
if module.get_status() == v8::ModuleStatus::Errored {
|
||||
let exc = v8::Local::new(scope, module.get_exception());
|
||||
throw(scope, exc)?;
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fn resolve_internal_module<'s>(
|
||||
context: v8::Local<'s, v8::Context>,
|
||||
spec: v8::Local<'s, v8::String>,
|
||||
_attrs: v8::Local<'s, v8::FixedArray>,
|
||||
_referrer: v8::Local<'s, v8::Module>,
|
||||
) -> Option<v8::Local<'s, v8::Module>> {
|
||||
let scope = &mut *unsafe { v8::CallbackScope::new(context) };
|
||||
if spec == spacetime_sys_10_0::SPEC_STRING.string(scope) {
|
||||
Some(spacetime_sys_10_0::make(scope))
|
||||
} else {
|
||||
let exc = module_exception(scope, spec);
|
||||
throw(scope, exc).ok()
|
||||
}
|
||||
}
|
||||
|
||||
strings!(SPACETIME_MODULE = "spacetimedb");
|
||||
|
||||
fn resolve_wrapper_module<'s>(
|
||||
context: v8::Local<'s, v8::Context>,
|
||||
spec: v8::Local<'s, v8::String>,
|
||||
_attrs: v8::Local<'s, v8::FixedArray>,
|
||||
_referrer: v8::Local<'s, v8::Module>,
|
||||
) -> Option<v8::Local<'s, v8::Module>> {
|
||||
let scope = &mut *unsafe { v8::CallbackScope::new(context) };
|
||||
if spec == SPACETIME_MODULE.string(scope) {
|
||||
let module = context
|
||||
.global(scope)
|
||||
.get_internal_field(scope, GlobalInternalField::WrapperModule as usize)
|
||||
.unwrap()
|
||||
.cast::<v8::Module>();
|
||||
Some(module)
|
||||
} else {
|
||||
let exc = module_exception(scope, spec);
|
||||
throw(scope, exc).ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn module_exception(scope: &mut v8::HandleScope<'_>, spec: v8::Local<'_, v8::String>) -> TypeError<String> {
|
||||
let mut buf = scratch_buf::<32>();
|
||||
let spec = spec.to_rust_cow_lossy(scope, &mut buf);
|
||||
TypeError(format!("Could not find module {spec:?}"))
|
||||
}
|
||||
|
||||
module!(
|
||||
spacetime_sys_10_0 = "spacetime:sys/v10.0",
|
||||
function(console_log),
|
||||
symbol(console_level_error = "console.Level.Error"),
|
||||
symbol(console_level_warn = "console.Level.Warn"),
|
||||
symbol(console_level_info = "console.Level.Info"),
|
||||
symbol(console_level_debug = "console.Level.Debug"),
|
||||
symbol(console_level_trace = "console.Level.Trace"),
|
||||
symbol(console_level_panic = "console.Level.Panic"),
|
||||
function(register_reducer),
|
||||
function(register_type),
|
||||
);
|
||||
|
||||
static EXTERN_REFS: LazyLock<v8::ExternalReferences> =
|
||||
LazyLock::new(|| v8::ExternalReferences::new(&spacetime_sys_10_0::external_refs().collect_vec()));
|
||||
|
||||
fn console_log(scope: &mut v8::HandleScope<'_>, args: v8::FunctionCallbackArguments<'_>) -> ExcResult<()> {
|
||||
let logger = scope.get_slot::<Arc<Logger>>().unwrap().clone();
|
||||
let level = args.get(0);
|
||||
let level = if level == spacetime_sys_10_0::console_level_error(scope) {
|
||||
LogLevel::Error
|
||||
} else if level == spacetime_sys_10_0::console_level_warn(scope) {
|
||||
LogLevel::Warn
|
||||
} else if level == spacetime_sys_10_0::console_level_info(scope) {
|
||||
LogLevel::Info
|
||||
} else if level == spacetime_sys_10_0::console_level_debug(scope) {
|
||||
LogLevel::Debug
|
||||
} else if level == spacetime_sys_10_0::console_level_trace(scope) {
|
||||
LogLevel::Trace
|
||||
} else if level == spacetime_sys_10_0::console_level_panic(scope) {
|
||||
LogLevel::Panic
|
||||
} else {
|
||||
throw(scope, TypeError(ascii_str!("Invalid log level")))?
|
||||
};
|
||||
let msg = args.get(1).cast::<v8::String>();
|
||||
let mut buf = scratch_buf::<128>();
|
||||
let msg = msg.to_rust_cow_lossy(scope, &mut buf);
|
||||
let frame = v8::StackTrace::current_stack_trace(scope, 2)
|
||||
.err()?
|
||||
.get_frame(scope, 1)
|
||||
.err()?;
|
||||
let mut buf = scratch_buf::<32>();
|
||||
let filename = frame
|
||||
.get_script_name(scope)
|
||||
.map(|s| s.to_rust_cow_lossy(scope, &mut buf));
|
||||
let record = Record {
|
||||
// TODO: figure out whether to use walltime now or logical reducer now (env.reducer_start)
|
||||
ts: chrono::Utc::now(),
|
||||
target: None,
|
||||
filename: filename.as_deref(),
|
||||
line_number: Some(frame.get_line_number() as u32),
|
||||
message: &msg,
|
||||
};
|
||||
logger.write(level, &record, &());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn js_to_type(scope: &mut v8::HandleScope<'_>, val: v8::Local<'_, v8::Value>) -> ExcResult<sats::AlgebraicType> {
|
||||
strings!(
|
||||
REF = "ref",
|
||||
TYPE = "type",
|
||||
SUM = "sum",
|
||||
PRODUCT = "product",
|
||||
ARRAY = "array",
|
||||
STRING = "string",
|
||||
BOOL = "bool",
|
||||
I8 = "i8",
|
||||
U8 = "u8",
|
||||
I16 = "i16",
|
||||
U16 = "u16",
|
||||
I32 = "i32",
|
||||
U32 = "u32",
|
||||
I64 = "i64",
|
||||
U64 = "u64",
|
||||
I128 = "i128",
|
||||
U128 = "u128",
|
||||
I256 = "i256",
|
||||
U256 = "u256",
|
||||
F32 = "f32",
|
||||
F64 = "f64",
|
||||
);
|
||||
|
||||
let val = val.cast::<v8::Object>();
|
||||
let ty = val.get_str(scope, &TYPE).err()?;
|
||||
|
||||
if ty == REF.string(scope) {
|
||||
let r = val.get_str(scope, &REF).err()?.cast::<v8::Number>().value() as u32;
|
||||
Ok(sats::AlgebraicType::Ref(sats::AlgebraicTypeRef(r)))
|
||||
} else if ty == PRODUCT.string(scope) {
|
||||
let elements = val.get_str(scope, ascii_str!("elements")).err()?.cast();
|
||||
let elements = iter_array(scope, elements, |scope, elem| {
|
||||
let elem = elem.cast::<v8::Object>();
|
||||
|
||||
let name = elem.get_str(scope, ascii_str!("name")).err()?;
|
||||
let name = if name.is_null_or_undefined() {
|
||||
None
|
||||
} else {
|
||||
Some(name.cast::<v8::String>().to_rust_string_lossy(scope).into())
|
||||
};
|
||||
|
||||
let ty = elem.get_str(scope, ascii_str!("algebraic_type")).err()?;
|
||||
let ty = js_to_type(scope, ty)?;
|
||||
|
||||
Ok(sats::ProductTypeElement::new(ty, name))
|
||||
})
|
||||
.collect::<Result<Box<[_]>, _>>()?;
|
||||
|
||||
Ok(sats::AlgebraicType::product(elements))
|
||||
} else if ty == ARRAY.string(scope) {
|
||||
let elem_ty = val.get_str(scope, ascii_str!("elem_ty")).err()?;
|
||||
let elem_ty = js_to_type(scope, elem_ty)?;
|
||||
Ok(sats::AlgebraicType::array(elem_ty))
|
||||
} else if ty == STRING.string(scope) {
|
||||
Ok(sats::AlgebraicType::String)
|
||||
} else if ty == BOOL.string(scope) {
|
||||
Ok(sats::AlgebraicType::Bool)
|
||||
} else if ty == I8.string(scope) {
|
||||
Ok(sats::AlgebraicType::I8)
|
||||
} else if ty == U8.string(scope) {
|
||||
Ok(sats::AlgebraicType::U8)
|
||||
} else if ty == I16.string(scope) {
|
||||
Ok(sats::AlgebraicType::I16)
|
||||
} else if ty == U16.string(scope) {
|
||||
Ok(sats::AlgebraicType::U16)
|
||||
} else if ty == I32.string(scope) {
|
||||
Ok(sats::AlgebraicType::I32)
|
||||
} else if ty == U32.string(scope) {
|
||||
Ok(sats::AlgebraicType::U32)
|
||||
} else if ty == I64.string(scope) {
|
||||
Ok(sats::AlgebraicType::I64)
|
||||
} else if ty == U64.string(scope) {
|
||||
Ok(sats::AlgebraicType::U64)
|
||||
} else if ty == I128.string(scope) {
|
||||
Ok(sats::AlgebraicType::I128)
|
||||
} else if ty == U128.string(scope) {
|
||||
Ok(sats::AlgebraicType::U128)
|
||||
} else if ty == I256.string(scope) {
|
||||
Ok(sats::AlgebraicType::I256)
|
||||
} else if ty == U256.string(scope) {
|
||||
Ok(sats::AlgebraicType::U256)
|
||||
} else if ty == F32.string(scope) {
|
||||
Ok(sats::AlgebraicType::F32)
|
||||
} else if ty == F64.string(scope) {
|
||||
Ok(sats::AlgebraicType::F64)
|
||||
} else {
|
||||
throw(scope, TypeError(ascii_str!("Unknown type")))
|
||||
}
|
||||
}
|
||||
|
||||
fn register_reducer(scope: &mut v8::HandleScope<'_>, args: v8::FunctionCallbackArguments<'_>) -> ExcResult<()> {
|
||||
if scope.get_slot::<ModuleBuilder>().is_none() {
|
||||
throw(scope, TypeError(ascii_str!("You cannot dynamically register reducers")))?;
|
||||
}
|
||||
|
||||
let name = args.get(0).cast::<v8::String>();
|
||||
let params = args.get(1);
|
||||
|
||||
let params = js_to_type(scope, params)?.into_product().unwrap();
|
||||
|
||||
let function = args
|
||||
.get(2)
|
||||
.try_cast::<v8::Function>()
|
||||
.map_err(|_| TypeError(ascii_str!("Third argument to register_reducer must be function")))
|
||||
.throw(scope)?;
|
||||
|
||||
function.set_name(name);
|
||||
|
||||
let name = name.to_rust_string_lossy(scope);
|
||||
|
||||
let context = scope.get_current_context();
|
||||
let function_idx = scope.add_context_data(context, function);
|
||||
|
||||
let module = scope.get_slot_mut::<ModuleBuilder>().unwrap();
|
||||
module.inner.reducers.push(RawReducerDefV9 {
|
||||
name: (&*name).into(),
|
||||
params,
|
||||
lifecycle: None,
|
||||
});
|
||||
match module.reducers.entry(name) {
|
||||
indexmap::map::Entry::Vacant(v) => {
|
||||
v.insert(function_idx);
|
||||
}
|
||||
indexmap::map::Entry::Occupied(o) => {
|
||||
let msg = format!("Reducer {:?} already registered", o.key());
|
||||
throw(scope, TypeError(msg))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_type(scope: &mut v8::HandleScope<'_>, args: v8::FunctionCallbackArguments<'_>) -> ExcResult<u32> {
|
||||
if scope.get_slot::<ModuleBuilder>().is_none() {
|
||||
throw(scope, TypeError(ascii_str!("You cannot dynamically register reducers")))?;
|
||||
}
|
||||
|
||||
let name = args.get(0).cast::<v8::String>();
|
||||
let ty = args.get(1);
|
||||
|
||||
let mut buf = scratch_buf::<32>();
|
||||
let name = name.to_rust_cow_lossy(scope, &mut buf);
|
||||
let name = sats_name_to_scoped_name(&name);
|
||||
|
||||
let ty = js_to_type(scope, ty)?;
|
||||
|
||||
let module = scope.get_slot_mut::<ModuleBuilder>().unwrap();
|
||||
let r = module.inner.typespace.add(ty);
|
||||
module.inner.types.push(RawTypeDefV9 {
|
||||
name,
|
||||
ty: r,
|
||||
custom_ordering: false,
|
||||
});
|
||||
|
||||
Ok(r.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_compile_test() {
|
||||
let platform = v8::new_default_platform(0, false).make_shared();
|
||||
v8::V8::initialize_platform(platform);
|
||||
v8::V8::initialize();
|
||||
let program = include_str!("./test_code.js");
|
||||
let (_snapshot, module) = compile(program, Arc::new(Logger)).unwrap();
|
||||
// dbg!(module);
|
||||
// dbg!(module_idx, bytes::Bytes::copy_from_slice(&snapshot));
|
||||
// panic!();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl Module for JsModule {
|
||||
type Instance = JsInstance;
|
||||
|
||||
type InitialInstances<'a> = std::iter::Empty<JsInstance>;
|
||||
|
||||
fn initial_instances(&mut self) -> Self::InitialInstances<'_> {
|
||||
std::iter::empty()
|
||||
}
|
||||
|
||||
fn info(&self) -> Arc<ModuleInfo> {
|
||||
self.info.clone()
|
||||
}
|
||||
|
||||
fn create_instance(&self) -> Self::Instance {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn replica_ctx(&self) -> &ReplicaContext {
|
||||
&self.replica_context
|
||||
}
|
||||
|
||||
fn scheduler(&self) -> &Scheduler {
|
||||
&self.scheduler
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JsInstance {}
|
||||
|
||||
#[allow(unused)]
|
||||
impl ModuleInstance for JsInstance {
|
||||
fn trapped(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn init_database(&mut self, program: Program) -> anyhow::Result<Option<ReducerCallResult>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn update_database(
|
||||
&mut self,
|
||||
program: Program,
|
||||
old_module_info: Arc<ModuleInfo>,
|
||||
) -> anyhow::Result<UpdateDatabaseResult> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn call_reducer(&mut self, tx: Option<MutTxId>, params: CallReducerParams) -> ReducerCallResult {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { registerReducer, registerType, type } from 'spacetimedb';
|
||||
|
||||
const Foo = registerType(
|
||||
'Foo',
|
||||
type.product({
|
||||
bar: type.f32,
|
||||
baz: type.string,
|
||||
})
|
||||
);
|
||||
|
||||
console.log('hello there', new Error().stack);
|
||||
try {
|
||||
function x() {
|
||||
throw new Error('hello');
|
||||
}
|
||||
x();
|
||||
} catch (e) {
|
||||
// Error.captureStackTrace(e, )
|
||||
console.log('woww', e);
|
||||
}
|
||||
registerReducer('beepboop', [type.array(type.f32), type.bool, Foo], (x, y, z) => {
|
||||
// z.bar;
|
||||
});
|
||||
// console.log(registerReducer);
|
||||
// registerReducer(1, [], () => {});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"paths": {
|
||||
"spacetimedb": ["./wrapper"]
|
||||
},
|
||||
"inlineSourceMap": true
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
declare module 'spacetime:sys/v10.0' {
|
||||
export const console_level_error: unique symbol;
|
||||
export const console_level_warn: unique symbol;
|
||||
export const console_level_info: unique symbol;
|
||||
export const console_level_debug: unique symbol;
|
||||
export const console_level_trace: unique symbol;
|
||||
export const console_level_panic: unique symbol;
|
||||
type ConsoleLevel =
|
||||
| typeof console_level_error
|
||||
| typeof console_level_warn
|
||||
| typeof console_level_info
|
||||
| typeof console_level_debug
|
||||
| typeof console_level_trace
|
||||
| typeof console_level_panic;
|
||||
|
||||
export function console_log(level: ConsoleLevel, msg: string): void;
|
||||
|
||||
export function register_reducer(name: string, product_type: ProductType, func: Function): void;
|
||||
|
||||
export function register_type(name: string, type: AlgebraicType): number;
|
||||
|
||||
export type AlgebraicType = TypeRef | ProductType | ArrayType | PrimitiveType;
|
||||
export type TypeRef = Readonly<{
|
||||
type: 'ref';
|
||||
ref: number;
|
||||
}>;
|
||||
export type ProductType = Readonly<{
|
||||
type: 'product';
|
||||
elements: readonly ProductTypeElement[];
|
||||
}>;
|
||||
export type ProductTypeElement = Readonly<{
|
||||
name: string | null;
|
||||
algebraic_type: AlgebraicType;
|
||||
}>;
|
||||
export type ArrayType = Readonly<{
|
||||
type: 'array';
|
||||
elem_ty: AlgebraicType;
|
||||
}>;
|
||||
export type PrimitiveType = Readonly<
|
||||
| { type: 'string' }
|
||||
| { type: 'bool' }
|
||||
| { type: 'i8' }
|
||||
| { type: 'u8' }
|
||||
| { type: 'i16' }
|
||||
| { type: 'u16' }
|
||||
| { type: 'i32' }
|
||||
| { type: 'u32' }
|
||||
| { type: 'i64' }
|
||||
| { type: 'u64' }
|
||||
| { type: 'i128' }
|
||||
| { type: 'u128' }
|
||||
| { type: 'i256' }
|
||||
| { type: 'u256' }
|
||||
| { type: 'f32' }
|
||||
| { type: 'f64' }
|
||||
>;
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
pub(super) struct StringConst(v8::OneByteConst);
|
||||
|
||||
impl StringConst {
|
||||
pub(super) const fn new(s: &'static str) -> Self {
|
||||
Self(v8::String::create_external_onebyte_const(s.as_bytes()))
|
||||
}
|
||||
pub(super) fn string<'s>(&'static self, scope: &mut v8::HandleScope<'s, ()>) -> v8::Local<'s, v8::String> {
|
||||
// unwrap() b/c create_external_onebyte_const asserts new_from_onebyte_const's
|
||||
// preconditions (str < kMaxLength)
|
||||
v8::String::new_from_onebyte_const(scope, &self.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn scratch_buf<const N: usize>() -> [MaybeUninit<u8>; N] {
|
||||
[const { MaybeUninit::uninit() }; N]
|
||||
}
|
||||
|
||||
pub(super) trait ObjectExt {
|
||||
fn get_str<'s>(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
key: &'static StringConst,
|
||||
) -> Option<v8::Local<'s, v8::Value>>;
|
||||
}
|
||||
|
||||
impl ObjectExt for v8::Object {
|
||||
fn get_str<'s>(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
key: &'static StringConst,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let key = key.string(scope);
|
||||
self.get(scope, key.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn iter_array<'a, 'b, 's, T, F>(
|
||||
scope: &'a mut v8::HandleScope<'s>,
|
||||
array: v8::Local<'b, v8::Array>,
|
||||
mut map: F,
|
||||
) -> impl Iterator<Item = ExcResult<T>> + use<'a, 'b, 's, T, F>
|
||||
where
|
||||
F: FnMut(&mut v8::HandleScope<'s>, v8::Local<'s, v8::Value>) -> ExcResult<T> + 'a,
|
||||
{
|
||||
(0..array.length()).map(move |i| {
|
||||
let val = array.get_index(scope, i).err()?;
|
||||
map(scope, val)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ExceptionThrown;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum ErrorOrException<Exc = ExceptionThrown> {
|
||||
Err(anyhow::Error),
|
||||
Exception(Exc),
|
||||
}
|
||||
|
||||
impl<Exc> From<anyhow::Error> for ErrorOrException<Exc> {
|
||||
fn from(err: anyhow::Error) -> Self {
|
||||
Self::Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExceptionThrown> for ErrorOrException {
|
||||
fn from(err: ExceptionThrown) -> Self {
|
||||
Self::Exception(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorOrException<super::JsError>> for anyhow::Error {
|
||||
fn from(err: ErrorOrException<super::JsError>) -> Self {
|
||||
match err {
|
||||
ErrorOrException::Err(e) => e,
|
||||
ErrorOrException::Exception(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait ExceptionOptionExt {
|
||||
type T;
|
||||
fn err(self) -> Result<Self::T, ExceptionThrown>;
|
||||
}
|
||||
impl<T> ExceptionOptionExt for Option<T> {
|
||||
type T = T;
|
||||
fn err(self) -> Result<Self::T, ExceptionThrown> {
|
||||
self.ok_or(ExceptionThrown)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn throw<'s, T, E>(scope: &mut v8::HandleScope<'s>, err: E) -> Result<T, ExceptionThrown>
|
||||
where
|
||||
E: IntoException<'s>,
|
||||
{
|
||||
let exc = err.into_exception(scope);
|
||||
scope.throw_exception(exc);
|
||||
Err(ExceptionThrown)
|
||||
}
|
||||
|
||||
pub(super) trait ThrowExceptionResultExt<'s> {
|
||||
type T;
|
||||
fn throw(self, scope: &mut v8::HandleScope<'s>) -> Result<Self::T, ExceptionThrown>;
|
||||
}
|
||||
|
||||
impl<'s, T, E: IntoException<'s>> ThrowExceptionResultExt<'s> for Result<T, E> {
|
||||
type T = T;
|
||||
fn throw(self, scope: &mut v8::HandleScope<'s>) -> Result<Self::T, ExceptionThrown> {
|
||||
self.or_else(|err| throw(scope, err))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait IntoException<'s> {
|
||||
fn into_exception(self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::Value>;
|
||||
}
|
||||
|
||||
impl<'s> IntoException<'s> for v8::Local<'s, v8::Value> {
|
||||
fn into_exception(self, _scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::Value> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TypeError<M>(pub M);
|
||||
|
||||
impl<'s, M: IntoJsString<'s>> IntoException<'s> for TypeError<M> {
|
||||
fn into_exception(self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::Value> {
|
||||
let msg = self.0.into_string(scope);
|
||||
v8::Exception::type_error(scope, msg)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait IntoJsString<'s> {
|
||||
fn into_string(self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::String>;
|
||||
}
|
||||
impl<'s> IntoJsString<'s> for v8::Local<'s, v8::String> {
|
||||
fn into_string(self, _scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::String> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'s> IntoJsString<'s> for String {
|
||||
fn into_string(self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::String> {
|
||||
v8::String::new(scope, &self).unwrap()
|
||||
}
|
||||
}
|
||||
impl<'s> IntoJsString<'s> for &'static StringConst {
|
||||
fn into_string(self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::String> {
|
||||
self.string(scope)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn nicer_callback<F, R>(f: F) -> v8::FunctionCallback
|
||||
where
|
||||
F: Fn(&mut v8::HandleScope<'_>, v8::FunctionCallbackArguments<'_>) -> ExcResult<R> + Copy,
|
||||
R: ReturnValue,
|
||||
{
|
||||
let cb = move |scope: &mut v8::HandleScope<'_>,
|
||||
args: v8::FunctionCallbackArguments<'_>,
|
||||
rv: v8::ReturnValue<'_>| {
|
||||
match f(scope, args) {
|
||||
Ok(value) => value.set_return_value(rv),
|
||||
Err(ExceptionThrown) => {}
|
||||
}
|
||||
};
|
||||
v8::MapFnTo::map_fn_to(cb)
|
||||
}
|
||||
|
||||
pub(super) type ExcResult<T> = Result<T, ExceptionThrown>;
|
||||
|
||||
pub(super) trait ReturnValue {
|
||||
fn set_return_value(self, rv: v8::ReturnValue<'_>);
|
||||
}
|
||||
|
||||
macro_rules! impl_return_value {
|
||||
($t:ty, $self:ident, $func:ident($($args:tt)*)) => {
|
||||
impl ReturnValue for $t {
|
||||
fn set_return_value($self, mut rv: v8::ReturnValue<'_>) {
|
||||
rv.$func($($args)*);
|
||||
}
|
||||
}
|
||||
};
|
||||
($t:ty, $func:ident) => {
|
||||
impl_return_value!($t, self, $func(self));
|
||||
};
|
||||
}
|
||||
|
||||
impl_return_value!(v8::Local<'_, v8::Value>, set);
|
||||
impl_return_value!(bool, set_bool);
|
||||
impl_return_value!(i32, set_int32);
|
||||
impl_return_value!(u32, set_uint32);
|
||||
impl_return_value!(f64, set_double);
|
||||
impl_return_value!((), self, set_undefined());
|
||||
|
||||
pub(super) fn external_synthetic_steps<'s, F>(f: F) -> v8::ExternalReference<'s>
|
||||
where
|
||||
F: v8::MapFnTo<v8::SyntheticModuleEvaluationSteps<'s>>,
|
||||
{
|
||||
let pointer = f.map_fn_to() as _;
|
||||
v8::ExternalReference { pointer }
|
||||
}
|
||||
|
||||
macro_rules! ascii_str {
|
||||
($str:expr) => {
|
||||
const { &$crate::host::v8::util::StringConst::new($str) }
|
||||
};
|
||||
}
|
||||
pub(super) use ascii_str;
|
||||
|
||||
macro_rules! strings {
|
||||
($vis:vis $($name:ident = $val:expr),*$(,)?) => {
|
||||
$($vis static $name: $crate::host::v8::util::StringConst = $crate::host::v8::util::StringConst::new($val);)*
|
||||
};
|
||||
}
|
||||
pub(super) use strings;
|
||||
|
||||
macro_rules! module {
|
||||
($name:ident = $module_name:expr, $($export_kind:ident($export_name:ident $($export:tt)*)),*$(,)?) => {
|
||||
mod $name {
|
||||
pub const SPEC: &str = $module_name;
|
||||
$crate::host::v8::util::strings!(pub SPEC_STRING = SPEC);
|
||||
|
||||
#[allow(non_snake_case, non_upper_case_globals)]
|
||||
mod names {
|
||||
$crate::host::v8::util::strings!(pub(super) $($export_name = stringify!($export_name),)*);
|
||||
}
|
||||
|
||||
pub fn make<'s>(scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::Module> {
|
||||
let export_names = [$(names::$export_name.string(scope),)*];
|
||||
let spec = SPEC_STRING.string(scope);
|
||||
v8::Module::create_synthetic_module(scope, spec, &export_names, evaluation_steps)
|
||||
}
|
||||
|
||||
fn evaluation_steps<'s>(context: v8::Local<'s, v8::Context>, module: v8::Local<'s, v8::Module>) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let scope = &mut *unsafe { v8::CallbackScope::new(context) };
|
||||
$({
|
||||
let name = names::$export_name.string(scope);
|
||||
let val =
|
||||
$crate::host::v8::util::module!(@export scope, name, $export_kind($export_name $($export)*));
|
||||
module.set_synthetic_module_export(scope, name, val)?;
|
||||
})*
|
||||
Some(v8::undefined(scope).into())
|
||||
}
|
||||
|
||||
pub fn external_refs<'s>() -> impl Iterator<Item = v8::ExternalReference<'s>> {
|
||||
[
|
||||
$crate::host::v8::util::external_synthetic_steps(evaluation_steps),
|
||||
$($crate::host::v8::util::module!(@export_ref $export_kind($export_name $($export)*)),)*
|
||||
].into_iter()
|
||||
}
|
||||
|
||||
$($crate::host::v8::util::module!(@export_rust $export_kind($export_name $($export)*));)*
|
||||
}
|
||||
};
|
||||
(@export $scope:ident, $name:ident, function($export_name:ident)) => {{
|
||||
let func = v8::Function::new_raw($scope, $crate::host::v8::util::nicer_callback(super::$export_name)).unwrap();
|
||||
func.set_name($name);
|
||||
func.into()
|
||||
}};
|
||||
(@export_ref function($export_name:ident)) => {
|
||||
v8::ExternalReference { function: $crate::host::v8::util::nicer_callback(super::$export_name) }
|
||||
};
|
||||
(@export_rust function($($t:tt)*)) => {};
|
||||
(@export $scope:ident, $name:ident, symbol($export_name:ident = $symbol:expr)) => {{
|
||||
$export_name($scope).into()
|
||||
}};
|
||||
(@export_ref symbol($($t:tt)*)) => {
|
||||
#[cfg(any())] ()
|
||||
};
|
||||
(@export_rust symbol($export_name:ident = $symbol:expr)) => {
|
||||
pub fn $export_name<'s>(scope: &mut v8::HandleScope<'s, ()>) -> v8::Local<'s, v8::Symbol> {
|
||||
$crate::host::v8::util::strings!(STRING = $symbol);
|
||||
let string = STRING.string(scope);
|
||||
v8::Symbol::for_api(scope, string)
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(super) use module;
|
||||
@@ -0,0 +1,301 @@
|
||||
import {
|
||||
console_log,
|
||||
console_level_error,
|
||||
console_level_warn,
|
||||
console_level_info,
|
||||
console_level_debug,
|
||||
console_level_trace,
|
||||
console_level_panic,
|
||||
register_reducer,
|
||||
register_type,
|
||||
} from 'spacetime:sys/v10.0';
|
||||
|
||||
function fmtLog(...data: unknown[]) {
|
||||
return data.join(' ');
|
||||
}
|
||||
|
||||
const console = {
|
||||
__proto__: {},
|
||||
|
||||
[Symbol.toStringTag]: 'console',
|
||||
|
||||
assert: (condition = false, ...data: any) => {
|
||||
if (!condition) {
|
||||
console_log(console_level_error, fmtLog(...data));
|
||||
}
|
||||
},
|
||||
clear: () => {},
|
||||
debug: (...data: any) => {
|
||||
console_log(console_level_debug, fmtLog(...data));
|
||||
},
|
||||
error: (...data: any) => {
|
||||
console_log(console_level_error, fmtLog(...data));
|
||||
},
|
||||
info: (...data: any) => {
|
||||
console_log(console_level_info, fmtLog(...data));
|
||||
},
|
||||
log: (...data: any) => {
|
||||
console_log(console_level_info, fmtLog(...data));
|
||||
},
|
||||
table: (tabularData: unknown, properties: any) => {
|
||||
console_log(console_level_info, fmtLog(tabularData));
|
||||
},
|
||||
trace: (...data: any) => {
|
||||
console_log(console_level_trace, fmtLog(...data));
|
||||
},
|
||||
warn: (...data: any) => {
|
||||
console_log(console_level_warn, fmtLog(...data));
|
||||
},
|
||||
dir: (item: any, options: any) => {},
|
||||
dirxml: (...data: any) => {},
|
||||
|
||||
// Counting
|
||||
count: (label = 'default') => {},
|
||||
countReset: (label = 'default') => {},
|
||||
|
||||
// Grouping
|
||||
group: (...data: any) => {},
|
||||
groupCollapsed: (...data: any) => {},
|
||||
groupEnd: () => {},
|
||||
|
||||
// Timing
|
||||
time: (label = 'default') => {},
|
||||
timeLog: (label = 'default', ...data: any) => {},
|
||||
timeEnd: (label = 'default') => {},
|
||||
};
|
||||
// @ts-ignore
|
||||
globalThis.console = console;
|
||||
|
||||
const { freeze } = Object;
|
||||
|
||||
const stringType = Symbol('spacetimedb.type.string');
|
||||
const boolType = Symbol('spacetimedb.type.bool');
|
||||
const i8Type = Symbol('spacetimedb.type.i8');
|
||||
const u8Type = Symbol('spacetimedb.type.u8');
|
||||
const i16Type = Symbol('spacetimedb.type.i16');
|
||||
const u16Type = Symbol('spacetimedb.type.u16');
|
||||
const i32Type = Symbol('spacetimedb.type.i32');
|
||||
const u32Type = Symbol('spacetimedb.type.u32');
|
||||
const i64Type = Symbol('spacetimedb.type.i64');
|
||||
const u64Type = Symbol('spacetimedb.type.u64');
|
||||
const i128Type = Symbol('spacetimedb.type.i128');
|
||||
const u128Type = Symbol('spacetimedb.type.u128');
|
||||
const i256Type = Symbol('spacetimedb.type.i256');
|
||||
const u256Type = Symbol('spacetimedb.type.u256');
|
||||
const f32Type = Symbol('spacetimedb.type.f32');
|
||||
const f64Type = Symbol('spacetimedb.type.f64');
|
||||
|
||||
export const type = freeze({
|
||||
string: stringType,
|
||||
bool: boolType,
|
||||
i8: i8Type,
|
||||
u8: u8Type,
|
||||
i16: i16Type,
|
||||
u16: u16Type,
|
||||
i32: i32Type,
|
||||
u32: u32Type,
|
||||
i64: i64Type,
|
||||
u64: u64Type,
|
||||
i128: i128Type,
|
||||
u128: u128Type,
|
||||
i256: i256Type,
|
||||
u256: u256Type,
|
||||
f32: f32Type,
|
||||
f64: f64Type,
|
||||
array<const Elem extends AlgebraicType>(elem: Elem) {
|
||||
return new ArrayType(elem);
|
||||
},
|
||||
product<const Map extends ProductMap>(map: Map) {
|
||||
return new ProductType(map);
|
||||
},
|
||||
});
|
||||
|
||||
const toInternalType = Symbol('spacetimedb.toInternalType');
|
||||
|
||||
class ArrayType<Elem extends AlgebraicType> {
|
||||
#inner: import('spacetime:sys/v10.0').ArrayType;
|
||||
constructor(inner: Elem) {
|
||||
this.#inner = freeze({ type: 'array', elem_ty: convertType(inner) });
|
||||
}
|
||||
get [toInternalType]() {
|
||||
return this.#inner;
|
||||
}
|
||||
}
|
||||
|
||||
type ProductMap = { [s: string]: AlgebraicType };
|
||||
class ProductType<Map extends ProductMap> {
|
||||
#inner: import('spacetime:sys/v10.0').ProductType;
|
||||
constructor(map: Map) {
|
||||
const elements = freeze(
|
||||
Object.entries(map).map(([k, v]) => freeze({ name: k, algebraic_type: convertType(v) }))
|
||||
);
|
||||
this.#inner = freeze({ type: 'product', elements });
|
||||
}
|
||||
get [toInternalType]() {
|
||||
return this.#inner;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeRef<Type extends AlgebraicType> {
|
||||
#inner: import('spacetime:sys/v10.0').TypeRef;
|
||||
constructor(ref: number) {
|
||||
this.#inner = freeze({ type: 'ref', ref });
|
||||
}
|
||||
get [toInternalType]() {
|
||||
return this.#inner;
|
||||
}
|
||||
}
|
||||
|
||||
const primitives = freeze({
|
||||
string: freeze({ type: 'string' }),
|
||||
bool: freeze({ type: 'bool' }),
|
||||
i8: freeze({ type: 'i8' }),
|
||||
u8: freeze({ type: 'u8' }),
|
||||
i16: freeze({ type: 'i16' }),
|
||||
u16: freeze({ type: 'u16' }),
|
||||
i32: freeze({ type: 'i32' }),
|
||||
u32: freeze({ type: 'u32' }),
|
||||
i64: freeze({ type: 'i64' }),
|
||||
u64: freeze({ type: 'u64' }),
|
||||
i128: freeze({ type: 'i128' }),
|
||||
u128: freeze({ type: 'u128' }),
|
||||
i256: freeze({ type: 'i256' }),
|
||||
u256: freeze({ type: 'u256' }),
|
||||
f32: freeze({ type: 'f32' }),
|
||||
f64: freeze({ type: 'f64' }),
|
||||
});
|
||||
|
||||
function convertType(ty: AlgebraicType): import('spacetime:sys/v10.0').AlgebraicType {
|
||||
if (typeof ty === 'symbol') {
|
||||
switch (ty) {
|
||||
case type.string:
|
||||
return primitives.string;
|
||||
case type.bool:
|
||||
return primitives.bool;
|
||||
case type.i8:
|
||||
return primitives.i8;
|
||||
case type.u8:
|
||||
return primitives.u8;
|
||||
case type.i16:
|
||||
return primitives.i16;
|
||||
case type.u16:
|
||||
return primitives.u16;
|
||||
case type.i32:
|
||||
return primitives.i32;
|
||||
case type.u32:
|
||||
return primitives.u32;
|
||||
case type.i64:
|
||||
return primitives.i64;
|
||||
case type.u64:
|
||||
return primitives.u64;
|
||||
case type.i128:
|
||||
return primitives.i128;
|
||||
case type.u128:
|
||||
return primitives.u128;
|
||||
case type.i256:
|
||||
return primitives.i256;
|
||||
case type.u256:
|
||||
return primitives.u256;
|
||||
case type.f32:
|
||||
return primitives.f32;
|
||||
case type.f64:
|
||||
return primitives.f64;
|
||||
}
|
||||
} else if (ty != null) {
|
||||
const x = ty[toInternalType];
|
||||
if (x) return x;
|
||||
}
|
||||
throw new TypeError('Expected Spacetime type, got ' + ty);
|
||||
}
|
||||
|
||||
type PrimitiveType = Extract<(typeof type)[keyof typeof type], symbol>;
|
||||
|
||||
type AlgebraicType = TypeRef<any> | ProductType<any> | ArrayType<any> | PrimitiveType;
|
||||
|
||||
export type I8 = number;
|
||||
export type U8 = number;
|
||||
export type I16 = number;
|
||||
export type U16 = number;
|
||||
export type I32 = number;
|
||||
export type U32 = number;
|
||||
export type I64 = bigint;
|
||||
export type U64 = bigint;
|
||||
export type I128 = bigint;
|
||||
export type U128 = bigint;
|
||||
export type I256 = bigint;
|
||||
export type U256 = bigint;
|
||||
|
||||
type PrimitiveTypeToType<T extends PrimitiveType> = T extends typeof stringType
|
||||
? string
|
||||
: T extends typeof boolType
|
||||
? boolean
|
||||
: T extends typeof i8Type
|
||||
? I8
|
||||
: T extends typeof u8Type
|
||||
? U8
|
||||
: T extends typeof i16Type
|
||||
? I16
|
||||
: T extends typeof u16Type
|
||||
? U16
|
||||
: T extends typeof i32Type
|
||||
? I32
|
||||
: T extends typeof u32Type
|
||||
? U32
|
||||
: T extends typeof i64Type
|
||||
? I64
|
||||
: T extends typeof u64Type
|
||||
? U64
|
||||
: T extends typeof i128Type
|
||||
? I128
|
||||
: T extends typeof u128Type
|
||||
? U128
|
||||
: T extends typeof i256Type
|
||||
? I256
|
||||
: T extends typeof u256Type
|
||||
? U256
|
||||
: T extends typeof f32Type
|
||||
? number
|
||||
: T extends typeof f64Type
|
||||
? number
|
||||
: never;
|
||||
|
||||
type AlgebraicTypeToType<T extends AlgebraicType> = [T] extends [TypeRef<infer U>]
|
||||
? AlgebraicTypeToType<U>
|
||||
: [T] extends [ProductType<infer U>]
|
||||
? { [k in keyof U]: AlgebraicTypeToType<U[k]> }
|
||||
: [T] extends [ArrayType<infer U>]
|
||||
? AlgebraicTypeToType<U>[]
|
||||
: [T] extends [PrimitiveType]
|
||||
? PrimitiveTypeToType<T>
|
||||
: never;
|
||||
|
||||
type MakeArray<T> = T extends Array<any> ? T : never;
|
||||
|
||||
type ArgsToType<Args extends readonly AlgebraicType[]> = {
|
||||
[i in keyof Args]: AlgebraicTypeToType<Args[i]>;
|
||||
};
|
||||
|
||||
export function registerReducer<const Args extends readonly AlgebraicType[]>(
|
||||
name: string,
|
||||
params: Args,
|
||||
func: (...args: ArgsToType<Args>) => void
|
||||
) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('First argument to registerReducer must be string');
|
||||
}
|
||||
if (!Array.isArray(params)) {
|
||||
throw new TypeError('Second argument to registerReducer must be array');
|
||||
}
|
||||
const elements = freeze(
|
||||
params.map(ty => freeze({ name: null, algebraic_type: convertType(ty) }))
|
||||
);
|
||||
register_reducer(name, freeze({ type: 'product', elements }), func);
|
||||
}
|
||||
|
||||
export function registerType<Type extends AlgebraicType>(name: string, type: Type): TypeRef<Type> {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('First argument to registerType must be string');
|
||||
}
|
||||
const ref = register_type(name, convertType(type));
|
||||
return new TypeRef(ref);
|
||||
}
|
||||
Reference in New Issue
Block a user