New Features
🚀 Async inter-database communication 🚀
Rust and TypeScript modules can now call reducers in other databases asynchronously through declared dependencies and generated receiver bindings. A module declares its dependencies in spacetime.json, referencing either a local module path or a published database, and spacetime generate produces typed receiver bindings for each dependency, while spacetime dev publishes dependency databases alongside the main one.
Senders enqueue calls by inserting rows into generated outbox tables, which are insert-only and map their columns to the receiver's reducer parameters with compile-time type checking. Delivery is handled by a new IDC actor that manages concurrent outbox streams over HTTP/2, and optional on_result / onResult callbacks report delivery outcomes back to the sender. Dependencies are namespaced by name, so a module can depend on two databases that export identically named types.
Note: C# and C++ Async IDC are coming in a future release.
(#6156, #6313, #6364, #6375, #6376, #6327)
Nested C# submodules
C# modules can now nest submodules to arbitrary depth, and the same module assembly can be mounted as multiple independent instances. Tables, indexes, reducers, procedures, and views are routed to the correct instance; ctx.As selects an instance when passing context to functions, ctx.From provides instance-aware query builders, and scheduling overloads are context-aware. Generated C# client bindings expose nested namespace paths such as ctx.Db.SubmoduleA.SubmoduleC.
(#6112)
Import extensions in generated TypeScript bindings
spacetime generate --lang typescript now accepts --import-extension none|js|ts, also settable per generate entry in spacetime.json. Generated bindings can therefore work out of the box with moduleResolution: "nodenext" and runtimes that execute TypeScript directly (Node, Bun, Deno) without post-generate patching. The default, none, preserves existing output.
(#6097)
Breaking Changes
Consistent is_internal() semantics across module languages
is_internal() now means ctx.sender() == ctx.database_identity() in every module library. Previously Rust, C#, and C++ inferred it from a missing ConnectionId and TypeScript always reported false. HTTP handlers now run with a real per-request identity and JWT instead of Identity::ZERO, and their transactions keep that sender and JWT.
Behavior changes to be aware of:
initis no longer internal, since its sender is the database owner.- In TypeScript,
isInternalis now true for scheduled reducers and procedures. jwt()is none wheneveris_internal()is true.- HTTP handlers (unstable) expose a sender:
ctx.sender()in Rust and C++,ctx.senderin TypeScript,ctx.Senderin C#. - C++
get_caller_identity()now returns the sender rather than the database identity for callers without a JWT.
Modules built with older libraries keep their previous behavior until rebuilt; modules built with the new libraries export an updated __call_http_handler__ that older hosts will reject.
(#5892)
Bug Fixes
Generated TypeScript schemas typecheck with exactOptionalPropertyTypes
Generated TypeScript bindings no longer fail typechecking or produce never for conn.reducers when a project enables exactOptionalPropertyTypes. The affected option types now declare name?: string | undefined; there is no runtime change.
(#6096)
What's Changed
- Added async inter-database communication:
spacetime.jsondependencies, generated Rust and TypeScript receiver bindings, insert-only outbox tables with typed parameters, result callbacks, and the IDC delivery actor in #6156, #6313, #6364, #6375, #6376, and #6327 - Added nested C# submodules with instance-aware contexts, queries, and scheduling in #6112
- Added
--import-extensiontospacetime generate --lang typescriptin #6097 - Standardized
is_internal()across module languages and gave HTTP handlers a sender and JWT in #5892 - Fixed generated TypeScript bindings under
exactOptionalPropertyTypesin #6096
Full public changelog: v2.13.0...release/candidate/v2.14.0