This commit is contained in:
Noa
2025-04-29 12:08:51 -05:00
committed by Mazdak Farrokhzad
parent e5f0adc17a
commit 56585bfca5
10 changed files with 1420 additions and 6 deletions
+11
View File
@@ -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
View File
@@ -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"
+2
View File
@@ -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}
+1
View File
@@ -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;
+577
View File
@@ -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!()
}
}
+25
View File
@@ -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, [], () => {});
+9
View File
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "ESNext",
"paths": {
"spacetimedb": ["./wrapper"]
},
"inlineSourceMap": true
}
}
+57
View File
@@ -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' }
>;
}
+279
View File
@@ -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;
+301
View File
@@ -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);
}