Skip to content

[T3 Connect] TypeScript SDK: types for NodeNext and exactOptionalPropertyTypes consumers - #6009

Draft
bradleyshep wants to merge 1 commit into
masterfrom
bradley/ts-sdk-strict-consumers
Draft

bradleyshep wants to merge 1 commit into
masterfrom
bradley/ts-sdk-strict-consumers

Conversation

@bradleyshep

@bradleyshep bradleyshep commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Part of the work to move T3 Code's T3 Connect relay onto SpacetimeDB.

Description of Changes

The published type files don't work for two common strict TypeScript setups.

  • NodeNext resolution. The type files re-export with extensionless paths such as './lib/identity'. The package is "type": "module", so NodeNext treats them as ESM and requires extensions; importing spacetimedb then finds no exports at all (Module '"spacetimedb"' has no exported member 'DbConnectionBuilder'). skipLibCheck doesn't help. Every relative import in the SDK now has an explicit extension, which carries into the published .d.ts files. A lint rule keeps it that way, and codegen (regen-typescript-moduledef) and generate-client-api emit extensions too.
  • exactOptionalPropertyTypes. Built table schemas type constraint names as string | undefined, but ConstraintOpts and IndexOpts declare name?: string. Under the flag, every generated table fails the SDK's own constraint and conn.reducers becomes never. Both option types now declare name?: string | undefined.

tests/consumer typechecks a small strict app against the built package, once with Bundler and once with NodeNext resolution, both with exactOptionalPropertyTypes, and asserts tsc exits 0.

Split out of #6006.

API and ABI breaking changes

None. Relative import paths inside the package change; the public entry points and types don't.

Rollback safety impact

n/a

Expected complexity level and risk

  1. It touches about 105 files, but the change is mechanical and is covered by the lint rule and the consumer typecheck test.

Testing

  • pnpm build and pnpm test in crates/bindings-typescript (324 passing), including both consumer cases
  • The NodeNext consumer case fails against origin/master's SDK build ("has no exported member" for every import) and passes with this change
  • eslint and prettier --check
  • Compatibility: packed this branch and its base, then typechecked the same consumers (spacetimedb, spacetimedb/server, spacetimedb/react) with TypeScript 4.9, 5.0, 5.4, 5.9 and 6.0, under node (node10), Node16, NodeNext and Bundler resolution, with skipLibCheck on and off. No configuration that passed before fails after; 16 Node16/NodeNext configurations go from failing to passing. The .ts paths in the published .d.ts files resolve in every mode from TypeScript 5.0 up.
  • Runtime is unchanged: both builds load under ESM and CJS with the same 133 root exports
  • Consumers' own imports are untouched: only paths inside the package change, so an app importing its own files without extensions resolves them as before
  • Reviewer: check that other generators are unaffected
  • Not covered: bindings generated by spacetime generate for users still import without extensions, so NodeNext consumers of their own generated code need a follow-up in the TypeScript codegen

@bradleyshep
bradleyshep force-pushed the bradley/ts-sdk-strict-consumers branch 2 times, most recently from b0901aa to 02db3b5 Compare September 30, 2026 15:57
@bradleyshep
bradleyshep force-pushed the bradley/ts-sdk-strict-consumers branch from 02db3b5 to c44434a Compare September 30, 2026 17:38
@bradleyshep bradleyshep changed the title TypeScript SDK: types for NodeNext and exactOptionalPropertyTypes consumers [T3 Connect] TypeScript SDK: types for NodeNext and exactOptionalPropertyTypes consumers Sep 30, 2026
…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.
@bradleyshep
bradleyshep force-pushed the bradley/ts-sdk-strict-consumers branch from c44434a to 405f890 Compare October 1, 2026 17:26

This branch has not been deployed

No deployments
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.

1 participant