supaschema 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/.agents/skills/supaschema/SKILL.md +61 -0
  2. package/.claude/hooks/block-generated-migration-edits.mjs +32 -0
  3. package/.claude/rules/supaschema.md +22 -0
  4. package/.claude/settings.json +16 -0
  5. package/.claude/skills/supaschema/SKILL.md +61 -0
  6. package/.codex/hooks/supaschema-tool-gate.mjs +73 -0
  7. package/.codex/hooks.json +16 -0
  8. package/.codex/rules/supaschema.rules +22 -0
  9. package/AGENTS.md +40 -0
  10. package/LICENSE +661 -0
  11. package/LICENSE-COMMERCIAL.md +35 -0
  12. package/README.md +249 -0
  13. package/benchmarks/README.md +104 -0
  14. package/benchmarks/compare.js +489 -0
  15. package/benchmarks/fixtures/additive/from.sql +8 -0
  16. package/benchmarks/fixtures/additive/manifest.json +1 -0
  17. package/benchmarks/fixtures/additive/to.sql +9 -0
  18. package/benchmarks/fixtures/functions-policies/from.sql +24 -0
  19. package/benchmarks/fixtures/functions-policies/manifest.json +5 -0
  20. package/benchmarks/fixtures/functions-policies/to.sql +24 -0
  21. package/benchmarks/plot-lib.js +234 -0
  22. package/benchmarks/plot-svg.js +339 -0
  23. package/benchmarks/plot.js +154 -0
  24. package/benchmarks/tools/bench-all.sh +49 -0
  25. package/benchmarks/tools/build-project-fixture.mjs +245 -0
  26. package/benchmarks/tools/compare-db.mjs +101 -0
  27. package/benchmarks/tools/compare-fixtures.mjs +84 -0
  28. package/benchmarks/tools/compare-report.mjs +90 -0
  29. package/benchmarks/tools/compare-supabase.mjs +67 -0
  30. package/benchmarks/tools/registry.js +266 -0
  31. package/benchmarks/tools/run-workflow.mjs +77 -0
  32. package/bin/postinstall.mjs +26 -0
  33. package/bin/supaschema +2 -0
  34. package/config-schema.json +208 -0
  35. package/corpus/supabase-style/corpus.json +6 -0
  36. package/corpus/supabase-style/migrations/20260101000000_init.sql +28 -0
  37. package/corpus/supabase-style/migrations/20260102000000_noise.sql +13 -0
  38. package/corpus/supabase-style/migrations/20260103000000_churn.sql +4 -0
  39. package/corpus/supabase-style/migrations/20260104000000_triggers.sql +17 -0
  40. package/corpus/supabase-style/roles.sql +13 -0
  41. package/corpus/supabase-style/tree/functions.sql +26 -0
  42. package/corpus/supabase-style/tree/policies.sql +4 -0
  43. package/corpus/supabase-style/tree/schema.sql +4 -0
  44. package/corpus/supabase-style/tree/tables.sql +20 -0
  45. package/corpus/supabase-style/tree/triggers.sql +3 -0
  46. package/corpus/supabase-style/tree/types.sql +2 -0
  47. package/corpus/supabase-style/tree/views.sql +6 -0
  48. package/dist/audit.d.ts +20 -0
  49. package/dist/audit.d.ts.map +1 -0
  50. package/dist/audit.js +68 -0
  51. package/dist/benchmark-db.d.ts +5 -0
  52. package/dist/benchmark-db.d.ts.map +1 -0
  53. package/dist/benchmark-db.js +71 -0
  54. package/dist/benchmark-fixtures.d.ts +10 -0
  55. package/dist/benchmark-fixtures.d.ts.map +1 -0
  56. package/dist/benchmark-fixtures.js +201 -0
  57. package/dist/benchmark.d.ts +2 -0
  58. package/dist/benchmark.d.ts.map +1 -0
  59. package/dist/benchmark.js +308 -0
  60. package/dist/catalog-comments.d.ts +9 -0
  61. package/dist/catalog-comments.d.ts.map +1 -0
  62. package/dist/catalog-comments.js +194 -0
  63. package/dist/catalog-extras.d.ts +12 -0
  64. package/dist/catalog-extras.d.ts.map +1 -0
  65. package/dist/catalog-extras.js +408 -0
  66. package/dist/catalog-foreign.d.ts +15 -0
  67. package/dist/catalog-foreign.d.ts.map +1 -0
  68. package/dist/catalog-foreign.js +114 -0
  69. package/dist/catalog-tables.d.ts +9 -0
  70. package/dist/catalog-tables.d.ts.map +1 -0
  71. package/dist/catalog-tables.js +114 -0
  72. package/dist/catalog.d.ts +8 -0
  73. package/dist/catalog.d.ts.map +1 -0
  74. package/dist/catalog.js +351 -0
  75. package/dist/check-hazards.d.ts +7 -0
  76. package/dist/check-hazards.d.ts.map +1 -0
  77. package/dist/check-hazards.js +83 -0
  78. package/dist/check-reporters.d.ts +8 -0
  79. package/dist/check-reporters.d.ts.map +1 -0
  80. package/dist/check-reporters.js +76 -0
  81. package/dist/check.d.ts +3 -0
  82. package/dist/check.d.ts.map +1 -0
  83. package/dist/check.js +229 -0
  84. package/dist/cli-defaults.d.ts +24 -0
  85. package/dist/cli-defaults.d.ts.map +1 -0
  86. package/dist/cli-defaults.js +65 -0
  87. package/dist/cli-diff.d.ts +13 -0
  88. package/dist/cli-diff.d.ts.map +1 -0
  89. package/dist/cli-diff.js +348 -0
  90. package/dist/cli-reports.d.ts +9 -0
  91. package/dist/cli-reports.d.ts.map +1 -0
  92. package/dist/cli-reports.js +90 -0
  93. package/dist/cli-tools.d.ts +17 -0
  94. package/dist/cli-tools.d.ts.map +1 -0
  95. package/dist/cli-tools.js +136 -0
  96. package/dist/cli.d.ts +2 -0
  97. package/dist/cli.d.ts.map +1 -0
  98. package/dist/cli.js +239 -0
  99. package/dist/config-schema-gen.d.ts +2 -0
  100. package/dist/config-schema-gen.d.ts.map +1 -0
  101. package/dist/config-schema-gen.js +11 -0
  102. package/dist/config.d.ts +58 -0
  103. package/dist/config.d.ts.map +1 -0
  104. package/dist/config.js +132 -0
  105. package/dist/core.d.ts +115 -0
  106. package/dist/core.d.ts.map +1 -0
  107. package/dist/core.js +1 -0
  108. package/dist/corpus.d.ts +26 -0
  109. package/dist/corpus.d.ts.map +1 -0
  110. package/dist/corpus.js +112 -0
  111. package/dist/database-url.d.ts +8 -0
  112. package/dist/database-url.d.ts.map +1 -0
  113. package/dist/database-url.js +74 -0
  114. package/dist/db-admin.d.ts +23 -0
  115. package/dist/db-admin.d.ts.map +1 -0
  116. package/dist/db-admin.js +147 -0
  117. package/dist/diagnostics.d.ts +16 -0
  118. package/dist/diagnostics.d.ts.map +1 -0
  119. package/dist/diagnostics.js +155 -0
  120. package/dist/diff-score.d.ts +12 -0
  121. package/dist/diff-score.d.ts.map +1 -0
  122. package/dist/diff-score.js +339 -0
  123. package/dist/doctor.d.ts +17 -0
  124. package/dist/doctor.d.ts.map +1 -0
  125. package/dist/doctor.js +110 -0
  126. package/dist/hash.d.ts +7 -0
  127. package/dist/hash.d.ts.map +1 -0
  128. package/dist/hash.js +34 -0
  129. package/dist/index.d.ts +24 -0
  130. package/dist/index.d.ts.map +1 -0
  131. package/dist/index.js +17 -0
  132. package/dist/lineage.d.ts +23 -0
  133. package/dist/lineage.d.ts.map +1 -0
  134. package/dist/lineage.js +61 -0
  135. package/dist/migrations-status.d.ts +35 -0
  136. package/dist/migrations-status.d.ts.map +1 -0
  137. package/dist/migrations-status.js +131 -0
  138. package/dist/plan-order.d.ts +4 -0
  139. package/dist/plan-order.d.ts.map +1 -0
  140. package/dist/plan-order.js +178 -0
  141. package/dist/planner-replace.d.ts +4 -0
  142. package/dist/planner-replace.d.ts.map +1 -0
  143. package/dist/planner-replace.js +76 -0
  144. package/dist/planner-table.d.ts +3 -0
  145. package/dist/planner-table.d.ts.map +1 -0
  146. package/dist/planner-table.js +165 -0
  147. package/dist/planner.d.ts +5 -0
  148. package/dist/planner.d.ts.map +1 -0
  149. package/dist/planner.js +385 -0
  150. package/dist/render-guards.d.ts +12 -0
  151. package/dist/render-guards.d.ts.map +1 -0
  152. package/dist/render-guards.js +159 -0
  153. package/dist/render.d.ts +7 -0
  154. package/dist/render.d.ts.map +1 -0
  155. package/dist/render.js +325 -0
  156. package/dist/selfcheck.d.ts +11 -0
  157. package/dist/selfcheck.d.ts.map +1 -0
  158. package/dist/selfcheck.js +43 -0
  159. package/dist/source-normalize.d.ts +14 -0
  160. package/dist/source-normalize.d.ts.map +1 -0
  161. package/dist/source-normalize.js +420 -0
  162. package/dist/source.d.ts +4 -0
  163. package/dist/source.d.ts.map +1 -0
  164. package/dist/source.js +233 -0
  165. package/dist/sql/ast.d.ts +42 -0
  166. package/dist/sql/ast.d.ts.map +1 -0
  167. package/dist/sql/ast.js +241 -0
  168. package/dist/sql/canonical-nodes.d.ts +5 -0
  169. package/dist/sql/canonical-nodes.d.ts.map +1 -0
  170. package/dist/sql/canonical-nodes.js +101 -0
  171. package/dist/sql/extract-helpers.d.ts +18 -0
  172. package/dist/sql/extract-helpers.d.ts.map +1 -0
  173. package/dist/sql/extract-helpers.js +127 -0
  174. package/dist/sql/extract.d.ts +13 -0
  175. package/dist/sql/extract.d.ts.map +1 -0
  176. package/dist/sql/extract.js +323 -0
  177. package/dist/sql/facts.d.ts +34 -0
  178. package/dist/sql/facts.d.ts.map +1 -0
  179. package/dist/sql/facts.js +392 -0
  180. package/dist/sql/identifiers.d.ts +13 -0
  181. package/dist/sql/identifiers.d.ts.map +1 -0
  182. package/dist/sql/identifiers.js +83 -0
  183. package/dist/sql/normalize-deparse.d.ts +25 -0
  184. package/dist/sql/normalize-deparse.d.ts.map +1 -0
  185. package/dist/sql/normalize-deparse.js +96 -0
  186. package/dist/sql/object-hash.d.ts +5 -0
  187. package/dist/sql/object-hash.d.ts.map +1 -0
  188. package/dist/sql/object-hash.js +24 -0
  189. package/dist/sql/parser.d.ts +8 -0
  190. package/dist/sql/parser.d.ts.map +1 -0
  191. package/dist/sql/parser.js +89 -0
  192. package/dist/sql/privileges.d.ts +33 -0
  193. package/dist/sql/privileges.d.ts.map +1 -0
  194. package/dist/sql/privileges.js +379 -0
  195. package/dist/sql/split.d.ts +3 -0
  196. package/dist/sql/split.d.ts.map +1 -0
  197. package/dist/sql/split.js +182 -0
  198. package/dist/sql/statements.d.ts +17 -0
  199. package/dist/sql/statements.d.ts.map +1 -0
  200. package/dist/sql/statements.js +284 -0
  201. package/dist/sql/table-constraints.d.ts +15 -0
  202. package/dist/sql/table-constraints.d.ts.map +1 -0
  203. package/dist/sql/table-constraints.js +304 -0
  204. package/dist/sql/table-shape.d.ts +38 -0
  205. package/dist/sql/table-shape.d.ts.map +1 -0
  206. package/dist/sql/table-shape.js +287 -0
  207. package/dist/sync.d.ts +27 -0
  208. package/dist/sync.d.ts.map +1 -0
  209. package/dist/sync.js +86 -0
  210. package/dist/typegen-model.d.ts +78 -0
  211. package/dist/typegen-model.d.ts.map +1 -0
  212. package/dist/typegen-model.js +338 -0
  213. package/dist/typegen-views.d.ts +7 -0
  214. package/dist/typegen-views.d.ts.map +1 -0
  215. package/dist/typegen-views.js +92 -0
  216. package/dist/typegen-zod.d.ts +3 -0
  217. package/dist/typegen-zod.d.ts.map +1 -0
  218. package/dist/typegen-zod.js +149 -0
  219. package/dist/typegen.d.ts +4 -0
  220. package/dist/typegen.d.ts.map +1 -0
  221. package/dist/typegen.js +184 -0
  222. package/dist/validators.d.ts +3 -0
  223. package/dist/validators.d.ts.map +1 -0
  224. package/dist/validators.js +104 -0
  225. package/dist/verify-environment.d.ts +5 -0
  226. package/dist/verify-environment.d.ts.map +1 -0
  227. package/dist/verify-environment.js +92 -0
  228. package/dist/verify.d.ts +3 -0
  229. package/dist/verify.d.ts.map +1 -0
  230. package/dist/verify.js +261 -0
  231. package/docs/benchmarks/additive-correctness.svg +86 -0
  232. package/docs/benchmarks/additive-latency.svg +60 -0
  233. package/docs/benchmarks/functions-policies-correctness.svg +86 -0
  234. package/docs/benchmarks/functions-policies-latency.svg +60 -0
  235. package/docs/benchmarks/realistic-correctness.svg +86 -0
  236. package/docs/benchmarks/realistic-latency.svg +60 -0
  237. package/docs/benchmarks/scaling-latency.svg +106 -0
  238. package/docs/benchmarks/workflow-latency.svg +98 -0
  239. package/docs/benchmarks/xl-correctness.svg +86 -0
  240. package/docs/benchmarks/xl-latency.svg +60 -0
  241. package/docs/benchmarks/xxl-correctness.svg +86 -0
  242. package/docs/benchmarks/xxl-latency.svg +66 -0
  243. package/docs/case-study-anilize.md +51 -0
  244. package/docs/ci-gate.md +44 -0
  245. package/docs/ci.md +68 -0
  246. package/docs/commands.md +35 -0
  247. package/docs/config.md +72 -0
  248. package/docs/corpus.md +33 -0
  249. package/docs/diagnostics.md +77 -0
  250. package/docs/hints.md +92 -0
  251. package/docs/release.md +19 -0
  252. package/docs/support-matrix.md +57 -0
  253. package/examples/postgres/schemas/001_app.sql +17 -0
  254. package/examples/supabase/schemas/001_app.sql +13 -0
  255. package/examples/supabase/schemas-next/001_app.sql +21 -0
  256. package/examples/supabase/supaschema.config.json +15 -0
  257. package/package.json +99 -0
package/docs/config.md ADDED
@@ -0,0 +1,72 @@
1
+ # Configuration Reference
2
+
3
+ `supaschema` looks for config in this order:
4
+
5
+ 1. `--config <path>` (`.json`, `.mjs`, or `.js` with a default export)
6
+ 2. `supaschema.config.json` in the working directory
7
+ 3. `supaschema.config.mjs`, then `supaschema.config.js`
8
+ 4. Built-in defaults
9
+
10
+ Unknown keys are rejected (the schema is strict), so typos fail loudly. The scaffolded config carries a `$schema` pointer at the shipped `config-schema.json` (generated from the Zod schema at build time), so editors autocomplete and validate every key.
11
+
12
+ | Option | Default | Meaning |
13
+ | --- | --- | --- |
14
+ | `$schema` | scaffolded | JSON Schema pointer for editor tooling; ignored by the loader. |
15
+ | `adapter` | `"supabase-auto"` | `supabase-auto` blocks managed Supabase schemas as declarative owners and blocks `CREATE INDEX CONCURRENTLY` plans; `postgres` disables those policies. |
16
+ | `cascade` | `"never"` | `CASCADE` is never emitted. This is not configurable away. |
17
+ | `destructiveChanges` | `"hint-required"` | `hint-required` blocks destructive operations until the object key is listed in `hints.destructive`; `block` always blocks; `allow` permits everything (not recommended). |
18
+ | `environments` | `{}` | Named database targets for the global `--env` flag: `{ "staging": { "databaseUrl": "$STAGING_DB" } }` lets `supaschema --env staging diff ...` resolve the URL (with `$ENV_NAME` indirection) without repeating it per command. |
19
+ | `excludedGrantRoles` | `[]` | Grants and default privileges whose grantee or `FOR ROLE` matches are removed from extracted models (useful for platform roles such as `supabase_admin`). |
20
+ | `hints.destructive` | `[]` | Exact object keys (or `"*"`) approved for destructive drop/replace. |
21
+ | `hints.renames` | `[]` | `{ "from": "<object key>", "to": "<object key>" }` pairs rendered as guarded `ALTER ... RENAME`. |
22
+ | `idempotency` | `"required"` | Rendered SQL is replay-safe by construction. Not configurable away. |
23
+ | `lockTimeout` | `"5s"` | Value for the `SET lock_timeout` migration preamble. |
24
+ | `managedSchemas` | Supabase set | Schemas treated as platform-owned under `supabase-auto`. |
25
+ | `migrationsDir` | `"supabase/migrations"` | Where zero-flag `diff` writes migrations, zero-arg `check` reads them, and `verify` finds the newest pending file. The `--migrations-dir` flag overrides per command. |
26
+ | `typesFile` | `"database.types.ts"` | Where `supaschema types` writes generated TypeScript types. When the file exists, every `diff` that writes a migration regenerates it from the target tree. |
27
+ | `zodFile` | `"database.zod.ts"` | Where `supaschema types` writes generated runtime Zod validators (requires `zod` in the consuming project). Refreshed by `diff` on the same exists-means-opted-in rule as `typesFile`. |
28
+ | `normalize` | `"deparse"` | Every object's SQL is rewritten into canonical form via `pgsql-deparser` (the pure-TypeScript companion of the `libpg-query` parser). Fidelity-gated per object: the canonical text is used only when it reparses to the identical location-stripped parse tree, otherwise the source text is kept with a `SUPA_NORMALIZE_*` warning. Hashes never change (identity is AST-based); rendered output is formatting-independent. Set `"off"` to keep source spelling verbatim. `check` always runs the round-trip proof and reports `SUPA_CHECK_DEPARSE_*` findings regardless of this setting. |
29
+ | `postgresVersion` | `"15+"` | Documentation of the supported floor; guards target PostgreSQL 15+ syntax. |
30
+ | `renameDetection` | `"hints-only"` | `hints-only` uses `hints.renames`; `off` disables rename handling entirely. |
31
+ | `schemaPaths` | `["supabase/schemas"]` | Paths read by `git:` sources and the default `--to dir:` target (the first entry). `supabase/config.toml` is read only to discover the local database URL (`[db] port`), never for schema paths; configure paths here. |
32
+ | `schemas.include` / `schemas.exclude` | `[]` | Persistent schema filters applied to every extracted model (the CLI `--schema` flag composes on top). |
33
+ | `statementTimeout` | `"60s"` | Value for the `SET statement_timeout` migration preamble. |
34
+ | `transactionMode` | `"per-migration"` | How `verify` applies the migration and how transaction hazards are graded. `per-migration` mirrors runners like `supabase db push` (one transaction per file); `per-statement` matches autocommit runners. |
35
+ | `verify --ensure-roles` (CLI) | off | Pre-creates missing `NOLOGIN` roles referenced by grants, default privileges, and policies on the verification server. Roles are cluster-level and are never dropped. |
36
+ | `validators` | `["internal-parser"]` | Additional external validators to run during `check` (see below). |
37
+
38
+ ## Validators
39
+
40
+ `internal-parser` is always the correctness owner. Optional external validators run as subprocesses during `check` and `verify`:
41
+
42
+ - `squawk` / `squawk-cli` — runs the `squawk` binary
43
+ - `pgls` / `postgres-language-server` / `@postgres-language-server/cli` — runs `postgres-language-server check`
44
+ - `sqlfluff` — runs the external Python `sqlfluff lint --dialect postgres`
45
+ - `pg-formatter` / `pgformatter` — runs `pg-formatter --check`
46
+
47
+ A configured validator that is not installed produces `SUPA_VALIDATOR_UNAVAILABLE` as an error: configured checks may not silently skip.
48
+
49
+ ## Example
50
+
51
+ ```json
52
+ {
53
+ "destructiveChanges": "hint-required",
54
+ "excludedGrantRoles": [
55
+ "supabase_admin",
56
+ "supabase_auth_admin",
57
+ "supabase_storage_admin",
58
+ "dashboard_user",
59
+ "pgbouncer",
60
+ "authenticator"
61
+ ],
62
+ "hints": {
63
+ "destructive": ["table:app.legacy_imports"],
64
+ "renames": [{ "from": "table:app.accounts", "to": "table:app.customers" }]
65
+ },
66
+ "lockTimeout": "5s",
67
+ "schemas": { "exclude": [], "include": ["app", "public"] },
68
+ "statementTimeout": "60s",
69
+ "transactionMode": "per-migration",
70
+ "validators": ["internal-parser", "squawk"]
71
+ }
72
+ ```
package/docs/corpus.md ADDED
@@ -0,0 +1,33 @@
1
+ # The corpus oracle
2
+
3
+ `supaschema corpus` exists because of the oracle problem: every other gate in this package compares supaschema to itself. `verify` builds both temporary databases from the engine's own models, so a modeling error that is symmetric in the models produces two identically-wrong databases — fingerprints match and the bug ships. Fixtures only contain constructs their author thought of, while real catalogs carry state no tree ever declares: materialized default ACL entries, `pg_init_privs` baselines, executor-role-attributed default privileges, statement-order grant churn.
4
+
5
+ The corpus is an independently-evolved, deliberately dirty database. The lane:
6
+
7
+ 1. Create a disposable database and apply `roles.sql` (the minimal platform role contract — applied by the runner over the wire, never by Docker volume mounts, so it works identically in CI service containers and local runs).
8
+ 2. Replay `migrations/*.sql` in version order, one transaction per file (mirroring `supabase db push`). These migrations deliberately deposit catalog noise: explicit grants that materialize `proacl` default entries, no-op revokes that leave no trace, default-ACL rows, RLS facet pairs, serial decomposition with hostile quoting, grant-then-revoke churn.
9
+ 3. Cross-lane diff the dirty database against `tree/` (the same end state in clean declarative spelling, plus intended pending work) and render the reconciliation under `corpus.json`.
10
+ 4. Static-check the rendered migration, then apply it twice — the second apply must leave the catalog fingerprint unchanged.
11
+ 5. Re-diff the database against the tree: the residual must be zero operations (`SUPA_CORPUS_RECONVERGENCE` otherwise). A correct diff engine must converge; false drift cannot.
12
+
13
+ ## Running it
14
+
15
+ ```sh
16
+ npm run corpus:check
17
+ # or
18
+ supaschema corpus --corpus-dir corpus/supabase-style [--database-url <url>] [--json]
19
+ ```
20
+
21
+ The database URL resolves like every other command (flag, then `SUPASCHEMA_DATABASE_URL`, then the nearest `supabase/config.toml`). Without a resolvable URL the command prints a skip notice and exits 0, so OS-matrix CI jobs without a database pass through. Non-local hosts are refused unless `SUPASCHEMA_CORPUS_ALLOW_REMOTE=1`.
22
+
23
+ ## Regression-corpus discipline
24
+
25
+ Every future false-drift or parity bug adds its statement class to the corpus in the same change that fixes it: the triggering DDL goes into `corpus/supabase-style/migrations/`, the clean declarative spelling goes into `tree/`, and `npm run corpus:check` must converge. The corpus is the living inventory of every noise class the engine has ever mishandled.
26
+
27
+ ## Bring your own corpus
28
+
29
+ `--corpus-dir` accepts any directory with the same four pieces (`roles.sql`, `migrations/`, `tree/`, `corpus.json`), so a consuming repository can maintain its **own** corpus pinning the noise classes from its own incidents: when a diff misbehaves on your schema, copy the triggering DDL into your corpus migrations, express the same end state in your corpus tree, and wire `supaschema corpus --corpus-dir <your-corpus>` into your CI. The shipped `corpus/supabase-style` keeps protecting the engine; yours keeps protecting your schema's specific shapes.
30
+
31
+ ## Scope
32
+
33
+ The corpus lane proves engine correctness against dirty-real state. It does not replace the repo-side drift gate: continuous assurance for an actual repository is `supaschema diff --fail-on-diff` plus `supaschema migrations` against the live database, which detect when that repo's tree and databases diverge. Corpus = is the engine right; drift gate = is the repo in sync.
@@ -0,0 +1,77 @@
1
+ # Diagnostics
2
+
3
+ Diagnostics have `code`, `severity`, `message`, and optional `ref`, `file`, `statement`, and `hint` fields.
4
+
5
+ | Code | Severity | Meaning |
6
+ | --- | --- | --- |
7
+ | `SUPA_PARSE_ERROR` | error | `libpg-query` could not parse the SQL. |
8
+ | `SUPA_PARSE_UNAVAILABLE` | warning | The parser dependency did not expose an expected parser function. |
9
+ | `SUPA_EXTRACT_PARSER_REQUIRED` | error | AST extraction requires the `libpg-query` parser; there is no regex fallback. |
10
+ | `SUPA_EXTRACT_UNSUPPORTED` | error | The source contains DDL that `supaschema` cannot model safely. |
11
+ | `SUPA_EXTRACT_DUPLICATE_OBJECT` | error | Two source statements claim the same object identity. |
12
+ | `SUPA_EXTRACT_SIDE_EFFECT_UNSUPPORTED` | error | The source contains a data/control-plane side-effect statement that is not a replay-safe schema object. |
13
+ | `SUPA_OBJECT_PARSE_FAILED` | error | Object SQL did not parse, so its identity hash fell back to normalized text. |
14
+ | `SUPA_SUPABASE_MANAGED_SCHEMA` | error | A Supabase-managed schema was edited as declarative source. |
15
+ | `SUPA_SUPABASE_VIEW_SECURITY_INVOKER` | warning | A view in an exposed schema does not set `security_invoker`, so RLS applies as the view owner. |
16
+ | `SUPA_CATALOG_EXTRACT_FAILED` | error | Catalog extraction failed against the supplied database. |
17
+ | `SUPA_CATALOG_SNAPSHOT_VERSION` | warning | A `catalog:` snapshot was produced by a different supaschema model version; regenerate it to keep hashes comparable. |
18
+ | `SUPA_OBJECT_PARSE_FAILED` | error | Object SQL did not parse, so its identity hash fell back to normalized text. |
19
+ | `SUPA_DIFF_LINEAGE_BROKEN` | error | The plan's from-state does not continue the newest pending supaschema migration in the output directory. |
20
+ | `SUPA_DIFF_LINEAGE_DUPLICATE` | error | A pending supaschema migration already covers this exact from/to transition. |
21
+ | `SUPA_DIFF_OUTPUT_EXISTS` | error | The output migration file already exists; supaschema never overwrites migrations. |
22
+ | `SUPA_PLAN_DESTRUCTIVE_HINT_REQUIRED` | error | A destructive change lacks an explicit hint. |
23
+ | `SUPA_PLAN_ADD_COLUMN_UNSAFE` | error | An additive column change needs explicit migration review. |
24
+ | `SUPA_PLAN_COLUMN_ALTER_HINT_REQUIRED` | error | Column drops and type changes render data-preserving ALTERs only after a destructive-change hint. |
25
+ | `SUPA_PLAN_DEPENDENCY_CYCLE` | error | Dependency ordering produced a cycle. |
26
+ | `SUPA_PLAN_EMPTY_WITH_DRIFT` | error | The plan has no operations but the model fingerprints differ; an empty migration would silently mask real drift. |
27
+ | `SUPA_PLAN_RENAME_HINT_UNMATCHED` | error | A rename hint did not match both source and target objects. |
28
+ | `SUPA_PLAN_RENAME_KIND_MISMATCH` | error | A rename hint maps between different object kinds. |
29
+ | `SUPA_PLAN_RENAME_SET_SCHEMA_UNSUPPORTED` | error | A rename hint attempts to move an object between schemas. |
30
+ | `SUPA_PLAN_RENAME_UNSUPPORTED` | error | The object kind cannot yet be renamed safely by the renderer. |
31
+ | `SUPA_PLAN_RENAME_VERIFY_REQUIRED` | warning | Rename output must be verified against PostgreSQL before release. |
32
+ | `SUPA_PLAN_ROUTINE_RETURN_TYPE_CHANGED` | error | The routine's return type or OUT parameters changed; `CREATE OR REPLACE` cannot apply it without a hinted drop. |
33
+ | `SUPA_PLAN_VIEW_REPLACE_INCOMPATIBLE` | error | The view drops, renames, or reorders output columns; `CREATE OR REPLACE VIEW` cannot apply it without a hinted drop. |
34
+ | `SUPA_PLAN_VIEW_REPLACE_VERIFY_REQUIRED` | warning | View output columns are statically unknowable; verify the replacement against PostgreSQL rules. |
35
+ | `SUPA_PLAN_CONCURRENT_INDEX_UNSUPPORTED` | error | `CREATE INDEX CONCURRENTLY` cannot run inside the transaction the migration runner uses. |
36
+ | `SUPA_CHECK_CASCADE` | error | `CASCADE` appears in migration SQL. |
37
+ | `SUPA_CHECK_DROP_IF_EXISTS` | error | A `DROP` statement lacks `IF EXISTS`. |
38
+ | `SUPA_CHECK_CREATE_SCHEMA_GUARD` | error | `CREATE SCHEMA` lacks `IF NOT EXISTS` or a catalog guard. |
39
+ | `SUPA_CHECK_CREATE_EXTENSION_GUARD` | error | `CREATE EXTENSION` lacks `IF NOT EXISTS` or a catalog guard. |
40
+ | `SUPA_CHECK_CREATE_TABLE_GUARD` | error | `CREATE TABLE` lacks `IF NOT EXISTS` or a catalog guard. |
41
+ | `SUPA_CHECK_CREATE_SEQUENCE_GUARD` | error | `CREATE SEQUENCE` lacks `IF NOT EXISTS` or a catalog guard. |
42
+ | `SUPA_CHECK_CREATE_INDEX_GUARD` | error | `CREATE INDEX` lacks `IF NOT EXISTS` or a catalog guard. |
43
+ | `SUPA_CHECK_CREATE_MATERIALIZED_VIEW_GUARD` | error | `CREATE MATERIALIZED VIEW` lacks `IF NOT EXISTS` or a catalog guard. |
44
+ | `SUPA_CHECK_CREATE_VIEW_REPLACE` | error | `CREATE VIEW` lacks `OR REPLACE`. |
45
+ | `SUPA_CHECK_CREATE_ROUTINE_REPLACE` | error | `CREATE FUNCTION` or `CREATE PROCEDURE` lacks `OR REPLACE`. |
46
+ | `SUPA_CHECK_CREATE_TYPE_GUARD` | error | `CREATE TYPE` or `CREATE DOMAIN` is not wrapped in a catalog guard. |
47
+ | `SUPA_CHECK_ADD_CONSTRAINT_GUARD` | error | `ALTER TABLE ... ADD CONSTRAINT` is not wrapped in a catalog guard. |
48
+ | `SUPA_CHECK_CREATE_TRIGGER_REPLACEMENT` | error | `CREATE TRIGGER` is not preceded by `DROP TRIGGER IF EXISTS`. |
49
+ | `SUPA_CHECK_SEARCH_PATH` | error | Migration SQL depends on session `search_path`. |
50
+ | `SUPA_CHECK_SECURITY_DEFINER_SEARCH_PATH` | warning | A `SECURITY DEFINER` function lacks function-local `SET search_path`. |
51
+ | `SUPA_CHECK_ENUM_VALUE_USE_SAME_TRANSACTION` | error/warning | An enum value added in this migration is used later in the same file; transactional runners fail. Error under `transactionMode: "per-migration"`, warning under `per-statement`. |
52
+ | `SUPA_CHECK_NONTRANSACTIONAL_INDEX` | error/warning | `CREATE INDEX CONCURRENTLY` needs transaction wrapping disabled. Error under `supabase-auto` or `per-migration` mode. |
53
+ | `SUPA_CHECK_NONTRANSACTIONAL_REFRESH` | error/warning | `REFRESH MATERIALIZED VIEW CONCURRENTLY` needs transaction wrapping disabled. Error under `supabase-auto` or `per-migration` mode. |
54
+ | `SUPA_CHECK_ALTER_COLUMN_TYPE_REWRITE` | warning | `ALTER COLUMN TYPE` can rewrite the table under an `ACCESS EXCLUSIVE` lock. |
55
+ | `SUPA_CHECK_SET_NOT_NULL_SCAN` | warning | `SET NOT NULL` scans the table unless a validated `CHECK` constraint already proves it. |
56
+ | `SUPA_CHECK_DEPARSE_MISMATCH` | warning | A statement does not round-trip through the deparser to an identical parse tree; `normalize: "deparse"` would keep its source text. |
57
+ | `SUPA_CHECK_DEPARSE_UNSUPPORTED` | warning | A statement cannot be deparsed for the round-trip proof. |
58
+ | `SUPA_NORMALIZE_FIDELITY` | warning | Deparsed SQL did not reparse to the identical parse tree; the object kept its source text under `normalize: "deparse"`. |
59
+ | `SUPA_NORMALIZE_UNSUPPORTED` | warning | The deparser cannot render this object; it kept its source text under `normalize: "deparse"`. |
60
+ | `SUPA_CORPUS_RECONVERGENCE` | error | The corpus oracle did not converge: residual operations remain after applying the rendered reconciliation to the dirty corpus database, the second apply changed the catalog, or a pipeline stage failed (see `docs/corpus.md`). |
61
+ | `SUPA_CHECK_VOLATILE_DEFAULT_REWRITE` | warning | `ADD COLUMN` with a volatile default rewrites the whole table. |
62
+ | `SUPA_CHECK_INSERT_ON_CONFLICT` | error | `INSERT` statements in migrations must use `ON CONFLICT` for replay safety. |
63
+ | `SUPA_CHECK_DML_REVIEW` | warning | `UPDATE`/`DELETE` statements need explicit idempotency review. |
64
+ | `SUPA_CHECK_POLICY_REPLACEMENT` | error | `CREATE POLICY` should be paired with an explicit prior drop for replacement. |
65
+ | `SUPA_SELFCHECK_HASH_MISMATCH` | error | A catalog object hashes differently after its rendered SQL is re-extracted; cross-lane diffs would report a false change. |
66
+ | `SUPA_SELFCHECK_MISSING` | error | A catalog object disappeared when its rendered SQL was re-extracted. |
67
+ | `SUPA_SELFCHECK_UNEXPECTED` | error | Re-extraction produced an object the catalog model does not contain. |
68
+ | `SUPA_VALIDATOR_FAILED` | error | A configured external validator reported diagnostics. |
69
+ | `SUPA_VALIDATOR_UNAVAILABLE` | error | A configured external validator binary was not found. |
70
+ | `SUPA_VALIDATOR_UNKNOWN` | error | The config references an unknown validator. |
71
+ | `SUPA_VERIFY_CLEANUP_FAILED` | warning | A temporary verification database could not be dropped and may need manual removal. |
72
+ | `SUPA_VERIFY_FINGERPRINT_MISMATCH` hints | — | The mismatch hint names the differing objects: missing from the migration result, not present in the target, and definition differs. |
73
+ | `SUPA_VERIFY_FAILED` | error | Temporary database verification failed. |
74
+ | `SUPA_VERIFY_FINGERPRINT_MISMATCH` | error | Applying the migration twice did not produce the target catalog fingerprint. |
75
+ | `SUPA_VERIFY_ROLE_CAPABILITY` | error | The verification role cannot `CREATE DATABASE`; use a role with `CREATEDB` (on local Supabase stacks prefer `supabase_admin`). |
76
+ | `SUPA_VERIFY_STUB_REFERENCE` | warning | A `verify` failure under `--ensure-environment` references a Supabase-managed schema that the environment stub only provisions minimally; the failure may be a stub limitation rather than a real migration defect. Re-run against a real Supabase database to confirm. |
77
+ | `SUPA_VERIFY_RECONVERGENCE` | error | The migrated catalog cross-lane diffed against the target model is not empty; the model declares state the catalog cannot reproduce (false drift), or lane parity is broken. |
package/docs/hints.md ADDED
@@ -0,0 +1,92 @@
1
+ # Hints Guide
2
+
3
+ `supaschema` fails closed when intent cannot be proven from the SQL parse tree. Hints are the explicit, reviewable way to state intent.
4
+
5
+ ## Destructive changes
6
+
7
+ A blocked plan looks like this:
8
+
9
+ ```
10
+ ERROR SUPA_PLAN_DESTRUCTIVE_HINT_REQUIRED [table:app.legacy_imports]: drop of table requires an explicit destructive-change hint
11
+ hint: Add "table:app.legacy_imports" to hints.destructive only after reviewing the migration.
12
+ ```
13
+
14
+ Recovery: review the rendered `-- BLOCKED` section, confirm the drop is intended, then add the exact object key:
15
+
16
+ ```json
17
+ {
18
+ "hints": {
19
+ "destructive": ["table:app.legacy_imports"]
20
+ }
21
+ }
22
+ ```
23
+
24
+ `"*"` approves all destructive changes for one run; prefer exact keys in committed config.
25
+
26
+ Dropped grants render as `REVOKE`, and dropped default privileges render as the reverse `ALTER DEFAULT PRIVILEGES ... REVOKE`, but both still require the destructive hint first.
27
+
28
+ ## Column drops and type changes
29
+
30
+ When only columns change, the hinted plan renders data-preserving column ALTERs instead of replacing the table (`SUPA_PLAN_COLUMN_ALTER_HINT_REQUIRED`):
31
+
32
+ - removed columns render `ALTER TABLE ... DROP COLUMN IF EXISTS`,
33
+ - type changes render `ALTER COLUMN ... TYPE <type> USING <column>::<type>`,
34
+ - `NOT NULL` and `DEFAULT` changes render without a hint.
35
+
36
+ Identity/generated changes and table-constraint changes fall back to the replace lane. A hinted table **replace** drops and recreates the table — it is not data-preserving; prefer the column lane or hand-author the migration.
37
+
38
+ ## Incompatible view and routine replacements
39
+
40
+ PostgreSQL rejects `CREATE OR REPLACE` when a view drops/renames/reorders output columns (`SUPA_PLAN_VIEW_REPLACE_INCOMPATIBLE`) or a routine changes its return type or OUT parameters (`SUPA_PLAN_ROUTINE_RETURN_TYPE_CHANGED`). Hinting the object key renders a guarded `DROP ... IF EXISTS` followed by the new definition. Dependent objects (views over views) must be hinted and recreated in the same plan.
41
+
42
+ ## Renames
43
+
44
+ Without a hint, a rename diffs as drop+create (destructive, blocked). Declare the rename instead:
45
+
46
+ ```json
47
+ {
48
+ "hints": {
49
+ "renames": [{ "from": "table:app.accounts", "to": "table:app.customers" }]
50
+ }
51
+ }
52
+ ```
53
+
54
+ The rendered SQL is a guarded `DO` block: it raises if both names exist, renames if only the old name exists, and is a no-op if the rename already happened. Rename hints:
55
+
56
+ - must keep the same object kind (`SUPA_PLAN_RENAME_KIND_MISMATCH`),
57
+ - must stay in the same schema (`SUPA_PLAN_RENAME_SET_SCHEMA_UNSUPPORTED`),
58
+ - support schemas, tables, sequences, indexes, functions, procedures, views, and materialized views (`SUPA_PLAN_RENAME_UNSUPPORTED` otherwise),
59
+ - always carry a `SUPA_PLAN_RENAME_VERIFY_REQUIRED` warning — run `supaschema verify`.
60
+
61
+ ## Object keys
62
+
63
+ Keys follow `kind:schema.name`, with `(signature)` for functions/procedures and `:table` for table-scoped objects:
64
+
65
+ ```
66
+ table:app.accounts
67
+ function:app.greeting()
68
+ policy:app.accounts_select:accounts
69
+ index:app.accounts_email_idx:accounts
70
+ grant:grant:table:app.accounts:authenticated
71
+ ```
72
+
73
+ `supaschema plan` prints every operation key; copy keys from there rather than constructing them by hand.
74
+
75
+ ## Enum reordering and removal
76
+
77
+ Enum value removal/reordering has no in-place ALTER in PostgreSQL. The hinted plan replaces the type (drop+create), which fails while columns depend on it. The safe hand-authored recipe is:
78
+
79
+ ```sql
80
+ CREATE TYPE app.mood_next AS ENUM ('happy', 'curious');
81
+ ALTER TABLE app.entries
82
+ ALTER COLUMN current TYPE app.mood_next
83
+ USING current::text::app.mood_next;
84
+ DROP TYPE IF EXISTS app.mood;
85
+ ALTER TYPE app.mood_next RENAME TO mood;
86
+ ```
87
+
88
+ Validate it with `supaschema check` and `supaschema verify` like any hand-authored migration.
89
+
90
+ ## Changes that have no hint
91
+
92
+ Data backfills and `CASCADE` drops have no hint lane by design. Hand-author those migrations and validate them with `supaschema check` and `supaschema verify`.
@@ -0,0 +1,19 @@
1
+ # Release
2
+
3
+ The package is designed for npm trusted publishing from GitHub Actions.
4
+
5
+ 1. Create the public GitHub repository.
6
+ 2. Add the npm trusted publisher for the repository and release workflow.
7
+ 3. Push a tag or publish a GitHub release.
8
+ 4. Confirm CI passes lint, typecheck, tests, fixture diff, fixture verification, the corpus oracle (`npm run corpus:check`, dirty-real reconvergence), source-tree, dump, catalog-snapshot, large, live-catalog, shadow-round-trip, and replay-verification benchmarks, `npm pack`, global tarball install, and `npx` tarball smoke.
9
+ 5. Stamp the release date on the `0.1.0 (unreleased)` CHANGELOG heading in the release commit.
10
+
11
+ The release workflow uses:
12
+
13
+ - `permissions.id-token: write`
14
+ - `actions/setup-node` with `registry-url: https://registry.npmjs.org`
15
+ - `npm publish --access public`
16
+
17
+ With npm trusted publishing, provenance is generated through the GitHub Actions OIDC trust relationship, so no long-lived npm token is required.
18
+
19
+ Do not publish from a laptop for production releases.
@@ -0,0 +1,57 @@
1
+ # Support Matrix
2
+
3
+ `supaschema` is fail-closed: unsupported DDL blocks migration generation instead of guessing. Extraction and checking are AST-only — every statement is classified through the PostgreSQL parser, never regex.
4
+
5
+ | Object | Extract | Render | Notes |
6
+ | --- | --- | --- | --- |
7
+ | Schemas | Yes | `CREATE SCHEMA IF NOT EXISTS` / `DROP SCHEMA IF EXISTS` | Supabase-managed schemas are blocked in `supabase-auto`. |
8
+ | Extensions | Yes | `CREATE EXTENSION IF NOT EXISTS` | Extension schema is fingerprint metadata. |
9
+ | Types/enums | Yes | `DO` catalog guard / `DROP TYPE IF EXISTS`; appended enum values render as `ALTER TYPE ... ADD VALUE IF NOT EXISTS` | Enum narrowing, removal, or reordering is destructive. |
10
+ | Domains | Yes | `DO` catalog guard / `DROP DOMAIN IF EXISTS` | Domain replacement is destructive. |
11
+ | Tables | Yes | `CREATE TABLE IF NOT EXISTS`; additive columns use `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` | Unsafe column changes, table replacement, and table drop block. Standalone `ALTER COLUMN ... SET/DROP DEFAULT` folds into the table's canonical shape. |
12
+ | Foreign data wrappers | Yes (whole-object) | `DO` catalog guard (no `IF NOT EXISTS` form upstream) / `DROP FOREIGN DATA WRAPPER IF EXISTS` | Drops and replaces are destructive-gated. Extension-owned wrappers are excluded from catalogs. |
13
+ | Foreign servers | Yes (whole-object) | `CREATE SERVER IF NOT EXISTS` / `DROP SERVER IF EXISTS` | Drops and replaces are destructive-gated. |
14
+ | Foreign tables | Yes (whole-object) | `CREATE FOREIGN TABLE IF NOT EXISTS` / `DROP FOREIGN TABLE IF EXISTS` | No column-level diffing; user mappings are excluded (credentials). |
15
+ | Constraints | Yes | `DO` catalog guard / `DROP CONSTRAINT IF EXISTS` | External `ALTER TABLE ADD CONSTRAINT` is modeled separately. |
16
+ | Indexes | Yes | `CREATE INDEX IF NOT EXISTS` | `CONCURRENTLY` emits transaction metadata. |
17
+ | Sequences | Yes | `CREATE SEQUENCE IF NOT EXISTS` | Drop requires destructive hint. |
18
+ | Functions/procedures | Yes | `CREATE OR REPLACE` / `DROP ... IF EXISTS` | Return-type incompatible replacements fail verification. |
19
+ | Views | Yes | `CREATE OR REPLACE VIEW` | PostgreSQL-compatible replacement shape must be verified. |
20
+ | Materialized views | Yes | `CREATE MATERIALIZED VIEW IF NOT EXISTS` | Replacement/drop requires destructive hint. |
21
+ | Triggers | Yes | `DROP TRIGGER IF EXISTS` then create | Replacement is explicit. |
22
+ | RLS | Yes | `ALTER TABLE ... ROW LEVEL SECURITY` | Removal/replacement is destructive. |
23
+ | Policies | Yes | `DROP POLICY IF EXISTS` then create | PostgreSQL has no `CREATE OR REPLACE POLICY`. |
24
+ | Grants/default privileges | Yes (structured per target × grantee) | Canonical `GRANT`; hinted removal renders `REVOKE` / reverse `ALTER DEFAULT PRIVILEGES ... REVOKE` | Split statements for one target × grantee aggregate into one privilege-set identity (full-set unions collapse to `ALL`); mixed `WITH GRANT OPTION` splits stay duplicate errors. |
25
+ | Comments | Yes (keyed by structured descriptor) | `COMMENT ON ...`; removal renders `COMMENT ON ... IS NULL` | Live catalogs extract relation, column, function, and schema comments. |
26
+ | Side-effect statements | Blocks | No | `INSERT`, `UPDATE`, `DELETE`, `DO`, and control-plane `SELECT` statements belong in explicit reviewed migrations. |
27
+
28
+ ## Supabase Adapter
29
+
30
+ With `adapter: "supabase-auto"`, objects in these platform-owned schemas are blocked: `auth`, `storage`, `realtime`, `vault`, `extensions`, `cron`, `net`, `supabase_functions`, `graphql`, and `graphql_public`.
31
+
32
+ ## Verify Environment Stub
33
+
34
+ `verify --ensure-environment` (the default under `adapter: "supabase-auto"`) provisions a minimal stand-in for the Supabase-provisioned surface so a declarative tree that _references_ managed schemas can apply against bare PostgreSQL:
35
+
36
+ - `auth.users` with the stable GoTrue column set (`id`, `aud`, `role`, `email`, `phone`, `raw_app_meta_data`, `raw_user_meta_data`, `last_sign_in_at`, `is_anonymous`, …).
37
+ - The `auth.uid()`, `auth.role()`, `auth.jwt()`, and `auth.email()` helper functions.
38
+ - The `cron.job` and `cron.job_run_details` tables.
39
+
40
+ The stub is symmetric across both temporary databases and subtracted from the reconvergence check, so it never affects catalog parity. It is an **approximation**: other managed objects (`storage.*`, `realtime.*`, `vault.*`, `auth.identities`, `auth.sessions`, …) are not stubbed. A migration that references an un-stubbed managed object fails verify with `SUPA_VERIFY_FAILED` plus a `SUPA_VERIFY_STUB_REFERENCE` warning naming the schema — that failure may be a stub limitation rather than a real defect; re-run verify against a real Supabase database (without `--ensure-environment`) to confirm.
41
+
42
+ ## Examples
43
+
44
+ - `examples/supabase/schemas` demonstrates Supabase-style declarative schema input with RLS and policies; `examples/supabase/schemas-next` is the evolved tree, so a full diff runs out of the box:
45
+
46
+ ```bash
47
+ cd examples/supabase
48
+ npx supaschema diff --from dir:schemas --to dir:schemas-next --out stdout
49
+ ```
50
+
51
+ - `examples/postgres/schemas` demonstrates a generic PostgreSQL schema tree without Supabase-managed schema rules.
52
+
53
+ ## Rename Policy
54
+
55
+ Rename detection is hints-only. The planner does not infer renames from similar definitions because a false positive can destroy data.
56
+
57
+ Explicit hints render guarded idempotent renames for schemas, tables, sequences, indexes, functions, procedures, views, and materialized views. Unsupported rename kinds, schema moves, and kind mismatches block.
@@ -0,0 +1,17 @@
1
+ CREATE SCHEMA app;
2
+
3
+ CREATE TABLE app.accounts (
4
+ id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
5
+ email text NOT NULL,
6
+ created_at timestamptz DEFAULT now() NOT NULL
7
+ );
8
+
9
+ CREATE INDEX accounts_email_idx ON app.accounts (email);
10
+
11
+ CREATE OR REPLACE FUNCTION app.account_count()
12
+ RETURNS bigint
13
+ LANGUAGE sql
14
+ STABLE
15
+ AS $$
16
+ SELECT count(*) FROM app.accounts
17
+ $$;
@@ -0,0 +1,13 @@
1
+ CREATE SCHEMA app;
2
+
3
+ CREATE TABLE app.accounts (
4
+ id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
5
+ name text NOT NULL
6
+ );
7
+
8
+ ALTER TABLE app.accounts ENABLE ROW LEVEL SECURITY;
9
+
10
+ CREATE POLICY accounts_select ON app.accounts
11
+ FOR SELECT
12
+ TO public
13
+ USING (true);
@@ -0,0 +1,21 @@
1
+ CREATE SCHEMA app;
2
+
3
+ CREATE TYPE app.account_status AS ENUM ('active', 'suspended');
4
+
5
+ CREATE TABLE app.accounts (
6
+ id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
7
+ name text NOT NULL,
8
+ status app.account_status DEFAULT 'active'::app.account_status NOT NULL
9
+ );
10
+
11
+ ALTER TABLE app.accounts ENABLE ROW LEVEL SECURITY;
12
+
13
+ CREATE POLICY accounts_select ON app.accounts
14
+ FOR SELECT
15
+ TO public
16
+ USING (true);
17
+
18
+ CREATE OR REPLACE VIEW app.account_names AS
19
+ SELECT id, name FROM app.accounts;
20
+
21
+ COMMENT ON TABLE app.accounts IS 'Customer accounts';
@@ -0,0 +1,15 @@
1
+ {
2
+ "adapter": "supabase-auto",
3
+ "destructiveChanges": "hint-required",
4
+ "excludedGrantRoles": [
5
+ "supabase_admin",
6
+ "supabase_auth_admin",
7
+ "supabase_storage_admin",
8
+ "dashboard_user",
9
+ "pgbouncer",
10
+ "authenticator"
11
+ ],
12
+ "schemaPaths": ["schemas"],
13
+ "transactionMode": "per-migration",
14
+ "validators": ["internal-parser"]
15
+ }
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "supaschema",
3
+ "version": "0.1.0-rc.1",
4
+ "description": "From declarative schema to fully synced database in milliseconds with one command. No ORM, no Docker, and no shadow database needed.",
5
+ "type": "module",
6
+ "license": "AGPL-3.0-only",
7
+ "author": "John McLaughlin",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/jmclaughlin724/supaschema.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/jmclaughlin724/supaschema/issues"
14
+ },
15
+ "homepage": "https://github.com/jmclaughlin724/supaschema#readme",
16
+ "bin": {
17
+ "supaschema": "bin/supaschema"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ ".agents",
27
+ ".claude",
28
+ ".codex",
29
+ "AGENTS.md",
30
+ "benchmarks/README.md",
31
+ "benchmarks/compare.js",
32
+ "benchmarks/fixtures",
33
+ "benchmarks/plot-lib.js",
34
+ "benchmarks/plot-svg.js",
35
+ "benchmarks/plot.js",
36
+ "benchmarks/tools",
37
+ "bin",
38
+ "config-schema.json",
39
+ "corpus",
40
+ "dist",
41
+ "docs",
42
+ "examples",
43
+ "LICENSE",
44
+ "LICENSE-COMMERCIAL.md",
45
+ "README.md"
46
+ ],
47
+ "engines": {
48
+ "node": ">=22"
49
+ },
50
+ "scripts": {
51
+ "benchmark": "npm run build && node dist/benchmark.js",
52
+ "bench:compare": "npm run build && node benchmarks/compare.js",
53
+ "bench:plot": "node benchmarks/plot.js",
54
+ "build": "tsc -p tsconfig.json && node dist/config-schema-gen.js",
55
+ "check": "npm run lint && npm run typecheck && npm test && npm run build",
56
+ "check:package": "publint && attw --pack . --profile esm-only",
57
+ "clean": "rm -rf dist coverage",
58
+ "docs:api": "typedoc src/index.ts --out api-docs",
59
+ "corpus:check": "npm run build && node dist/cli.js corpus --corpus-dir corpus/supabase-style",
60
+ "fixture:diff": "node dist/cli.js diff --from dir:tests/fixtures/basic/from --to dir:tests/fixtures/basic/to --out stdout",
61
+ "fixture:verify": "npm run build && out=$(mktemp -d)/basic.sql && node dist/cli.js diff --from dir:tests/fixtures/basic/from --to dir:tests/fixtures/basic/to --out \"$out\" && node dist/cli.js verify --from dir:tests/fixtures/basic/from --to dir:tests/fixtures/basic/to --migration \"$out\"",
62
+ "format": "biome check --write .",
63
+ "lint": "biome check .",
64
+ "pack:dry": "npm pack --dry-run",
65
+ "postinstall": "node bin/postinstall.mjs",
66
+ "prepare": "npm run build",
67
+ "test": "vitest run",
68
+ "test:coverage": "vitest run --coverage",
69
+ "typecheck": "tsc -p tsconfig.json --noEmit"
70
+ },
71
+ "keywords": [
72
+ "postgres",
73
+ "postgresql",
74
+ "supabase",
75
+ "migration",
76
+ "schema",
77
+ "diff",
78
+ "sql"
79
+ ],
80
+ "dependencies": {
81
+ "commander": "^14.0.3",
82
+ "libpg-query": "^17.7.3",
83
+ "pg": "^8.21.0",
84
+ "pgsql-deparser": "^17.18.3",
85
+ "zod": "^4.4.3"
86
+ },
87
+ "devDependencies": {
88
+ "@arethetypeswrong/cli": "^0.18.3",
89
+ "@biomejs/biome": "^2.4.16",
90
+ "@types/node": "^25.9.3",
91
+ "@types/pg": "^8.15.6",
92
+ "@vitest/coverage-v8": "^4.1.8",
93
+ "fast-check": "^4.8.0",
94
+ "publint": "^0.3.21",
95
+ "typedoc": "^0.28.19",
96
+ "typescript": "^6.0.3",
97
+ "vitest": "^4.1.8"
98
+ }
99
+ }