TypeScript SDK: strict TypeScript consumers, Expo codegen, short-lived tokens, and HTTP/2 for procedure HTTP - #6006
Closed
bradleyshep wants to merge 9 commits into
Closed
bradleyshep wants to merge 9 commits into
bradleyshep wants to merge 9 commits into
Conversation
…opertyTypes consumers
The published .d.ts files re-export with extensionless relative paths
(`export * from './lib/connection_id'`). The package is `"type": "module"`,
so TypeScript projects using `moduleResolution: node16/nodenext` cannot
resolve them and see no exports from `spacetimedb` at all.
Separately, `ConstraintOpts` and `IndexOpts` declare `name?: string` while
the SDK derives `{ name: string | undefined }` for them. Under
`exactOptionalPropertyTypes` every generated schema then fails
`UntypedSchemaDef`, and `conn.reducers` collapses to `never`.
- Give every relative import in the SDK source an explicit extension; tsc
copies specifiers into the emitted .d.ts files verbatim. The moduledef and
client-api generators now emit the extension too.
- Widen the optional `name` fields to `string | undefined`.
- Lint rejects extensionless relative imports in the SDK source, and a
test typechecks generated bindings against the built package with
`exactOptionalPropertyTypes` on.
…y points
The package is `"type": "module"`, so its .d.ts files are ES module types,
yet `require` resolves to the .cjs builds. Under node16 resolution
CommonJS consumers were told they were importing an ES module
("Masquerading as ESM" in arethetypeswrong) and could not require it.
build:types now mirrors dist/**/*.d.ts as .d.cts, and each entry that ships
a .cjs build points its `require` types condition at them. The consumer
types test also typechecks a Node ESM and a CommonJS consumer.
…nnot be applied Errors while processing an inbound message, whether an undecodable row (e.g. client bindings out of step with the module's schema) or a user callback that throws, escaped the WebSocket message listener. Browsers only report such errors, but Node rethrows them on the next tick, which killed the host process. The inbound drain now catches them, logs, drops the queued messages, and closes the socket, so `onDisconnect` receives the error the same way it does for a websocket error on an established connection.
Generated TypeScript bindings narrowed `db` with `declare db: DbView;`. Babel rejects `declare` class fields unless `allowDeclareFields` is set, and babel-preset-expo does not set it, so React Native and Expo apps could not bundle generated bindings for any module with snake_case table accessor aliases. Declare the narrowed type on an interface merged into the class instead. It is type-only like before and emits nothing in any transpiler. Updated the codegen snapshot and the checked-in bindings to match.
`withToken` only took a string, and the ConnectionManager re-applies the session's token on every automatic rebuild. For third-party OIDC tokens such as Clerk session JWTs (about a minute of lifetime) that token is the original JWT, so once it expired every reconnect failed with 401 and retried forever. React apps also had no way to switch identity while mounted: `retain()` ignores a new builder once a connection is live. `withToken` now also accepts a `TokenProvider`, a sync or async function that is called on every connection attempt: the initial build, automatic reconnects, resume and zombie revival, and `rebuild()`. Its value is that attempt's token (`undefined` connects anonymously) and `conn.token` reflects it. A throw or rejection reaches `onConnectError`, so the manager retries it with backoff. The manager no longer resumes the session token over a builder with a provider; string tokens resume as before. `TokenProvider` is exported from the package root. The React context gains `reconnect(builder)`, calling `ConnectionManager.rebuild()` like the Svelte provider does. The Clerk guide now passes a provider instead of a token fetched once.
Enable reqwest's `native-tls-alpn` feature so the native-tls connector offers ALPN `h2`. Without it every outbound `ctx.http.fetch` over HTTPS was pinned to HTTP/1.1, and HTTP/2-only APIs such as Apple's APNs (iOS push notifications) could not be reached from modules.
/v1/identity/websocket-token re-signed any token it accepted, its own included, with a fresh iat and an exp 60 s out. Re-signing each copy before it expired renewed a token forever, and modules could never tell when a session token was issued. The copy now keeps the original iat and expires within 60 s but never after the original.
…ire entry points" This reverts commit b2e4ae6. T3 Code, the consumer these SDK changes were made for, only imports the SDK as an ES module, and typechecks without the .d.cts declarations. The TokenProvider check a later commit added to the removed Node consumer tests moves to the strict consumer app.
…ct-types # Conflicts: # crates/bindings-typescript/src/sdk/reducers.ts # crates/bindings-typescript/src/server/index.ts
This was referenced Sep 29, 2026
Draft
Draft
Contributor
Author
3 of 4 tasks
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.
Description of Changes
Changes needed to use the TypeScript SDK from a strict Node/TypeScript codebase with Clerk auth, and to send iOS pushes from a module:
exactOptionalPropertyTypesconsumers. The published type files re-export with extensionless paths such as'./lib/identity', so under NodeNext resolutionspacetimedbappears to have no exports. Every relative import now has an explicit extension, a lint rule keeps it that way, and codegen andgenerate-client-apiemit them too. Separately, underexactOptionalPropertyTypesevery generated table schema failed the SDK's own constraint (name?: stringversusstring | undefined), soconn.reducersbecamenever. Those two option types now allowundefined.tests/consumertypechecks a strict app against the built package.DbConnection.dbwith a merged interface instead of adeclarefield. Babel rejectsdeclarefields unlessallowDeclareFieldsis set, whichbabel-preset-expodoesn't do, so Expo apps couldn't bundle generated bindings.withTokenalso accepts a function, called before every attempt, anduseSpacetimeDB()gainsreconnect(builder).native-tls-alpn), for HTTP/2-only APIs such as APNs./v1/identity/websocket-tokenre-signed any accepted token with a freshiatand anexp60 s out, so a token could be renewed forever. The copy now keeps the originaliatand never expires after the original.A smaller alternative that keeps only the crash fix, the token function and HTTP/2 is
bradley/ts-sdk-bundler. The history includes a CommonJS-types commit and its revert; squash on merge.API and ABI breaking changes
None intended. The import-extension change affects only relative paths inside the package.
withTokenaccepts a function in addition to a string.Rollback safety impact
n/a
Expected complexity level and risk
Testing
pnpm testincrates/bindings-typescript(329 passing), including the strict consumer typecheckeslint srcexactOptionalPropertyTypes), a web client and an Expo app against a local 2.11 standalone