Implement event tables (server, Rust/TS/C# codegen + client SDKs)#4217
Open
cloutiertyler wants to merge 22 commits intomasterfrom
Open
Implement event tables (server, Rust/TS/C# codegen + client SDKs)#4217cloutiertyler wants to merge 22 commits intomasterfrom
cloutiertyler wants to merge 22 commits intomasterfrom
Conversation
c96550a to
57cc9a5
Compare
cloutiertyler
commented
Feb 6, 2026
c13a4b1 to
07d57a5
Compare
gefjon
reviewed
Feb 9, 2026
Contributor
|
A few things:
|
614401f to
ea81292
Compare
4f698fc to
1ec6de5
Compare
1ec6de5 to
1d669cb
Compare
…ings-macro, and codegen - Add `event` attribute to #[table] macro for declaring event tables - Add CanBeLookupTable trait (non-event tables implement it) - Generate CanBeLookupTable impls in Rust codegen - Update physical plan and query-builder for event table support - Update module-test with example event table - Update codegen snapshots
- Add EventTable trait for transient event table rows (no client cache persistence) - Add from_event_inserts() and into_event_diff() for event table update handling - Parse EventTable rows from v2 TableUpdateRows - Exclude event tables from automatic subscription plans (get_all) - Reject v1 client subscriptions to event tables with upgrade message - Prevent event tables from being used as join lookup tables - Export EventTable in SDK public API
- Add sdk-test-event-table module with event table and reducers - Add event-table-client test binary with generated bindings - Register event-table-client and sdk-test-event-table in workspace - Update test configuration
- Add event-tables-status.md tracking implementation progress - Add 1.0 to 2.0 migration guide
The MyEvent table and emit_event reducer belong in sdk-test-event-table, not in module-test which must stay in sync with C# and TypeScript modules.
- Rust codegen: branch on is_event to emit EventTable trait, skip delete/update callbacks - TS module bindings: add event option to table(), pass isEvent through schema - TS codegen: emit event: true for event tables - TS client SDK: event tables fire on_insert but skip cache storage - C# SDK: IsEventTable flag, parse EventTableRows, skip cache/indices, fire OnInsert only - C# codegen: pass isEventTable: true in constructor for event tables - Datastore: 5 new tests for constraints/indexes/auto_inc on event tables - Docs: update event-tables-status.md with completed items
Event tables only expose onInsert/removeOnInsert at the type level, matching the Rust SDK's EventTable trait. Split ClientTableMethods into ClientTableInsertMethods and ClientTableDeleteMethods, and use an IsEventTable conditional type in ClientTableCore to exclude delete and update methods for event tables.
…s in C# SDK Event tables only expose OnInsert at the type level, matching Rust's EventTable trait and the TypeScript conditional types. RemoteEventTableHandle inherits from RemoteTableHandle and uses private new to shadow the OnDelete, OnBeforeDelete, and OnUpdate events. Codegen generates event table classes inheriting from RemoteEventTableHandle instead.
fdf598e to
f0f22d1
Compare
At some point, a rebase on this branch appears to have accidentally overwritten the Rust codegen changes in #4257 . This commit fixes that, to integrate the event tables codegen changes with the prior WS V2 codegen changes. I've also taken the minor liberty of re-ordering the emitted top-level forms in a table definition file, in order to allow merging the definitions of `register_table` and `parse_table_update`, which this branch previously had duplicated into the `if` and `else` cases of the conditional on whether the table was an event table or not. Plus I added a few comments.
…eforeDelete/OnUpdate from RemoteEventTableHandle
The server was always sending event table rows as PersistentTable inserts, causing clients to store them in the cache. Fix the server to send the EventTable variant, fix Rust codegen to use into_event_diff() (bypassing the cache), revert C# Table.cs tolerance checks that are no longer needed, and fix event table test race conditions where tests passed without actually running assertions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_eventflag onRawTableDefV10,TableDef,TableSchemais_eventbetween module versionsSELECT * FROM *excludes event tablesCanBeLookupTabletrait — event tables cannot be lookup tables in semijoinsModule bindings
#[spacetimedb::table(name = my_events, public, event)]table({ event: true }, ...)[Table(Event = true)]Client codegen (
crates/codegen/)EventTableimpl (insert-only) for event tables,Tableimpl for normal tables.CanBeLookupTableemitted for non-event tables.event: truein generated table schemas.ClientTableCoretype excludesonDelete/onUpdatefor event tables via conditional types.RemoteEventTableHandle(which hidesOnDelete/OnBeforeDelete/OnUpdate) for event tables.Client SDKs
EventTabletrait with insert-only callbacks, client cache bypass,count()returns 0,iter()returns emptytable_cache.ts— firesonInsertcallbacks but doesn't store rows. Type-level narrowing excludes delete/update methods.RemoteEventTableHandlebase class hides delete/update events. Parse/Apply/PostApply handleEventTableRowswire format, skip cache storage, fire onlyOnInsert.Tests
Deferred
on_deletecodegen for event tables (server only sends inserts; client synthesis deferred)API and ABI breaking changes
is_event: booladded toRawTableDefV10(appended, defaults tofalse— existing modules unaffected)CanBeLookupTabletrait bound on semijoin methods in query builder (all non-event tables implement it, so existing code compiles unchanged)RemoteEventTableHandleadded 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
cargo clippy --workspace --tests --benchespassescargo test -p spacetimedb-codegen(snapshot tests)cargo test -p spacetimedb-datastore --features spacetimedb-schema/test -- event_table(9 unit tests)pnpm formatpassesevent_table_testsmodule)