sdk / postgrest-typegen
CLAUDE.md52 starsChanged 8 days agoMITView on GitHub
Type generation for PostgREST from a PostgreSQL schema. Introspects a database into a normalized GeneratorMetadata shape, then renders language types (TypeScript, Go, Python) from it. This is the engine extracted from postgres-meta (the one behind supabase gen types), repackaged as a small, driver-agnostic library. IMPORTANT — scope, read before adding a language here. Introspection and the GeneratorMetadata/JSON contract are this package's permanent job. The three bundled generators (TypeScript/Go/Python) are a deliberate transition, not the pattern for new languages: they move…
In this repository3 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- packages/typegen/CLAUDE.mdCLAUDE.md
- capability-matrixSkill
The file
We split this CLAUDE.md into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 99
Every generator output change is a behavior change for every consumer.
- Distinctive: line 104
Don't reformat or "clean up" template strings casually.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md -- @supabase/postgrest-typegen
What This Package Does
Type generation for PostgREST from a PostgreSQL schema. Introspects a database
into a normalized GeneratorMetadata shape, then renders language types
(TypeScript, Go, Python) from it. This is the engine extracted from
postgres-meta (the one behind supabase gen types), repackaged as a small,
driver-agnostic library.
IMPORTANT — scope, read before adding a language here. Introspection and
the GeneratorMetadata/JSON contract are this package's permanent job. The
three bundled generators (TypeScript/Go/Python) are a deliberate
transition, not the pattern for new languages: they move out one language
at a time, TypeScript first into supabase-js, then Go and Python.
A new language's generator does NOT belong in this package — it lives in
that language's own SDK repo, consuming introspect()'s JSON output via
serializeGeneratorMetadata/generatorMetadataJsonSchema (see the Dart
supabase_typegen package in supabase/supabase-flutter and the Swift
supabase-typegen executable in supabase/supabase-swift for the pattern).
The compatibility rule for the contract, how GENERATOR_METADATA_VERSION
relates to the package version and who reviews contract changes are in the
README under "Contract compatibility".
Architecture
Hard split between introspection and generation:
src/introspection/--introspect(db, opts) => GeneratorMetadata. Takes a structuralQueryable(pg.Pool/pg.Clientsatisfy it; postgres-meta injects its forked-pg pool). Runs the SQL builders insrc/introspection/sql/. View columnis_nullableis the one value computed outside SQL:view-nullability.tsparses each view'spg_rewrite.ev_actionwithnode-tree.tsand walks its query tree (see "View column nullability" in the README). It reads only the join tree and range table, nevervarnullingrels, so one code path serves PostgreSQL 15 and later.src/generation/--generateTypescript/generateGo/generatePython. Pure functions:GeneratorMetadatain, the complete source file (ending in a single newline) out. No database access.src/rpc.ts--resolveRpcFunctions(metadata) => RpcFunction[], the PostgREST view of the rawfunctionscollection: which functions/rpcexposes, overload grouping and order, resolved parameter and return types,setof_optionsfor embedding,computed_field_ofand the per-overloaderrorfor unresolvable overloads. Pure and canonically ordered.introspect()attaches its result asmetadata.rpcFunctions(optional in the contract); the TypeScript generator reads that collection and falls back to running the pass when a document lacks it. All PostgREST function semantics live here, so an out-of-process generator never reimplements them: a rule change is a change to this file plus the parity goldens, never to a generator.src/types.ts--GeneratorMetadata+Postgres*types. This is the public, pluggable contract: any source that can produceGeneratorMetadatacan feed the generators. ArkType is the single source of truth here: each shape is an ArkType schema and the exported type istypeof schema.infer. The arrays onGeneratorMetadatause.omit("columns")to mirrorOmit<…, "columns">. A compile-time equivalence test (test/validation/validate.test.ts) pins the inferred types to the frozen interface contract so they can't silently drift.parseGeneratorMetadata(data)is an opt-in runtime validator (throws on mismatch) — it is intentionally NOT called insideintrospect(); integrators with a custom producer wrap the result themselves.GENERATOR_METADATA_VERSIONandgeneratorMetadataSchema.versionlet an out-of-process consumer detect a shape change;generatorMetadataJsonSchemaandserializeGeneratorMetadataown the JSON boundary for generators living outside this process.
Subpath Exports
.-- everything./introspection--introspect,Queryable./generation-- thegenerateXfunctions
The bun condition serves TypeScript source directly; import/require serve
compiled JS from dist/.
Commands
bun run build # tsc --project tsconfig.build.json (emits dist/)
bun run check-types # tsc --noEmit
bun run check-types:consumer # tsc --project tsconfig.consumer.json
bun run test # bun:test (Docker required for integration/parity tests)
bun run format-and-lint # oxfmt + oxlint check
bun run knip # unused-code/deps checkcheck-types:consumer compiles src/ with @tsconfig/bun plus
customConditions: ["bun"], which is how the Supabase CLI type-checks this
package: its bun exports condition hands the CLI src/*.ts rather than
dist/*.d.ts, so src/ must also pass stricter flags such as
noUncheckedIndexedAccess that the package's own base config does not set.
Consumers
postgres-meta's hosted /generators/* routes and the Supabase CLI's
gen types consume this package through @supabase/typegen
(packages/typegen), which pins an exact version. This package is the single
source of truth for generator output: there is no upstream to stay
byte-identical with, and no cross-check against postgres-meta, whose output is
whatever version its lockfile pins. Consequences:
- Every generator output change is a behavior change for every consumer.
Regenerate the inline snapshots (
bun test --update-snapshots) and the goldens undertest/parity/expected/in the same PR, review that diff deliberately, and describe the output change in the PR so it reaches the release notes consumers read before bumping. - Don't reformat or "clean up" template strings casually; a formatter-only change still churns every consumer's generated files.
SQL literal quoting: the introspection queries use literal() from
src/introspection/sql/pg-format.ts, an inlined port of pg-format@1.0.4
(the package has no pg-format runtime dependency: pg-format is CJS-only,
which is painful for ESM and bundled consumers such as the CLI). It keeps pg-format's
quirks — numbers are quoted (literal(10) is '10') — so it stays a faithful
port even though the introspection only quotes schema names today. Don't align
it with packages/pg-meta's variant in supabase/supabase, which emits
unquoted numbers.
int8 columns: stock pg returns them as strings while postgres-meta installs
a global int8 type parser. src/introspection/normalize.ts coerces known
numeric id fields after each query so output is identical under any driver.
Deterministic ordering: the Go/Python generators emit objects in
GeneratorMetadata order (only TypeScript sorts internally), so output is
sensitive to however the producer ordered its collections. Do NOT rely on
introspection query order (heap/aggregate-plan order is environment- and
data-dependent — the parity test can pass locally while postgres-meta's
order-sensitive snapshots break in CI). Stability is enforced by a single
generator-agnostic pass, sortGeneratorMetadata (src/sort.ts): callers apply
it to the metadata after introspection and before any generate* call. The
generators document that they expect pre-sorted input; introspection queries
carry no ORDER BY. Sort keys are semantic (schema + name + signature),
never oid — equivalent databases assign different oids, so an oid sort would
still churn output across environments.
TypeScript is the source of truth for ordering. sortGeneratorMetadata
applies the sorts generateTypescript defines, and all three generators
consume that single pass. The only sorts that remain inside generators are ones the
single-collection pass cannot express: cross-collection merge sorts
(TypeScript merges tables + foreign tables, and views + materialized
views, into one per-schema group then sort by name). RPC overload order is
owned by resolveRpcFunctions (argument list, then return type) and kept by
sortGeneratorMetadata. Don't add per-collection sorting in a
generator — extend sortGeneratorMetadata instead.
Test Patterns
- Generation unit tests: pure, no Docker, fixture-builder + per-language inline
snapshots (
src/andtest/generation/). - Introspection/parity tests:
bun:test+ testcontainers PostgreSQL.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

