diff --git a/.github/workflows/postgrest-typegen-validate.yml b/.github/workflows/postgrest-typegen-validate.yml index a0c525ff..758f3661 100644 --- a/.github/workflows/postgrest-typegen-validate.yml +++ b/.github/workflows/postgrest-typegen-validate.yml @@ -10,9 +10,6 @@ on: paths: - "packages/postgrest-typegen/**" - ".github/workflows/postgrest-typegen-validate.yml" - schedule: - - cron: "0 6 * * *" # nightly drift check against real postgres-meta - workflow_dispatch: {} defaults: run: @@ -21,10 +18,9 @@ defaults: jobs: test: name: Types, lint, build, test - if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Install bun run: curl -fsSL https://bun.sh/install | bash - name: Add bun to PATH @@ -35,92 +31,3 @@ jobs: - run: bun run knip - run: bun run build - run: bun run test - - parity-check: - name: Parity vs. real postgres-meta - # Nightly/manual only, never on push or pull_request: postgres-meta can - # drift for reasons unrelated to any change here, and that shouldn't - # block or flake unrelated PRs (same pattern as capability-matrix's - # "Tier 2" reference checks). - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - services: - postgres: - image: postgres:15-alpine - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 5s - --health-timeout 5s - --health-retries 10 - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres - steps: - - name: Checkout supabase/sdk - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Checkout supabase/postgres-meta - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - repository: supabase/postgres-meta - path: postgres-meta - - name: Install bun - run: curl -fsSL https://bun.sh/install | bash - - name: Add bun to PATH - run: echo "$HOME/.bun/bin" >> "$GITHUB_PATH" - - run: bun install --frozen-lockfile - - name: Load shared fixture schema - run: bun scripts/load-fixtures.ts - - name: Generate postgrest-typegen output - run: | - mkdir -p /tmp/ours - for lang in typescript go python swift; do - bun scripts/print-generated.ts "$lang" > "/tmp/ours/$lang.txt" - done - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "22" - - name: Install postgres-meta dependencies - working-directory: postgres-meta - run: npm ci - - name: Generate postgres-meta output - working-directory: postgres-meta - env: - PG_META_DB_URL: postgresql://postgres:postgres@localhost:5432/postgres - run: | - mkdir -p /tmp/theirs - for lang in typescript go python swift; do - PG_META_GENERATE_TYPES="$lang" node --loader ts-node/esm src/server/server.ts > "/tmp/theirs/$lang.txt" - done - - name: Canonicalize postgres-meta's TypeScript output - # postgres-meta formats with prettier; this package defaults to - # oxfmt, which has different line-wrap heuristics (e.g. printWidth is - # matched at 80, but wrapping decisions for nested object types can - # still differ). Reformatting postgres-meta's output through our own - # formatter first means the diff below reflects content, not which - # formatter produced it. - run: bun scripts/canonicalize-typescript.ts /tmp/theirs/typescript.txt > /tmp/theirs/typescript.canonical.txt - - name: Compare outputs - # Order-independent: sortGeneratorMetadata canonicalizes collection - # order (semantic schema+name, not postgres-meta's environment- and - # data-dependent heap order) across all four generators, which is a - # deliberate improvement over postgres-meta's real behavior, not a - # divergence to flag. Comparing declaration BLOCKS (paragraphs - # separated by a blank line) as a sorted set, rather than raw bytes, - # verifies the two agree on content without being sensitive to that - # reordering. - run: | - mv /tmp/theirs/typescript.canonical.txt /tmp/theirs/typescript.txt - normalize() { awk 'BEGIN{RS="";FS="\n"} {gsub(/\n/, "\\n"); print}' "$1" | sort; } - failed=0 - for lang in typescript go python swift; do - if ! diff -u <(normalize "/tmp/theirs/$lang.txt") <(normalize "/tmp/ours/$lang.txt"); then - echo "::error::$lang output diverges from real postgres-meta" - failed=1 - fi - done - exit "$failed" diff --git a/CLAUDE.md b/CLAUDE.md index 594a3a65..b609b702 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ This is a lightweight monorepo of shared tooling for the Supabase client SDKs. P - `packages/capability-matrix/` — the **canonical feature registry** for all Supabase client SDKs — not the SDKs themselves. It defines what features exist (name, description, grouping) across 7 client SDKs (JavaScript, Flutter, Python, Swift, C#, Go, Kotlin). SDKs declare compliance separately in their own repos via `sdk-compliance.yaml` files. The output is a static capability matrix website at https://supabase.github.io/sdk/ showing which features each SDK implements. - `packages/dart-symbol-extractor/` — Dart public API symbol extractor used by the Dart compliance workflow. - `packages/go-symbol-extractor/` — Go public API symbol extractor used by the Go compliance workflow. -- `packages/postgrest-typegen/` — introspects a PostgreSQL schema into the `GeneratorMetadata` contract and generates PostgREST types for TypeScript, Go, Python, and Swift. See its own `CLAUDE.md` for architecture and the byte-parity constraint with postgres-meta. +- `packages/postgrest-typegen/` — introspects a PostgreSQL schema into the `GeneratorMetadata` contract and generates PostgREST types for TypeScript, Go, Python, and Swift. See its own `CLAUDE.md` for architecture and how postgres-meta and the CLI consume it. ## Commands diff --git a/packages/postgrest-typegen/CLAUDE.md b/packages/postgrest-typegen/CLAUDE.md index 5aa422e6..99bc78de 100644 --- a/packages/postgrest-typegen/CLAUDE.md +++ b/packages/postgrest-typegen/CLAUDE.md @@ -65,21 +65,23 @@ bun run format-and-lint # oxfmt + oxlint check bun run knip # unused-code/deps check ``` -## Byte-Parity Constraint - -This package must produce **byte-identical** output to postgres-meta's -templates until parity is validated and released. Two consequences: - -- `prettier` is pinned **exact** to the version postgres-meta's own lockfile - currently resolves (not a caret range, and not just "latest"), since a - version mismatch reformats generator output and silently breaks parity with - the real upstream CLI, not just with this package's own snapshots. Check - postgres-meta's resolved version before bumping. Bumping means regenerating - every inline snapshot (`bun test --update-snapshots`) and the parity - fixtures under `test/parity/expected/`, and reviewing the diff. -- Don't "improve" template strings or SQL for existing generators without - regenerating snapshots/fixtures first. Byte parity first; behavior-changing - cleanups (e.g. oxfmt instead of prettier) come later. +## Relationship to postgres-meta + +postgres-meta deleted its own templates and consumes this package for all four +languages (supabase/postgres-meta#1084, shipped in postgres-meta v0.99.0 on +2026-08-31), and so does the CLI (supabase/cli#6404). This package is the +single source of truth for generator output: there is no upstream to stay +byte-identical with, and there is no cross-check against postgres-meta (its +output is whatever version of this package its lockfile pins, so such a check +only ever measured release lag). Consequences: + +- Every generator output change is a behavior change for every consumer. + Regenerate the inline snapshots (`bun test --update-snapshots`) and the + goldens under `test/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` diff --git a/packages/postgrest-typegen/README.md b/packages/postgrest-typegen/README.md index 61808cb8..8fc5ee04 100644 --- a/packages/postgrest-typegen/README.md +++ b/packages/postgrest-typegen/README.md @@ -17,19 +17,19 @@ JSON output, not inside `postgrest-typegen`. The TypeScript, Go, Python, and Swift generators bundled here are a **deliberate transition**, not the target architecture for new languages. -They were ported byte-parity from postgres-meta's own templates so -`supabase gen types` keeps working unchanged while postgres-meta's copies get -deprecated in favor of this package. It's decided that all four eventually -move out to their own SDK repos too (tracked in SDK-1641), not yet scheduled -— sequenced after postgres-meta's cutover to this package settles. +They were ported byte-for-byte from postgres-meta's own templates so +`supabase gen types` kept working unchanged; postgres-meta has since deleted +its copies and consumes this package instead. It's decided that all four +eventually move out to their own SDK repos too (tracked in SDK-1641), not yet +scheduled — sequenced after postgres-meta's cutover to this package settles. > [!IMPORTANT] > **We are no longer accepting contributions that fix the bundled generators.** > Because each of the four is moving out to its own language's SDK repository, > a fix landed here would have to be made a second time in the destination -> repository, and it also breaks byte parity with postgres-meta until a release -> propagates. Please open an issue describing the bug instead, so it can be -> tracked and carried across the move. +> repository, and postgres-meta and the CLI only pick it up once they bump +> their pinned version of this package. Please open an issue describing the +> bug instead, so it can be tracked and carried across the move. ## Design diff --git a/packages/postgrest-typegen/knip.json b/packages/postgrest-typegen/knip.json index 120b6d19..4d23f829 100644 --- a/packages/postgrest-typegen/knip.json +++ b/packages/postgrest-typegen/knip.json @@ -1,11 +1,5 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", - "entry": [ - "test/global-setup.ts", - "test/**/*.test.ts", - "scripts/load-fixtures.ts", - "scripts/print-generated.ts", - "scripts/canonicalize-typescript.ts" - ], + "entry": ["test/global-setup.ts", "test/**/*.test.ts"], "ignoreBinaries": ["oxfmt", "oxlint"] } diff --git a/packages/postgrest-typegen/scripts/canonicalize-typescript.ts b/packages/postgrest-typegen/scripts/canonicalize-typescript.ts deleted file mode 100644 index b9609322..00000000 --- a/packages/postgrest-typegen/scripts/canonicalize-typescript.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Reformats a TypeScript file through this package's default formatter - * (oxfmt, `semi: false`, `printWidth: 80`) and prints the result to stdout. - * - * Used by the "parity vs. real postgres-meta" CI job to canonicalize - * postgres-meta's prettier-formatted output before diffing it against ours, - * so a formatter-only difference (e.g. line-wrap heuristics) doesn't read as - * a content divergence. Content itself still fails the diff either way. - * - * Usage: bun scripts/canonicalize-typescript.ts - */ -import { readFileSync } from "node:fs"; -import { format } from "oxfmt"; - -const filePath = process.argv[2]; -if (!filePath) { - throw new Error("A file path is required"); -} - -const { code, errors } = await format( - "output.ts", - readFileSync(filePath, "utf8"), - { - semi: false, - printWidth: 80, - }, -); -if (errors.length > 0) { - throw new Error( - `oxfmt failed to format ${filePath}: ${errors.map((error) => error.message).join("; ")}`, - ); -} -process.stdout.write(code); diff --git a/packages/postgrest-typegen/scripts/load-fixtures.ts b/packages/postgrest-typegen/scripts/load-fixtures.ts deleted file mode 100644 index df2ed6f5..00000000 --- a/packages/postgrest-typegen/scripts/load-fixtures.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Loads the shared fixture schema (the same one `test/introspection/` and - * `test/parity/` use) into DATABASE_URL. Used by the "parity vs. real - * postgres-meta" CI job so both sides introspect the identical schema - * without depending on a system `psql` client being present on the runner. - */ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { Pool } from "pg"; - -const databaseUrl = process.env.DATABASE_URL; -if (!databaseUrl) { - throw new Error("DATABASE_URL is required"); -} - -const FIXTURE_DIR = join( - import.meta.dir, - "..", - "test", - "introspection", - "fixtures", -); - -const pool = new Pool({ connectionString: databaseUrl }); -try { - await pool.query(readFileSync(join(FIXTURE_DIR, "00-init.sql"), "utf8")); - await pool.query(readFileSync(join(FIXTURE_DIR, "01-memes.sql"), "utf8")); -} finally { - await pool.end(); -} diff --git a/packages/postgrest-typegen/scripts/print-generated.ts b/packages/postgrest-typegen/scripts/print-generated.ts deleted file mode 100644 index d075325e..00000000 --- a/packages/postgrest-typegen/scripts/print-generated.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Prints this package's generated output for one language against a live - * database, for the "parity vs. real postgres-meta" CI job to diff against - * postgres-meta's own `npm run gen:types:` output for the same schema. - * Not used by the test suite, which compares against committed goldens - * instead — this is the live-vs-live check the goldens can't provide, since - * they're maintained by regenerating from this package itself. - * - * Mirrors postgres-meta's `server.ts` CLI path: no generator options - * overridden, matching its defaults when no `PG_META_GENERATE_TYPES_*` env - * vars are set, and a single `console.log` so the trailing newline matches. - * - * Usage: bun scripts/print-generated.ts - */ -import { Pool } from "pg"; - -import { - generateGo, - generatePython, - generateSwift, - generateTypescript, - introspect, - sortGeneratorMetadata, -} from "../src/index.ts"; - -const language = process.argv[2]; -const databaseUrl = process.env.DATABASE_URL; -if (!databaseUrl) { - throw new Error("DATABASE_URL is required"); -} - -const pool = new Pool({ connectionString: databaseUrl }); -try { - const metadata = sortGeneratorMetadata(await introspect(pool)); - switch (language) { - case "typescript": - console.log(await generateTypescript(metadata)); - break; - case "go": - console.log(generateGo(metadata)); - break; - case "python": - console.log(generatePython(metadata)); - break; - case "swift": - console.log(generateSwift(metadata)); - break; - default: - throw new Error(`Unsupported language: ${language}`); - } -} finally { - await pool.end(); -} diff --git a/packages/postgrest-typegen/test/parity/parity.test.ts b/packages/postgrest-typegen/test/parity/parity.test.ts index 088f59d4..00e1e0a0 100644 --- a/packages/postgrest-typegen/test/parity/parity.test.ts +++ b/packages/postgrest-typegen/test/parity/parity.test.ts @@ -20,30 +20,22 @@ import { introspect } from "../../src/introspection/index.ts"; import type { GeneratorMetadata } from "../../src/types.ts"; /** - * End-to-end parity gate: introspect the shared postgres-meta fixture DB, run - * all four generators with their default options, and assert the output matches - * the committed golden files in `expected/`. + * End-to-end golden gate: introspect the shared fixture DB, run all four + * generators with their default options, and assert the output matches the + * committed golden files in `expected/`. * * The metadata is passed through `sortGeneratorMetadata` first (as every * consumer must), so the golden files reflect the canonical, deterministic - * ordering rather than the database's heap order. The goldens were originally - * captured from a real postgres-meta CLI run against this fixture DB; only the - * ordering of order-sensitive collections (Go/Python/Swift emit tables/views - * in metadata order) is canonicalized — and postgres-meta applies the same - * sort pass, so the two stay in lockstep. postgres-meta's CLI prints with - * `console.log`, which appends exactly one trailing newline to the - * generator's return value — hence the `+ "\n"` below. + * ordering rather than the database's heap order. Consumers print the + * generator's return value with `console.log`, which appends exactly one + * trailing newline, hence the `+ "\n"` below. * - * CAUTION — this only proves parity at the moment a golden was captured. The - * established workflow for an *intentional* generator change is to - * regenerate the goldens from this package itself and review the diff, which - * means a generator change and its golden update land in the same PR: the - * gate then only proves internal self-consistency (generator output matches - * its own golden), not agreement with postgres-meta's actual current output. - * It cannot catch a regression introduced by that same change. Re-verify - * against a real postgres-meta run periodically, and always whenever - * postgres-meta's own templates change (e.g. the upstream fixes ported into - * this package) rather than trusting a self-regenerated golden alone. + * The goldens were originally captured from postgres-meta's own templates + * before it cut over to this package; since then they are regenerated from + * this package whenever a generator changes on purpose. A generator change + * and its golden update land in the same PR, so this gate proves output does + * not change *unintentionally*, and the reviewed golden diff is where an + * intentional change gets scrutinized. */ const FIXTURE_DIR = join(import.meta.dir, "..", "introspection", "fixtures"); const EXPECTED_DIR = join(import.meta.dir, "expected");