Skip to content

Async IDC: Typescript. - #6327

Merged
cloutiertyler merged 49 commits into
masterfrom
shub/idc-typescript
Oct 29, 2026
Merged

cloutiertyler merged 49 commits into
masterfrom
shub/idc-typescript

Conversation

@Shubham8287

@Shubham8287 Shubham8287 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Description of Changes

Based on #6376.

Adds TypeScript receiver bindings and outbox declarations for intermodule reducer calls.

It can be reviewed through commits:

  1. Generate TypeScript receiver bindings with reducer handles, argument types, signature hashes, and dependency-specific identity types.
  2. Wire receiver bindings into CLI generation and configured dependencies.
  3. Add typed outbox declarations, generated shorthand, and onResult callbacks.
  4. Map outbox columns to receiver parameters using .param({ name }) and make outboxes insert-only while preserving existing table APIs.
  5. Add end-to-end smoke tests covering TypeScript and Rust senders and receivers, callbacks, receiver updates, and two dependencies with identical type names.

Proposal drfits:

  • TypeScript sugar accepts only { name }, rather than { accessor, name }. The key passed to schema({...}) defines the accessor, matching regular TypeScript tables.
  • Without an explicit name, the sugar uses the outbox table's accessor name (case converted) as the canonical name rather than defaulting to <reducer>Outbox.

API and ABI breaking changes

NA

Rollback safety impact

n/a

Expected complexity level and risk

Testing

unit tests and smoketests.

@Shubham8287
Shubham8287 changed the base branch from master to shub/idc-ux October 17, 2026 08:26
@Shubham8287
Shubham8287 changed the base branch from shub/idc-ux to shub/idc-rust-outbox-macro October 17, 2026 08:30
@Shubham8287
Shubham8287 force-pushed the shub/idc-rust-outbox-macro branch 2 times, most recently from 138855d to b393c47 Compare October 22, 2026 07:22
@Shubham8287
Shubham8287 force-pushed the shub/idc-rust-outbox-macro branch from b393c47 to 8942ea8 Compare October 22, 2026 09:06
@Shubham8287
Shubham8287 force-pushed the shub/idc-typescript branch 2 times, most recently from 7f996d0 to ff91af0 Compare October 26, 2026 11:47
@Shubham8287
Shubham8287 changed the base branch from shub/idc-rust-outbox-macro to shub/idc-rust-outbox-macro-split October 26, 2026 12:14
@Shubham8287
Shubham8287 force-pushed the shub/idc-rust-outbox-macro-split branch from e657762 to 9d87560 Compare October 27, 2026 14:55
@Shubham8287
Shubham8287 force-pushed the shub/idc-typescript branch 2 times, most recently from bb4c0b4 to 90b2a05 Compare October 28, 2026 11:26
Shubham8287 and others added 5 commits October 29, 2026 02:49
# Description of Changes

This PR tidies the TypeScript side of async IDC after the review of
#6327: it drops the `declare const` brand on `ReceiverIdentity` and the
`boolean &` cast on `isOutbox`, takes `onResult` at the top level of the
reducer options, lets index lookups and query comparisons take a plain
`Identity`, and names the conflict when `onResult` is combined with
`onSchedule` or a lifecycle reducer. Previously the brand duplicated the
`unique symbol` that every `remoteModule` caller already passes
([thread](#6327 (comment))),
the cast only existed because `table()` intersected `TableSchema`'s
`isOutbox?: boolean` with its literal type
([thread](#6327 (comment))),
`reactive: { onResult }` didn't match the shipped `onSchedule` option
from #5435 or Rust's `#[reducer(on_result(t))]`
([thread](#6327 (comment))),
a reducer or view couldn't check `ctx.sender` against a `gw.identity()`
column without casting to a type users can't name, and both conflicts
were reported as a parameter mismatch. Now `ReceiverIdentity` is keyed
by the module symbol, `TableSchema` takes an `IsOutbox extends boolean =
boolean` parameter that `table()` sets directly, index keys and
comparison literals accept any `Identity` while rows keep the receiver's
type, each conflict has its own error, and proposals 0036 and 0037 match
in SpacetimeDBPrivate#2862 and SpacetimeDBPrivate#4056.

# API and ABI breaking changes

None for users, because everything here except the lookup and comparison
changes is new in #6327 and hasn't shipped, and those two only loosen
types, so code that compiled before still compiles. Code written against
#6327's `reactive: { onResult }` stops compiling, but only the SDK
tests, the `idc-sender-ts` smoketest, `module-test-ts` and the SDK
README used it, and they're updated here. `TableSchema`'s `isOutbox` is
now required, which only matters to code that builds a `TableSchema` by
hand instead of through `table()`, and the new parameter defaults to
`boolean`, so two-argument annotations like `TableSchema<Row, []>` keep
the full table API.

# Rollback safety impact

n/a

# Expected complexity level and risk

1

# Testing

- [x] `tsc -p tsconfig.json --noEmit` reports no new errors, and the
cases in `src/server/idc.test-d.ts` and
`src/server/table-compatibility.test-d.ts` pass, including the
`@ts-expect-error` ones that reject another module's identity, a raw
`Identity` and an unchecked `ctx.sender` as outbox targets
- [x] The new check-then-reply and view cases in `idc.test-d.ts` fail
without the `indexes.ts` and `query.ts` changes and pass with them
- [x] `vitest run tests/idc.test.ts` (18 passing), including the new
conflict errors
- [x] The full SDK unit suite passes apart from four files that need a
full workspace install (built wasm datastore, `react-dom`, and the
`spacetimedb` workspace link)
- [x] ESLint and Prettier on the changed SDK files and README
- [ ] The `idc-sender-ts` and `module-test-ts` modules build in CI
- [ ] CI
LookupValue was named after index lookups, but query comparisons use it
too, and the name didn't say what it does: it removes a receiver
identity's module brand and passes other types through.
TableSchema carried both isOutbox and outbox, and they always agreed.
The outbox field is now typed from the table options, so its presence is
the type-level marker for DbView and onResult. The internal table
definition keeps an isOutbox flag, now derived from outbox.
The comments said scope segments of type names stay unchanged, but the
validator converts them to PascalCase too, which the test's own
assertion (MyLib::Utils::MetaInfo) and describe.rs's scoped_ref test
both check.

@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.

I'm okay to have this merge. The one thing I have concerns with is:

export const PingDetails = __t.object("game_world.PingDetails", {   // ← sender's name baked in
  labels: __t.array(__t.string()),
  owner: __t.identity(),
});

game_world I don't think should belong here. I think rather it should belong here:

export const PingDetails = __t.object("PingDetails", {              // ← same as the source
  labels: __t.array(__t.string()),
  owner: __t.identity(),
});
export type PingDetails = __Infer<typeof PingDetails>;

const __receiverModule: unique symbol = globalThis.Symbol() as typeof __receiverModule;
// The scope is given once, where the dependency is attached. remoteModule records
// the named types reachable from these params, and the sender registers them
// as game_world.PingDetails.
export default __remoteModule("game_world", {
  "receivePing": { /* unchanged */ },
}, __receiverModule);

I'm having Claude prototype this in a PR into this PR, but I don't think that should necessarily block this from merging (it is not hard to fix later, because we can just change codegen). See #6414 for what I'm talking about.

@cloutiertyler
cloutiertyler added this pull request to the merge queue Oct 29, 2026
@cloutiertyler
cloutiertyler removed this pull request from the merge queue due to a manual request Oct 29, 2026
Receiver bindings formatted the receiver's type name with ScopedTypeName's
Display, which joins scope segments with `::`. Rust splits sats names on
`::` and `.`, but the TypeScript SDK's splitName splits only on `.`, so a
receiver type like module-test's Namespace.TestC registered in a
TypeScript sender as the invalid identifier `Namespace::TestC`, and the
sender failed to publish.
@cloutiertyler
cloutiertyler added this pull request to the merge queue Oct 29, 2026
Merged via the queue into master with commit b9b2a5a Oct 29, 2026
125 of 126 checks passed
@Shubham8287
Shubham8287 deleted the shub/idc-typescript branch October 29, 2026 11:20
This was referenced Oct 30, 2026
Penguinjanator pushed a commit to Penguinjanator/SpacetimeDB that referenced this pull request Oct 31, 2026
# Description of Changes

This PR teaches the agent skills about asynchronous inter-database
communication (IDC). Async IDC shipped in clockworklabs#6150 through clockworklabs#6327, but the
skills only mentioned receiver bindings in the CLI skill, so an agent
writing a module wouldn't know how to declare an outbox table or handle
`on_result`, or that a receiving database's `client_connected` must not
require a JWT. Following the docs page in clockworklabs#6424, this adds IDC sections
to the Rust server, TypeScript server, and concepts skills, switches the
CLI skill to the docs' "sending database" wording, and resyncs the Codex
plugin copy, without touching code or the plugin version.

# API and ABI breaking changes

This PR doesn't break anything because it only changes agent skill text.

# Rollback safety impact

n/a

# Expected complexity level and risk

1

# Testing

- [x] Compiled the Rust skill snippet against generated receiver
bindings with `cargo check --target wasm32-unknown-unknown`.
- [x] Typechecked the TypeScript skill snippet against generated
receiver bindings with `tsc --noEmit`.
- [x] `node codex-plugin/scripts/check-skills-sync.ts` reports the Codex
copy in sync.
- [ ] CI
Penguinjanator pushed a commit to Penguinjanator/SpacetimeDB that referenced this pull request Oct 31, 2026
# Description of Changes

This PR adds a Core Concepts page that documents asynchronous
inter-database communication (IDC). The feature landed in clockworklabs#6150, clockworklabs#6156,
clockworklabs#6313, clockworklabs#6364, clockworklabs#6375, clockworklabs#6376, and clockworklabs#6327, but the only docs so far covered
receiver-dependency configuration (clockworklabs#6362), so database developers had
nothing explaining what an outbox is, what delivery guarantees they get,
or how to send a call and handle its result. Following
SpacetimeDBPrivate#2863, the new page explains the concept and
motivation, leads with a lobby-to-game-world example in TypeScript and
Rust, and then covers outbox tables, receiving calls, `on_result`,
receiver changes, and current limitations, with links from the
Databases, `spacetime dev`, `spacetime.json`, and auth usage pages,
which now say "sending database" instead of "calling database", and a
new `parent` row in the `spacetime.json` reference, with no code
changes.

# API and ABI breaking changes

This PR doesn't break anything because it only adds and links
documentation.

# Rollback safety impact

n/a

# Expected complexity level and risk

1

# Testing

- [x] Ran the lobby and game world example end to end in both Rust and
TypeScript against a local standalone server built from master, and
confirmed the SQL and log output shown on the page.
- [x] Compiled or typechecked every smaller snippet, and checked
formatting with the repo's rustfmt and Prettier settings.
- [x] Reproduced the signature-mismatch error, the compile error after
regenerating bindings, and the `client_connected` retry behavior that
the page quotes.
- [x] `pnpm build` in `docs` passes with broken links and anchors set to
throw.
- [x] Published the example with `"parent": "lobby"` on a local server,
published again, and confirmed calls still deliver.
- [ ] Confirm IDC works between two databases on Maincloud, which the
"same server" limitation assumes.
- [ ] Remove the Rust index-lookup limitation if it is fixed before
release.
- [ ] CI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants