Repository navigation
feat: dispatch the hosted /generators/* routes through @supabase/typegen - #1155
Conversation
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.
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.
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
left a comment
There was a problem hiding this comment.
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-js2.117.2 compiles too, usingselect('*'), joins,rpc,insertandupdate. 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=trueand inline generation give byte-identical output for typescript, go, python and swift.- In one-shot mode,
PG_META_GENERATE_TYPES=dartand an invalid swift access level both fail before a DB connection is attempted. npm packshipstypegen-host.jsandformat-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 readComputedFieldsyet (supabase/supabase-js#2730 is still open), soselect('*')result types don't change with this PR. Maybe say "will let", and mention the rpc row-argument improvement instead. apps/clistill pins@supabase/typegen0.2.1. Once this deploys,supabase gen types --localand--linkedoutput will differ by theComputedFields/Omitlines until the CLI bumps to 0.2.2.src/server/format-pool.ts:31still 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 meansinternal. 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/dart404 test also passes on master, so it doesn't really guard theinProcessfilter.
|
Thanks for the thorough review @avallete! |
…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
left a comment
There was a problem hiding this comment.
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 ciworks without flags, andnpm ls oxfmtis 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, annpm packinstall, and the Docker image. So the default formatter resolvesoxfmtcorrectly everywhere. - An empty
access_control=now returns 200 withinternal.fileprivatereturns 400 with the newQuery parameter "access_control" is invalid:prefix. The python route still ignoresaccess_control. - One-shot output matches the routes and ends in a single newline.
dartandfileprivateexit 1 before connecting, even with the DB pointed at port 1.
PR body (non-blocking)
- The
--legacy-peer-depsnote 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_controltest. - In the diagram,
no formatreads as if TypeScript is no longer formatted. It's only that the host passes noformatfunction.
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.
|
Thanks for the review @avallete, fixed all the nits too! |
Closes SDK-1962.
Summary
The hosted
/generators/typescript,/generators/go,/generators/pythonand/generators/swiftroutes, and thePG_META_GENERATE_TYPESone-shot mode, now dispatch through@supabase/typegen, the language registry behindsupabase gen types --lang. postgres-meta no longer imports a generator function by name: the routes are registered for everyinProcesslanguage the registry lists, and each language receives the options it declares. When a generator relocates under SDK-1641, postgres-meta follows with a@supabase/typegenbump and no route change.What changed
package.json:@supabase/typegen@0.2.2replaces@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.oxfmtbecomes a direct dependency, pinned to0.66.0, the exact optional peer@supabase/typegenand postgrest-typegen declare: npm rejects any other version (npm lsreports it invalid and newer oxfmt releases hit anERESOLVEthroughvite-plus), so.github/dependabot.ymlignores 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.nodeis>=22.12.0, which the registry requires and.nvmrcand the Dockerfile already used.src/server/routes/generators/index.tsreplaces the four per-language route files. Query parameters map onto registry option names:detect_one_to_one_relationships=trueisdetect-one-to-one-relationships,postgrest_versionispostgrest-version,access_controlisswift-access-control, andPG_META_GENERATE_TYPES_DEFAULT_SCHEMAisdefault-schema.declaredOptionsinsrc/lib/generators.tskeeps only what the language declares, soaccess_controlon the Python route stays ignored as before.access_control=fileprivate. An empty value such asaccess_control=counts as absent, as before. Before, the value reached the Swift generator unchecked.declaredOptionsvalidates and applies defaults itself, so both the routes and the one-shot mode reject a bad value before opening a database connection, and an emptyPG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROLfalls back tointernalas before.CLAUDE.mddescribes the registry dispatch, the two previously undocumented environment variables and the worker running every language.src/server/format-pool.tsandformat-worker.jsrunlanguage.generateinstead ofgenerateTypescript, for every in-process language. WithPG_META_FORMAT_IN_WORKER=trueall four languages now generate on the worker and count againstPG_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 ownHost.src/lib/generators.tsdrops itssortGeneratorMetadatacall. The registry sorts insidegenerate, andgetGeneratorMetadatais not exported from the library entry point.src/server/server.tslooksPG_META_GENERATE_TYPESup in the registry and writes the result withprocess.stdout.write. The registry output ends in the newlineconsole.logused to add, so the one-shot output is byte-identical for Go, Python and Swift.swiftaccepts 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.tschanged for three reasons, and it is worth telling them apart when reviewing:ArgsasRecord<PropertyKey, never>instead ofnever, functions returning table or view rows get theirReturnsexpanded, composite arguments on materialized views resolve, Python json columns areJsonValue, and composite attributes areOptional.ComputedFields, the names of theRowkeys that are computed fields, ornever; named row parameters of functions are typedOmit<Row, ComputedFields>; and a function named like a column of the relation it takes is no longer added toRow. Today this already letsrpccalls 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 ofselect('*')without inferring them fromFunctions.Tests
test/server/generate-types-mode.tsrunsserver.tsinPG_META_GENERATE_TYPESmode as a child process: TypeScript output equals the route's body and ends in a single newline, anddartfails 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.tsloses itspgTypeToTsTypecase, which imported@supabase/postgrest-typegendirectly 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). Nopostgrest-typegenimport remains in this repository.test/server/format-pool.tscompares the worker path againsttypescript.generatefrom the registry instead ofgenerateTypescript.test/server/typegen.tsadds:access_control=privateandpackageproduce Swift with that level, an unknown level returns 400 withQuery parameter "access_control" is invalid:followed by the registry's message, an emptyaccess_control=falls back tointernal, and a route exists for every in-process registry language and for no other, so/generators/darthas none.npm run check, prettier, and the full suite pass locally; the lockfile installs withnpm clean-installwithout any peer-dependency flag.