Skip to content

Additive schedule registration for TypeScript for file-splitting#5435

Open
JasonAtClockwork wants to merge 9 commits into
masterfrom
jlarabie/ts-scheduled-table-cycle
Open

Additive schedule registration for TypeScript for file-splitting#5435
JasonAtClockwork wants to merge 9 commits into
masterfrom
jlarabie/ts-scheduled-table-cycle

Conversation

@JasonAtClockwork

@JasonAtClockwork JasonAtClockwork commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Closes: #4571 - The secondary issue specifically

Description of Changes

  • Added new onSchedule optional parameter for reducers and procedures as suggested by @cloutiertyler to move towards reactive reducers
    • Enforced at most one scheduled reducer/procedure per schedule table
  • Kept the existing table({ scheduled }) path working
  • Moved ScheduleAt column tracking onto table metadata
    • Lets the new schedule API resolve schedule-at columns without relying on legacy table options
    • Kept schedule.scheduleAtCol for compatibility

Other Options Considered

  • Add new additive API .schedule()
    • Was the first implementation of this branch but was the wrong direction for the future
  • Rust-inspired schedule token/name handle
    • Closest conceptual match to Rust: table stores a schedule descriptor/name and the reducer binds the implementation separately
    • Avoids importing reducers from table definitions and could preserve type checking if the token carries the row type
    • Adds more API surface and abstraction, and needs careful design to avoid users managing fragile names manually
  • scheduled: 'reducerName' string/name in table(...)
    • Very simple and closest to the raw module-def storage model
    • Fully breaks the runtime import cycle
    • Loses compile-time signature checking unless paired with another checker/registration API, and typos become runtime/module validation errors
    • This basically is what we had before 2.0

API and ABI breaking changes

  • Adds new TypeScript options to .reducer() and .procedure() for onSchedule
  • No intended breaking changes to existing table({ scheduled }) behavior

Expected complexity level and risk

3 - Preserving legacy scheduled table behavior while changing how schedule metadata is assembled

Testing

  • Added new tests to cover the legacy schedule and new registration
  • Updated module-test-ts coverage to show both legacy and new reducer scheduling
  • Ran a local throwaway test project to test before and after for both legacy + new api

@JasonAtClockwork JasonAtClockwork self-assigned this Jun 23, 2026
@JasonAtClockwork JasonAtClockwork marked this pull request as ready for review June 24, 2026 17:04
Comment thread crates/bindings-typescript/src/lib/table.ts Outdated
Comment thread crates/bindings-typescript/src/server/schema.test-d.ts Outdated
@cloutiertyler

Copy link
Copy Markdown
Contributor

For context, the solution that @coolreader18 and I discussed was to allow you to specify on the reducer, the table you wanted to schedule to. The one detail is that we would need to enforce (at least for now), that at most (and perhaps exactly) one reducer was subscribed to a schedule table. The reason being is that the calling order would not be well defined, and we didn't want people to rely on whatever order we end up calling them in. So that would look like this:

const scheduledMessages = table({public: true}, {
    scheduledId: t.u64().primaryKey().autoInc(),
    scheduledAt: t.scheduleAt(),
    text: t.string(),
 });
 
 const spacetimedb = schema({ scheduledMessages });
 
 spacetime.reducer({ on_schedule = spacetimedb.scheduledMessages });
 // or something similar

The idea was that it would align with the future reactive reducers envisioned in: https://github.com/clockworklabs/SpacetimeDBPrivate/pull/2862

This change would also be an additive change.

Also please add any new syntax we go with to the module-test-ts module. I'm really trying to make sure those are exhaustive.

@JasonAtClockwork

Copy link
Copy Markdown
Contributor Author

For context, the solution that @coolreader18 and I discussed was to allow you to specify on the reducer, the table you wanted to schedule to. The one detail is that we would need to enforce (at least for now), that at most (and perhaps exactly) one reducer was subscribed to a schedule table. The reason being is that the calling order would not be well defined, and we didn't want people to rely on whatever order we end up calling them in. So that would look like this:

const scheduledMessages = table({public: true}, {
    scheduledId: t.u64().primaryKey().autoInc(),
    scheduledAt: t.scheduleAt(),
    text: t.string(),
 });
 
 const spacetimedb = schema({ scheduledMessages });
 
 spacetime.reducer({ on_schedule = spacetimedb.scheduledMessages });
 // or something similar

The idea was that it would align with the future reactive reducers envisioned in: clockworklabs/SpacetimeDBPrivate#2862

This change would also be an additive change.

Also please add any new syntax we go with to the module-test-ts module. I'm really trying to make sure those are exhaustive.

I've completely refactored the PR to move in this direction as .schedule() is definitely the wrong direction and is removed from this branch. I have supported both .reducer() and .procedure() though procedures were not called out.

I did go with onSchedule rather than on_schedule to match the rest of TypeScript but as this is a bit of a departure and you think we should go with on_schedule I'll fix that up.

@cloutiertyler

Copy link
Copy Markdown
Contributor

onSchedule is actually the correct capitalization.

scheduled: (): any => repeatingTest,
},
repeatingTestArg
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please keep both the old and new syntax since the old syntax is still supported?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I've done is have one of the scheduled tests left as the legacy and one with the new structure. repeatingTest is set up with the new, and nonrepeatingTest is set up with the original:

  // nonrepeating_test_arg table with legacy scheduled(nonrepeating_test)
  nonrepeatingTestArg: table(
    {
      name: 'nonrepeating_test_arg',
      scheduled: (): any => nonrepeatingTest,
    },
    nonrepeatingTestArg
  ),

With how module-test is set up for ensure_schema between Rust and TypeScript it's less churn to not add a new test everywhere.

@cloutiertyler cloutiertyler left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now LGTM but with an important comment about module-test-ts that I'd like to address.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typescript scheduled tables fail to generate correctly

3 participants