From d7f5f2c87ea61adadee68c3060d0a7f7610851fe Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 12 Aug 2026 13:38:48 -0400 Subject: [PATCH 1/2] docs: document TypeScript onSchedule reducer/procedure registration (#5435) Document the additive schedule registration API from #5435: - Schedule Tables: lead the TypeScript example with onSchedule, note the one-scheduled-function-per-table constraint and file-splitting benefit, and demote the scheduled table-option thunk to a legacy callout - Scheduling Procedures: switch the example to procedure({ onSchedule }, ...) and note the t.unit() return requirement - Cheat sheet and reducer-context examples updated to the new syntax migrating-to-2.0 intentionally keeps the legacy syntax. --- .../00100-databases/00500-cheat-sheet.md | 13 ++++--- .../00300-reducers/00300-reducers.md | 6 ++-- .../00300-reducers/00400-reducer-context.md | 12 ++++--- .../00300-tables/00500-schedule-tables.md | 35 +++++++++++++++++-- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md index 5ecd5421564..5095da5a06e 100644 --- a/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md +++ b/docs/docs/00200-core-concepts/00100-databases/00500-cheat-sheet.md @@ -405,7 +405,7 @@ SPACETIMEDB_CLIENT_DISCONNECTED(on_disconnect, ReducerContext ctx) { /* ... */ } ```typescript const reminder = table( - { name: 'reminder', scheduled: (): any => send_reminder }, + { name: 'reminder' }, { id: t.u64().primaryKey().autoInc(), message: t.string(), @@ -413,9 +413,14 @@ const reminder = table( } ); -export const send_reminder = spacetimedb.reducer({ arg: reminder.rowType }, (ctx, { arg }) => { - console.log(`Reminder: ${arg.message}`); -}); +// `onSchedule` binds the reducer to the schedule table +export const send_reminder = spacetimedb.reducer( + { onSchedule: reminder }, + { arg: reminder.rowType }, + (ctx, { arg }) => { + console.log(`Reminder: ${arg.message}`); + } +); ``` diff --git a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md index 9f8f0fec51c..7e8a28ce52d 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md +++ b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00300-reducers.md @@ -559,7 +559,7 @@ import { schema, t, table } from 'spacetimedb/server'; // Define a schedule table for the procedure const fetchSchedule = table( - { name: 'fetch_schedule', scheduled: (): any => fetchExternalData }, + { name: 'fetch_schedule' }, { scheduledId: t.u64().primaryKey().autoInc(), scheduledAt: t.scheduleAt(), @@ -570,8 +570,10 @@ const fetchSchedule = table( const spacetimedb = schema({ fetchSchedule }); export default spacetimedb; -// The procedure to be scheduled +// The procedure to be scheduled, bound to the schedule table with `onSchedule`. +// A scheduled procedure must return `t.unit()`. export const fetchExternalData = spacetimedb.procedure( + { onSchedule: fetchSchedule }, { arg: fetchSchedule.rowType }, t.unit(), (ctx, { arg }) => { diff --git a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md index 3274f75fc57..f05e0db2d0e 100644 --- a/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md +++ b/docs/docs/00200-core-concepts/00200-functions/00300-reducers/00400-reducer-context.md @@ -325,7 +325,7 @@ Scheduled reducers and procedures are private by default in SpacetimeDB 2.x, so import { schema, table, t } from 'spacetimedb/server'; const scheduledTask = table( - { name: 'scheduled_task', scheduled: (): any => sendReminder }, + { name: 'scheduled_task' }, { taskId: t.u64().primaryKey().autoInc(), scheduledAt: t.scheduleAt(), @@ -336,9 +336,13 @@ const scheduledTask = table( const spacetimedb = schema({ scheduledTask }); export default spacetimedb; -export const sendReminder = spacetimedb.reducer({ arg: scheduledTask.rowType }, (_ctx, { arg }) => { - console.log(`Reminder: ${arg.message}`); -}); +export const sendReminder = spacetimedb.reducer( + { onSchedule: scheduledTask }, + { arg: scheduledTask.rowType }, + (_ctx, { arg }) => { + console.log(`Reminder: ${arg.message}`); + } +); ``` diff --git a/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md b/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md index 24c2a99284b..bc66f2cc91c 100644 --- a/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md +++ b/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md @@ -11,7 +11,7 @@ import { CppModuleVersionNotice } from "@site/src/components/CppModuleVersionNot Tables can trigger [reducers](../00200-functions/00300-reducers/00300-reducers.md) or [procedures](../00200-functions/00400-procedures.md) at specific times by including a special scheduling column. This allows you to schedule future actions like sending reminders, expiring items, or running periodic maintenance tasks. :::tip Scheduling Procedures -Procedures use the same scheduling pattern as reducers. Simply reference the procedure name in the `scheduled` attribute. This is particularly useful when you need scheduled tasks that make HTTP requests or perform other side effects. See [Scheduling Procedures](../00200-functions/00300-reducers/00300-reducers.md#scheduling-procedures) for an example. +Procedures use the same scheduling pattern as reducers: pass the `onSchedule` option in TypeScript, or reference the procedure name in the `scheduled` attribute in other languages. This is particularly useful when you need scheduled tasks that make HTTP requests or perform other side effects. See [Scheduling Procedures](../00200-functions/00300-reducers/00300-reducers.md#scheduling-procedures) for an example. ::: ## Defining a Schedule Table @@ -23,6 +23,35 @@ The table attribute uses `scheduled` (with a "d") because it refers to the **sch +In TypeScript, declare the binding on the reducer with the `onSchedule` option: + +```typescript +const reminder = table( + { name: 'reminder' }, + { + scheduledId: t.u64().primaryKey().autoInc(), + scheduledAt: t.scheduleAt(), + message: t.string(), + } +); + +export const sendReminder = spacetimedb.reducer( + { onSchedule: reminder }, + { arg: reminder.rowType }, + (_ctx, { arg }) => { + // Invoked automatically by the scheduler + // arg.message, arg.scheduledAt, arg.scheduledId + } +); +``` + +`onSchedule` registers the reducer as the schedule table's target. Because the table definition does not reference the reducer, the table and the reducer can live in separate files without a circular import. A schedule table can be bound to at most one reducer or procedure; binding a second one is a schema error. + +The same option works on procedures, provided the procedure's return type is `t.unit()`. See [Scheduling Procedures](../00200-functions/00300-reducers/00300-reducers.md#scheduling-procedures). + +:::note Legacy `scheduled` table option +Older code declares the binding on the table instead, using a thunk that forward-references the scheduled reducer: + ```typescript const reminder = table( { name: 'reminder', scheduled: (): any => sendReminder }, @@ -35,10 +64,12 @@ const reminder = table( export const sendReminder = spacetimedb.reducer({ arg: reminder.rowType }, (_ctx, { arg }) => { // Invoked automatically by the scheduler - // arg.message, arg.scheduledAt, arg.scheduledId }); ``` +This form still works, but the forward reference forces the table and reducer into the same file and defeats type inference (hence the `(): any =>` cast). Prefer `onSchedule` in new code. +::: + From ef42cfcba1ef8c17189f8e7bfd48ff7c45123631 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 12 Aug 2026 13:58:59 -0400 Subject: [PATCH 2/2] docs: make onSchedule lead example self-contained Add imports and schema setup so the recommended schedule-tables example is copy/pasteable, per review. --- .../00300-tables/00500-schedule-tables.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md b/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md index bc66f2cc91c..0c3f5df4518 100644 --- a/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md +++ b/docs/docs/00200-core-concepts/00300-tables/00500-schedule-tables.md @@ -26,6 +26,8 @@ The table attribute uses `scheduled` (with a "d") because it refers to the **sch In TypeScript, declare the binding on the reducer with the `onSchedule` option: ```typescript +import { schema, table, t } from 'spacetimedb/server'; + const reminder = table( { name: 'reminder' }, { @@ -35,6 +37,9 @@ const reminder = table( } ); +const spacetimedb = schema({ reminder }); +export default spacetimedb; + export const sendReminder = spacetimedb.reducer( { onSchedule: reminder }, { arg: reminder.rowType },