Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f2401e6
Add CanBeLookupTable trait, event table support in query-builder/bind…
cloutiertyler Feb 12, 2026
48fcada
Add client SDK support for event tables
cloutiertyler Feb 12, 2026
46327f2
Add event table integration tests
cloutiertyler Feb 12, 2026
766bfcd
Add event tables documentation
cloutiertyler Feb 12, 2026
6813513
Remove event table from module-test to fix ensure_same_schema tests
cloutiertyler Feb 12, 2026
6d1b5ae
Add event table support to codegen (Rust/TS/C#) and client SDKs (TS/C#)
cloutiertyler Feb 13, 2026
6523070
Exclude onDelete/onUpdate from event table types in TypeScript SDK
cloutiertyler Feb 13, 2026
bb666c8
Run cargo fmt and pnpm format
cloutiertyler Feb 13, 2026
f0f22d1
Add RemoteEventTableHandle to hide OnDelete/OnUpdate from event table…
cloutiertyler Feb 13, 2026
9e0e81a
Fix remaining merge conflict in table.ts from rebase
cloutiertyler Feb 13, 2026
4f35a39
Fix Rust codegen with apparent borked rebase
gefjon Feb 13, 2026
31cd756
Regenerate view-client bindings and fix with_database_name in event-t…
cloutiertyler Feb 13, 2026
ef45624
Add CanBeLookupTable to client codegen for non-event tables and regen…
cloutiertyler Feb 13, 2026
1279749
Fix table_cache.ts: use sourceName instead of name for event table ca…
cloutiertyler Feb 13, 2026
6d5b64d
Add event tables documentation page
cloutiertyler Feb 13, 2026
8c8beb3
Revise event tables documentation based on review feedback
cloutiertyler Feb 13, 2026
899f2e0
Link to migration guide from event tables doc
cloutiertyler Feb 13, 2026
23d842f
Fix extract-schema V10 output and make C# table rowset parsing tolerant
JasonAtClockwork Feb 13, 2026
3cdbede
Updated RemoteTableHandle to split up in order to remove OnDelete/OnB…
JasonAtClockwork Feb 13, 2026
281264a
Updated regression tests to include new event tests + regenerated Bla…
JasonAtClockwork Feb 14, 2026
ab083e1
Blackholio removed usage of scheduled reducer information as it's now…
JasonAtClockwork Feb 14, 2026
c66ef52
Send EventTable wire variant for event tables and bypass client cache
cloutiertyler Feb 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ members = [
"modules/sdk-test-connect-disconnect",
"modules/sdk-test-procedure",
"modules/sdk-test-view",
"modules/sdk-test-event-table",
"sdks/rust/tests/test-client",
"sdks/rust/tests/test-counter",
"sdks/rust/tests/connect_disconnect_client",
"sdks/rust/tests/procedure-client",
"sdks/rust/tests/view-client",
"sdks/rust/tests/event-table-client",
"tools/ci",
"tools/upgrade-version",
"tools/license-check",
Expand Down
1 change: 1 addition & 0 deletions crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mod sym {
symbol!(unique);
symbol!(update);
symbol!(default);
symbol!(event);

symbol!(u8);
symbol!(i8);
Expand Down
22 changes: 22 additions & 0 deletions crates/bindings-macro/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub(crate) struct TableArgs {
scheduled: Option<ScheduledArg>,
name: Ident,
indices: Vec<IndexArg>,
event: Option<Span>,
}

enum TableAccess {
Expand Down Expand Up @@ -71,6 +72,7 @@ impl TableArgs {
let mut scheduled = None;
let mut name = None;
let mut indices = Vec::new();
let mut event = None;
syn::meta::parser(|meta| {
match_meta!(match meta {
sym::public => {
Expand All @@ -91,6 +93,10 @@ impl TableArgs {
check_duplicate(&scheduled, &meta)?;
scheduled = Some(ScheduledArg::parse_meta(meta)?);
}
sym::event => {
check_duplicate(&event, &meta)?;
event = Some(meta.path.span());
}
});
Ok(())
})
Expand All @@ -107,6 +113,7 @@ impl TableArgs {
scheduled,
name,
indices,
event,
})
}
}
Expand Down Expand Up @@ -852,6 +859,18 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
);

let table_access = args.access.iter().map(|acc| acc.to_value());
let is_event = args.event.iter().map(|_| {
quote!(
const IS_EVENT: bool = true;
)
});
let can_be_lookup_impl = if args.event.is_none() {
quote! {
impl spacetimedb::query_builder::CanBeLookupTable for #original_struct_ident {}
}
} else {
quote! {}
};
let unique_col_ids = unique_columns.iter().map(|col| col.index);
let primary_col_id = primary_key_column.clone().into_iter().map(|col| col.index);
let sequence_col_ids = sequenced_columns.iter().map(|col| col.index);
Expand Down Expand Up @@ -977,6 +996,7 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
const TABLE_NAME: &'static str = #table_name;
// the default value if not specified is Private
#(const TABLE_ACCESS: spacetimedb::table::TableAccess = #table_access;)*
#(#is_event)*
const UNIQUE_COLUMNS: &'static [u16] = &[#(#unique_col_ids),*];
const INDEXES: &'static [spacetimedb::table::IndexDesc<'static>] = &[#(#index_descs),*];
#(const PRIMARY_KEY: Option<u16> = Some(#primary_col_id);)*
Expand Down Expand Up @@ -1088,6 +1108,8 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R
}
}

#can_be_lookup_impl

};

let table_query_handle_def = quote! {
Expand Down
1 change: 1 addition & 0 deletions crates/bindings-typescript/src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export function tableToSchema<
};
}) as T['idxs'],
tableDef,
...(tableDef.isEvent ? { isEvent: true } : {}),
};
}

Expand Down
5 changes: 4 additions & 1 deletion crates/bindings-typescript/src/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export type UntypedTableDef = {
indexes: readonly IndexOpts<any>[];
constraints: readonly ConstraintOpts<any>[];
tableDef: Infer<typeof RawTableDefV10>;
isEvent?: boolean;
};

/**
Expand Down Expand Up @@ -179,6 +180,7 @@ export type TableOpts<Row extends RowObj> = {
{ [k: string]: RowBuilder<RowObj> },
ReturnType<typeof t.unit>
>;
event?: boolean;
};

/**
Expand Down Expand Up @@ -301,6 +303,7 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
public: isPublic = false,
indexes: userIndexes = [],
scheduled,
event: isEvent = false,
} = opts;

// 1. column catalogue + helpers
Expand Down Expand Up @@ -476,7 +479,7 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
tableType: { tag: 'User' },
tableAccess: { tag: isPublic ? 'Public' : 'Private' },
defaultValues,
isEvent: false,
isEvent,
};
},
idxs: {} as OptsIndices<Opts>,
Expand Down
37 changes: 31 additions & 6 deletions crates/bindings-typescript/src/sdk/client_table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type ClientTablePrimaryKeyMethods<
): void;
};

export type ClientTableMethods<
export type ClientTableInsertMethods<
RemoteModule extends UntypedRemoteModule,
TableName extends TableNamesOf<RemoteModule>,
> = {
Expand All @@ -66,7 +66,12 @@ export type ClientTableMethods<
row: Prettify<RowType<TableDefForTableName<RemoteModule, TableName>>>
) => void
): void;
};

export type ClientTableDeleteMethods<
RemoteModule extends UntypedRemoteModule,
TableName extends TableNamesOf<RemoteModule>,
> = {
/**
* Registers a callback to be invoked when a row is deleted from the table.
*/
Expand All @@ -89,6 +94,12 @@ export type ClientTableMethods<
): void;
};

export type ClientTableMethods<
RemoteModule extends UntypedRemoteModule,
TableName extends TableNamesOf<RemoteModule>,
> = ClientTableInsertMethods<RemoteModule, TableName> &
ClientTableDeleteMethods<RemoteModule, TableName>;

/**
* Table<Row, UniqueConstraintViolation = never, AutoIncOverflow = never>
*
Expand All @@ -107,6 +118,12 @@ export type ClientTable<
>
>;

type IsEventTable<TableDef extends UntypedTableDef> = TableDef extends {
isEvent: true;
}
? true
: false;

type HasPrimaryKey<TableDef extends UntypedTableDef> = ColumnsHavePrimaryKey<
TableDef['columns']
>;
Expand Down Expand Up @@ -142,13 +159,21 @@ export type ClientTableCoreImplementable<

/**
* Core methods of ClientTable, without the indexes mixed in.
* Includes only staticly known methods.
* Includes only statically known methods.
*
* Event tables only expose insert callbacks (no delete or update),
* matching the Rust SDK's `EventTable` trait.
*/
export type ClientTableCore<
RemoteModule extends UntypedRemoteModule,
TableName extends TableNamesOf<RemoteModule>,
> = ReadonlyTableMethods<TableDefForTableName<RemoteModule, TableName>> &
ClientTableMethods<RemoteModule, TableName> &
(HasPrimaryKey<TableDefForTableName<RemoteModule, TableName>> extends true
? ClientTablePrimaryKeyMethods<RemoteModule, TableName>
: {});
ClientTableInsertMethods<RemoteModule, TableName> &
(IsEventTable<TableDefForTableName<RemoteModule, TableName>> extends true
? {}
: ClientTableDeleteMethods<RemoteModule, TableName> &
(HasPrimaryKey<
TableDefForTableName<RemoteModule, TableName>
> extends true
? ClientTablePrimaryKeyMethods<RemoteModule, TableName>
: {}));
3 changes: 2 additions & 1 deletion crates/bindings-typescript/src/sdk/db_connection_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,8 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
return inserts.concat(deletes);
}
if (rows.tag === 'EventTable') {
// TODO: Decide how event tables should be merged into the cache.
// Event table rows are insert-only. The table cache handles skipping
// storage for event tables and only firing on_insert callbacks.
return this.#parseRowList('insert', tableName, rows.value.events);
}
return [];
Expand Down
17 changes: 17 additions & 0 deletions crates/bindings-typescript/src/sdk/table_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,23 @@ export class TableCacheImpl<
ctx: EventContextInterface<RemoteModule>
): PendingCallback[] => {
const pendingCallbacks: PendingCallback[] = [];

// Event tables: fire on_insert callbacks but don't store rows in the cache.
if (this.tableDef.isEvent) {
for (const op of operations) {
if (op.type === 'insert') {
pendingCallbacks.push({
type: 'insert',
table: this.tableDef.sourceName,
cb: () => {
this.emitter.emit('insert', ctx, op.row);
},
});
}
}
return pendingCallbacks;
}

// TODO: performance
const hasPrimaryKey = Object.values(this.tableDef.columns).some(
col => col.columnMetadata.isPrimaryKey === true
Expand Down
3 changes: 2 additions & 1 deletion crates/bindings/src/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,8 @@ pub fn register_table<T: Table>() {
.inner
.build_table(T::TABLE_NAME, product_type_ref)
.with_type(TableType::User)
.with_access(T::TABLE_ACCESS);
.with_access(T::TABLE_ACCESS)
.with_event(T::IS_EVENT);

for &col in T::UNIQUE_COLUMNS {
table = table.with_unique_constraint(col);
Expand Down
1 change: 1 addition & 0 deletions crates/bindings/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ pub trait TableInternal: Sized {
const PRIMARY_KEY: Option<u16> = None;
const SEQUENCES: &'static [u16];
const SCHEDULE: Option<ScheduleDesc<'static>> = None;
const IS_EVENT: bool = false;

/// Returns the ID of this table.
fn table_id() -> TableId;
Expand Down
7 changes: 6 additions & 1 deletion crates/codegen/src/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,14 @@ impl Lang for Csharp<'_> {
let csharp_table_class_name = csharp_table_name.clone() + "Handle";
let table_type = type_ref_name(module, table.product_type_ref);

let base_class = if table.is_event {
"RemoteEventTableHandle"
} else {
"RemoteTableHandle"
};
writeln!(
output,
"public sealed class {csharp_table_class_name} : RemoteTableHandle<EventContext, {table_type}>"
"public sealed class {csharp_table_class_name} : {base_class}<EventContext, {table_type}>"
);
indented_block(output, |output| {
writeln!(
Expand Down
Loading
Loading