mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-10 17:49:49 -04:00
3f58b5951b
## Summary
Implements event tables end-to-end: server datastore, module bindings
(Rust/TypeScript/C#), client codegen (Rust/TypeScript/C#), client SDKs
(Rust/TypeScript/C#), and integration tests.
Event tables are tables whose rows are ephemeral — they persist to the
commitlog and are delivered to V2 subscribers, but are NOT merged into
committed state. Rows are only visible within the transaction that
inserted them. This is the mechanism that replaces reducer event
callbacks in 2.0.
## What's included
### Server
- `is_event` flag on `RawTableDefV10`, `TableDef`, `TableSchema`
- Event table rows recorded in TxData but skipped during committed state
merge
- Commitlog replay treats event table inserts as no-ops
- Migration validation rejects changing `is_event` between module
versions
- `SELECT * FROM *` excludes event tables
- V1 WebSocket subscriptions to event tables rejected with upgrade
message
- V2 subscription path delivers event table rows correctly
- `CanBeLookupTable` trait — event tables cannot be lookup tables in
semijoins
- Runtime view validation rejects event tables
### Module bindings
- **Rust**: `#[spacetimedb::table(name = my_events, public, event)]`
- **TypeScript**: `table({ event: true }, ...)`
- **C#**: `[Table(Event = true)]`
### Client codegen (`crates/codegen/`)
- **Rust**: Generates `EventTable` impl (insert-only) for event tables,
`Table` impl for normal tables. `CanBeLookupTable` emitted for non-event
tables.
- **TypeScript**: Emits `event: true` in generated table schemas.
`ClientTableCore` type excludes `onDelete`/`onUpdate` for event tables
via conditional types.
- **C#**: Generates classes inheriting from `RemoteEventTableHandle`
(which hides `OnDelete`/`OnBeforeDelete`/`OnUpdate`) for event tables.
### Client SDKs
- **Rust**: `EventTable` trait with insert-only callbacks, client cache
bypass, `count()` returns 0, `iter()` returns empty
- **TypeScript**: Event table cache bypass in `table_cache.ts` — fires
`onInsert` callbacks but doesn't store rows. Type-level narrowing
excludes delete/update methods.
- **C#**: `RemoteEventTableHandle` base class hides delete/update
events. Parse/Apply/PostApply handle `EventTableRows` wire format, skip
cache storage, fire only `OnInsert`.
### Tests
- 9 datastore unit tests (insert/delete/update semantics, replay,
constraints, indexes, auto-inc, cross-tx reset)
- 3 Rust SDK integration tests (basic events, multiple events per
reducer, no persistence across transactions)
- Codegen snapshot tests (Rust, TypeScript, C#)
- Trybuild compile tests (event tables rejected as semijoin lookup
tables)
## Deferred
- `on_delete` codegen for event tables (server only sends inserts;
client synthesis deferred)
- Event tables in subscription joins / views (well-defined but
restricted for now)
- C++ SDK support
- RLS integration test
## API and ABI breaking changes
- `is_event: bool` added to `RawTableDefV10` (appended, defaults to
`false` — existing modules unaffected)
- `CanBeLookupTable` trait bound on semijoin methods in query builder
(all non-event tables implement it, so existing code compiles unchanged)
- `RemoteEventTableHandle` added to C# SDK (new base class for generated
event table handles)
## Expected complexity level and risk
3 — Changes touch the schema pipeline end-to-end and all three client
SDKs, but each individual change is straightforward. The core risk area
is the committed state merge skip in `committed_state.rs`. Client SDK
changes are additive (new code paths for event tables, existing paths
unchanged).
## Testing
- [x] `cargo clippy --workspace --tests --benches` passes
- [x] `cargo test -p spacetimedb-codegen` (snapshot tests)
- [x] `cargo test -p spacetimedb-datastore --features
spacetimedb-schema/test -- event_table` (9 unit tests)
- [x] `pnpm format` passes
- [x] Rust SDK integration tests pass (`event_table_tests` module)
---------
Signed-off-by: Tyler Cloutier <cloutiertyler@users.noreply.github.com>
Co-authored-by: Phoebe Goldman <phoebe@goldman-tribe.org>
Co-authored-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: joshua-spacetime <josh@clockworklabs.io>
SpacetimeDB C++ Module Library Internal API
This directory contains the internal implementation of the SpacetimeDB C++ Module Library, matching the design of the C# bindings.
Overview
The Internal API provides:
- Module Definition Management: Automatic generation of module definitions using autogenerated types
- Type Registration: Type-safe registration system for tables and reducers
- FFI Wrappers: Safe wrappers around the SpacetimeDB ABI
- Table Operations: Type-safe CRUD operations on tables
- Reducer System: Framework for implementing reducers with proper serialization
Directory Structure
-
autogen/- Autogenerated types from Rust definitions (RawModuleDefV9, RawTableDefV9, etc.) -
Module.h/cpp- Main module management and registration -
FFI.h/cpp- Foreign Function Interface definitions
Key Components
1. Module Registration
The Module class is a singleton that manages:
- Table registration
- Reducer registration
- Type registration
- Module description generation
- Reducer invocation
2. Autogenerated Types
All module definition types are generated from the Rust codebase:
RawModuleDefV9- Main module definitionRawTableDefV9- Table definitionsRawReducerDefV9- Reducer definitionsTableAccess,TableType, etc. - Enums and supporting types
3. Table Implementation
Tables are implemented using the SPACETIMEDB_TABLE macro system:
- Define row types with field registration using
SPACETIMEDB_MODULE_STRUCT(modules) orSPACETIMEDB_STRUCT(clients) - Register tables with
SPACETIMEDB_TABLE(Type, "name", Access, ...constraints) - Use
ctx.db.table<T>("name")for table operations (insert, delete) - Tables are automatically registered during module initialization via
__preinit__functions
4. Reducer Implementation
Reducers are implemented using unified macro system:
SPACETIMEDB_REDUCER(name, ReducerContext ctx, ...params)- User-defined reducersSPACETIMEDB_INIT(name, ReducerContext ctx)- Module initializationSPACETIMEDB_CLIENT_CONNECTED(name, ReducerContext ctx)- Client connection handlerSPACETIMEDB_CLIENT_DISCONNECTED(name, ReducerContext ctx)- Client disconnection handler- The macros handle serialization, registration, and ABI integration automatically
Usage Example
// Define row type with field registration
struct Person {
uint32_t id;
std::string name;
uint8_t age;
};
SPACETIMEDB_MODULE_STRUCT(Person, id, name, age);
// Define table with constraints
SPACETIMEDB_TABLE(Person, "people", Public,
PrimaryKeyAutoInc(id),
Index(name)
);
// Define reducer with ReducerContext
SPACETIMEDB_REDUCER(add_person, ReducerContext ctx, std::string name, uint8_t age) {
// Access table and insert record
Person person{0, name, age}; // id will be auto-generated
ctx.db.table<Person>("people").insert(person);
LOG_INFO("Added person: " + name);
}
// Lifecycle reducers
SPACETIMEDB_INIT(init, ReducerContext ctx) {
LOG_INFO("Module initialized!");
return Ok();
}
SPACETIMEDB_CLIENT_CONNECTED(on_connect, ReducerContext ctx) {
LOG_INFO("Client connected!");
return Ok();
}
Migration from Old API
See MIGRATION_GUIDE.md for detailed steps on migrating from the manual module definition approach to this new Internal API.
Future Improvements
- Automatic BSATN serialization generation
- Macro-based table/reducer definitions
- Query builder API
- Index management