Skip to content

feat: dispatch the hosted /generators/* routes through @supabase/typegen - #1155

Merged
spydon merged 10 commits into
masterfrom
lukasklingsbo/sdk-1962-typegen-registry
Oct 6, 2026
Merged

spydon merged 10 commits into
masterfrom
lukasklingsbo/sdk-1962-typegen-registry

Conversation

@spydon

@spydon spydon commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes SDK-1962.

Summary

The hosted /generators/typescript, /generators/go, /generators/python and /generators/swift routes, and the PG_META_GENERATE_TYPES one-shot mode, now dispatch through @supabase/typegen, the language registry behind supabase gen types --lang. postgres-meta no longer imports a generator function by name: the routes are registered for every inProcess language the registry lists, and each language receives the options it declares. When a generator relocates under SDK-1641, postgres-meta follows with a @supabase/typegen bump and no route change.

request  ->  route (one per in-process language)
             |  query parameters -> registry option names, filtered to what the language declares
             |  resolveOptions() -> 400 on a bad value, before touching the database
             |  getGeneratorMetadata() -> introspect() re-exported by the registry
             v
         generateTypes(language, metadata, options)
             |  inline, or on the piscina worker when PG_META_FORMAT_IN_WORKER=true
             v
         language.generate(metadata, options, host)   host = { cwd, env }, no spawn, no format function

What changed

  • package.json: @supabase/typegen@0.2.2 replaces @supabase/postgrest-typegen. The pin is exact: a registry release is the one dependency change that alters generated output, so it lands as a deliberate bump with its snapshot update rather than through a range. The registry in turn pins postgrest-typegen 0.4.0 exactly, so the lockfile can only ever hold one version of it. oxfmt becomes a direct dependency, pinned to 0.66.0, the exact optional peer @supabase/typegen and postgrest-typegen declare: npm rejects any other version (npm ls reports it invalid and newer oxfmt releases hit an ERESOLVE through vite-plus), so .github/dependabot.yml ignores it and it moves together with a registry bump. TypeScript is formatted by postgrest-typegen's default formatter, which loads oxfmt as an optional peer, and npm does not install optional peers, which is why the direct dependency exists at all. engines.node is >=22.12.0, which the registry requires and .nvmrc and the Dockerfile already used.
  • src/server/routes/generators/index.ts replaces the four per-language route files. Query parameters map onto registry option names: detect_one_to_one_relationships=true is detect-one-to-one-relationships, postgrest_version is postgrest-version, access_control is swift-access-control, and PG_META_GENERATE_TYPES_DEFAULT_SCHEMA is default-schema. declaredOptions in src/lib/generators.ts keeps only what the language declares, so access_control on the Python route stays ignored as before.
  • Invalid option values return 400 naming the query parameter and carrying the registry's message, for example access_control=fileprivate. An empty value such as access_control= counts as absent, as before. Before, the value reached the Swift generator unchecked. declaredOptions validates and applies defaults itself, so both the routes and the one-shot mode reject a bad value before opening a database connection, and an empty PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL falls back to internal as before.
  • CLAUDE.md describes the registry dispatch, the two previously undocumented environment variables and the worker running every language.
  • src/server/format-pool.ts and format-worker.js run language.generate instead of generateTypescript, for every in-process language. With PG_META_FORMAT_IN_WORKER=true all four languages now generate on the worker and count against PG_META_FORMAT_MAX_QUEUE; before, only TypeScript did. The language name and option values are plain data and cross the thread boundary; the worker builds its own Host.
  • src/lib/generators.ts drops its sortGeneratorMetadata call. The registry sorts inside generate, and getGeneratorMetadata is not exported from the library entry point.
  • src/server/server.ts looks PG_META_GENERATE_TYPES up in the registry and writes the result with process.stdout.write. The registry output ends in the newline console.log used to add, so the one-shot output is byte-identical for Go, Python and Swift.
  • swift accepts all four access levels the generator supports (internal, public, private, package), matching what the route accepted before through the unchecked type cast.

Snapshot changes

The inline snapshots in test/server/typegen.ts changed for three reasons, and it is worth telling them apart when reviewing:

  1. The postgrest-typegen bump from 0.2.0 (what the lockfile held) to 0.3.1, through the registry pin. The generator fixes in 0.2.1 to 0.2.5 account for most of the diff: zero-argument functions type Args as Record<PropertyKey, never> instead of never, functions returning table or view rows get their Returns expanded, composite arguments on materialized views resolve, Python json columns are JsonValue, and composite attributes are Optional.
  2. Go, Python and Swift responses now end in a final newline. Their generators emit it since postgrest-typegen 0.3.1 (SDK-1961, fix(typegen): generators emit the final newline themselves, registry passes it through sdk#233); before, the routes returned the trimmed template verbatim. TypeScript responses are unchanged in this respect: the registry passes a generator's own newline through instead of adding one.
  3. The postgrest-typegen bump from 0.3.1 to 0.4.0 (feat(postgrest-typegen): declare computed fields next to Row and type named row parameters without them sdk#243, SDK-1986): every table and view gains ComputedFields, the names of the Row keys that are computed fields, or never; named row parameters of functions are typed Omit<Row, ComputedFields>; and a function named like a column of the relation it takes is no longer added to Row. Today this already lets rpc calls pass a row argument without its computed fields, which master's types demanded; once feat(postgrest): leave computed fields out of select('*') and resolve no-argument rpc overloads for postgrest-typegen output supabase-js#2730 lands, it will also let postgrest-js leave computed fields out of select('*') without inferring them from Functions.

Tests

  • test/server/generate-types-mode.ts runs server.ts in PG_META_GENERATE_TYPES mode as a child process: TypeScript output equals the route's body and ends in a single newline, and dart fails before connecting, which the test proves by pointing that run at an unreachable database. The child process clears the settings the route does not read, so a developer's shell cannot make the two outputs differ. The worker pool test covers every in-process language.
  • test/types.test.ts loses its pgTypeToTsType case, which imported @supabase/postgrest-typegen directly and only resolved through npm hoisting once the package stopped being declared; it moves to postgrest-typegen's own tests in test(postgrest-typegen): cover the interval to string mapping sdk#235 (SDK-1972). No postgrest-typegen import remains in this repository.
  • test/server/format-pool.ts compares the worker path against typescript.generate from the registry instead of generateTypescript.
  • test/server/typegen.ts adds: access_control=private and package produce Swift with that level, an unknown level returns 400 with Query parameter "access_control" is invalid: followed by the registry's message, an empty access_control= falls back to internal, and a route exists for every in-process registry language and for no other, so /generators/dart has none.
  • npm run check, prettier, and the full suite pass locally; the lockfile installs with npm clean-install without any peer-dependency flag.

The four per-language route files become one route registered for every
in-process language the registry lists, and PG_META_GENERATE_TYPES looks
the language up the same way. Query parameters map onto registry option
names and each language receives only the options it declares; an
invalid value returns 400 before the database is touched. The worker
pool runs language.generate for every language instead of
generateTypescript.

@supabase/typegen pins @supabase/postgrest-typegen, so postgres-meta
depends on the registry alone. oxfmt becomes a direct dependency because
postgrest-typegen 0.3.0 made it an optional peer and the TypeScript route
keeps formatting with the default formatter.
A registry release is the one dependency change that alters generated
output, so it lands as a deliberate bump rather than through a range.
… registry dispatch

declaredOptions resolves and validates the options it keeps, so both the
routes and the one-shot mode reject a bad value before opening a
database connection. An empty PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL
falls back to the registry default again. Dependabot leaves oxfmt alone,
since postgrest-typegen peers on that exact version.
…rest-typegen

The interval mapping is a generator concern and now lives in
postgrest-typegen's own tests (SDK-1972); nothing in this repository
imports @supabase/postgrest-typegen anymore.
spydon added a commit to supabase/sdk that referenced this pull request Sep 28, 2026
Closes SDK-1972 together with the matching removal in
supabase/postgres-meta#1155.

postgres-meta's `test/types.test.ts` asserted that `pgTypeToTsType` maps
`interval` to `string` through a direct import of
`@supabase/postgrest-typegen`. After postgres-meta#1155 that package is
no longer one of its declared dependencies (it depends on
`@supabase/typegen`, which pins postgrest-typegen), so the import only
resolved through npm hoisting. The case moves here, next to the
generator it tests, and postgres-meta drops the import.

No source change. `bun test`, `check-types` and `format-and-lint` pass.
The registry now passes a generator's final newline through instead of
appending one, so the TypeScript responses end in a single newline
again while Go, Python and Swift keep the one their generators emit
since postgrest-typegen 0.3.1.
@spydon
spydon marked this pull request as ready for review September 28, 2026 12:32
@spydon
spydon requested review from a team, avallete and soedirgo as code owners September 28, 2026 12:32
The host in typegen-host.js formats TypeScript with the same oxfmt
options postgrest-typegen's default formatter uses, so oxfmt is this
package's own dependency and version rather than the optional peer the
default formatter would load, and dependabot no longer has to skip it.
Both the inline path and the worker thread import the same host.
The registry now pins postgrest-typegen 0.4.0 (supabase/sdk#243). Its
TypeScript output declares `ComputedFields` next to every table's and
view's `Row`, types named row parameters of functions as
`Omit<Row, ComputedFields>`, and no longer adds a function named like one
of the relation's columns to `Row`. The inline snapshots in
`test/server/typegen.ts` follow.

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I dogfooded and reviewed this at bc030db. It works well. The one thing I'd like changed before merge is the oxfmt range and the text around it (inline comments below).

What I checked

  • npm ci, npm run check, npm run build, and the full suite (199/199) all pass.
  • The generated TS compiles under tsc --strict. A consumer on @supabase/supabase-js / postgrest-js 2.117.2 compiles too, using select('*'), joins, rpc, insert and update.
  • Omit<Row, ComputedFields> is a real improvement. rpc('function_returning_single_row', { todos: { id, details, 'user-id' } }) fails to compile against master's types, because they require every computed field. It compiles here. Passing a computed key is still accepted.
  • PG_META_FORMAT_IN_WORKER=true and inline generation give byte-identical output for typescript, go, python and swift.
  • In one-shot mode, PG_META_GENERATE_TYPES=dart and an invalid swift access level both fail before a DB connection is attempted.
  • npm pack ships typegen-host.js and format-worker.js.

Non-blocking

  • PR body, snapshot reason 3: "This is what lets postgrest-js leave computed fields out of select('*')". Published postgrest-js doesn't read ComputedFields yet (supabase/supabase-js#2730 is still open), so select('*') result types don't change with this PR. Maybe say "will let", and mention the rpc row-argument improvement instead.
  • apps/cli still pins @supabase/typegen 0.2.1. Once this deploys, supabase gen types --local and --linked output will differ by the ComputedFields / Omit lines until the CLI bumps to 0.2.2.
  • src/server/format-pool.ts:31 still says "Formatting generated types timed out", but it now covers go, python and swift too. Something like "Generating types timed out" would fit better.
  • An empty access_control= now returns 400. On master it produced Swift with no modifier, which means internal. That's probably fine, but it is a small behavior change for the hosted route.
  • Test gaps: nothing covers the one-shot mode in server.ts (including the TS output now ending in one newline instead of two), and the worker test is TS-only. The /generators/dart 404 test also passes on master, so it doesn't really guard the inProcess filter.

Comment thread package.json Outdated
Comment thread CLAUDE.md Outdated
Comment thread src/server/routes/generators/index.ts Outdated
@spydon

spydon commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review @avallete!
I'm waiting for supabase/cli#6919 to be merged and released, once that is done then supabase/supabase-js#2730 can move forward.
Fixing up the other things now.

spydon added 2 commits October 1, 2026 11:36
…meter in option errors

npm rejects any oxfmt version other than the exact optional peer
@supabase/typegen declares, so the dependency is pinned and dependabot
skips it; it moves with a registry bump. A rejected option now names
the query parameter the caller sent, an empty parameter counts as
absent as it did before, and the worker timeout message covers every
language. Tests cover the one-shot mode, every in-process language on
the worker, and the route set against the registry.
With oxfmt pinned to the registry's peer version anyway, an explicit
formatter only duplicated the default one's options. The host is the two
fields the contract requires, built in the pool and again on the worker
thread.

@avallete avallete left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @spydon, the follow-ups cover everything from my last review. I re-dogfooded at a0f3016 and it looks good, so approving. The inline notes are all non-blocking.

What I checked

  • npm ci works without flags, and npm ls oxfmt is clean with the exact pin. Check, prettier and build pass, and the full suite passes (203/203).
  • TypeScript, Go, Python and Swift output is byte-identical to bc030db, which still had the custom formatter. I compared inline generation, PG_META_FORMAT_IN_WORKER=true, the server started from an unrelated cwd, an npm pack install, and the Docker image. So the default formatter resolves oxfmt correctly everywhere.
  • An empty access_control= now returns 200 with internal. fileprivate returns 400 with the new Query parameter "access_control" is invalid: prefix. The python route still ignores access_control.
  • One-shot output matches the routes and ends in a single newline. dart and fileprivate exit 1 before connecting, even with the DB pointed at port 1.

PR body (non-blocking)

  • The --legacy-peer-deps note is stale: a fresh resolve at oxfmt 0.66.0 works without it.
  • The Tests section still describes the old 400 message and the dart-only 404 test. It doesn't list the new registry-driven route test or the empty access_control test.
  • In the diagram, no format reads as if TypeScript is no longer formatted. It's only that the host passes no format function.

Outside this PR: apps/cli still pins @supabase/typegen 0.2.1. Until it moves to 0.2.2, --local and --linked TypeScript output will differ by the ComputedFields / Omit lines.

Comment thread test/server/generate-types-mode.ts Outdated
Comment thread test/server/generate-types-mode.ts Outdated
Comment thread src/server/format-worker.js Outdated
Comment thread .github/dependabot.yml Outdated
@spydon

spydon commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @avallete, fixed all the nits too!

@spydon
spydon merged commit 9a2effd into master Oct 6, 2026
11 checks passed
@spydon
spydon deleted the lukasklingsbo/sdk-1962-typegen-registry branch October 6, 2026 11:59
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.

2 participants