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/dist/sync.js ADDED
@@ -0,0 +1,86 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { checkMigrationSql } from "./check.js";
5
+ import { diagnostic, hasErrors } from "./diagnostics.js";
6
+ import { migrationsStatus, renderMigrationsStatus } from "./migrations-status.js";
7
+ /**
8
+ * Auto-sync orchestration: supaschema gates, the Supabase CLI applies. Every
9
+ * pending migration must pass the static replay-safety check before any
10
+ * runner executes; ghost or out-of-order history refuses outright; and
11
+ * nothing touches a database unless `local`/`remote` was explicitly chosen —
12
+ * the default is a dry run that prints exactly what would execute. History
13
+ * stays runner-owned: supaschema never writes
14
+ * supabase_migrations.schema_migrations itself.
15
+ */
16
+ export async function syncMigrations(options) {
17
+ const diagnostics = [];
18
+ const status = await migrationsStatus({
19
+ directory: options.directory,
20
+ ...(options.databaseUrl === undefined ? {} : { databaseUrl: options.databaseUrl }),
21
+ });
22
+ diagnostics.push(...status.diagnostics);
23
+ const lines = [renderMigrationsStatus(status.report).trimEnd()];
24
+ if (hasErrors(status.diagnostics)) {
25
+ lines.push("refusing to sync: resolve ghost or out-of-order history first");
26
+ return { applied: false, diagnostics, pending: status.report.pending, report: render(lines) };
27
+ }
28
+ if (status.report.pending.length === 0) {
29
+ lines.push("nothing to sync: disk and target history match");
30
+ return { applied: false, diagnostics, pending: [], report: render(lines) };
31
+ }
32
+ for (const file of status.report.pending) {
33
+ const sql = await readFile(join(options.directory, file), "utf8");
34
+ const checkDiagnostics = await checkMigrationSql(sql, {
35
+ ...(options.config === undefined ? {} : { config: options.config }),
36
+ });
37
+ const errors = checkDiagnostics.filter((item) => item.severity === "error");
38
+ diagnostics.push(...errors);
39
+ if (errors.length > 0) {
40
+ lines.push(`refusing to sync: ${file} fails the replay-safety check`);
41
+ return {
42
+ applied: false,
43
+ diagnostics,
44
+ pending: status.report.pending,
45
+ report: render(lines),
46
+ };
47
+ }
48
+ lines.push(`checked: ${file} (replay-safe)`);
49
+ }
50
+ const planned = [];
51
+ if (options.local === true) {
52
+ planned.push(["supabase", "migration", "up"]);
53
+ }
54
+ if (options.remote === true) {
55
+ planned.push(["supabase", "db", "push"]);
56
+ }
57
+ if (planned.length === 0) {
58
+ lines.push(`dry run: pass --local to apply ${status.report.pending.length} pending migration(s) via \`supabase migration up\`, --remote to push via \`supabase db push\``);
59
+ return { applied: false, diagnostics, pending: status.report.pending, report: render(lines) };
60
+ }
61
+ for (const [command, ...args] of planned) {
62
+ lines.push(`running: ${command} ${args.join(" ")}`);
63
+ const exitCode = await run(command ?? "", args);
64
+ if (exitCode !== 0) {
65
+ diagnostics.push(diagnostic("SUPA_SYNC_RUNNER_FAILED", "error", `\`${command} ${args.join(" ")}\` exited with code ${exitCode}`, { hint: "The migration runner owns apply/deploy; inspect its output above." }));
66
+ return {
67
+ applied: false,
68
+ diagnostics,
69
+ pending: status.report.pending,
70
+ report: render(lines),
71
+ };
72
+ }
73
+ }
74
+ return { applied: true, diagnostics, pending: status.report.pending, report: render(lines) };
75
+ }
76
+ function run(command, args) {
77
+ return new Promise((resolvePromise) => {
78
+ // Inherit stdio so the runner's own confirmation prompts reach the user.
79
+ const child = spawn(command, args, { stdio: "inherit" });
80
+ child.on("error", () => resolvePromise(127));
81
+ child.on("close", (code) => resolvePromise(code ?? 1));
82
+ });
83
+ }
84
+ function render(lines) {
85
+ return `${lines.join("\n")}\n`;
86
+ }
@@ -0,0 +1,78 @@
1
+ import type { SchemaModel } from "./core.js";
2
+ export interface ColumnShape {
3
+ default?: unknown;
4
+ generated?: unknown;
5
+ identity?: string;
6
+ name: string;
7
+ notNull: boolean;
8
+ type: string;
9
+ }
10
+ export interface RelationshipShape {
11
+ columns: string[];
12
+ foreignKeyName: string;
13
+ isOneToOne: boolean;
14
+ referencedColumns: string[];
15
+ referencedRelation: string;
16
+ referencedSchema: string;
17
+ }
18
+ export interface FunctionShape {
19
+ args: {
20
+ name: string;
21
+ optional: boolean;
22
+ type: string;
23
+ }[];
24
+ name: string;
25
+ returns: {
26
+ setof: boolean;
27
+ type: string;
28
+ } | undefined;
29
+ }
30
+ export interface TableShape {
31
+ columns: ColumnShape[];
32
+ name: string;
33
+ primaryKey?: string[];
34
+ relationships: RelationshipShape[];
35
+ uniqueColumnSets: string[][];
36
+ }
37
+ export interface SchemaEntry {
38
+ composites: {
39
+ columns: ColumnShape[];
40
+ name: string;
41
+ }[];
42
+ enums: {
43
+ name: string;
44
+ values: string[];
45
+ }[];
46
+ functions: FunctionShape[];
47
+ tables: TableShape[];
48
+ views: {
49
+ columns: {
50
+ name: string;
51
+ type: string;
52
+ }[];
53
+ name: string;
54
+ }[];
55
+ }
56
+ export interface SchemaShapes {
57
+ domains: Map<string, string>;
58
+ enumsByBareName: Map<string, {
59
+ name: string;
60
+ schema: string;
61
+ }[]>;
62
+ enumsByQualifiedName: Map<string, {
63
+ name: string;
64
+ schema: string;
65
+ }>;
66
+ schemas: Map<string, SchemaEntry>;
67
+ }
68
+ export interface ResolvedColumnType {
69
+ arrayDepth: number;
70
+ enumRef?: {
71
+ name: string;
72
+ schema: string;
73
+ };
74
+ kind: "boolean" | "enum" | "json" | "number" | "string" | "unknown";
75
+ }
76
+ export declare function collectSchemaShapes(model: SchemaModel): Promise<SchemaShapes>;
77
+ export declare function resolveColumnType(shapes: SchemaShapes, schemaName: string, sqlType: string): ResolvedColumnType;
78
+ //# sourceMappingURL=typegen-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typegen-model.d.ts","sourceRoot":"","sources":["../src/typegen-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAgB,MAAM,WAAW,CAAC;AAM3D,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,gBAAgB,EAAE,MAAM,EAAE,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE;QAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC5C,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,EAAE;QAAE,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtE;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACjE,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;CACrE;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAwEnF;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,kBAAkB,CA8CpB"}
@@ -0,0 +1,338 @@
1
+ import { asRecord, readArray, readString, stringList, typeNameToSql } from "./sql/ast.js";
2
+ import { parseSqlAst } from "./sql/parser.js";
3
+ import { canonicalColumnType, canonicalTableShape } from "./sql/table-shape.js";
4
+ import { collectViewColumns } from "./typegen-views.js";
5
+ export async function collectSchemaShapes(model) {
6
+ const shapes = {
7
+ domains: new Map(),
8
+ enumsByBareName: new Map(),
9
+ enumsByQualifiedName: new Map(),
10
+ schemas: new Map(),
11
+ };
12
+ const schemaOf = (object) => schemaEntry(shapes, object.ref.schema ?? "public");
13
+ for (const object of model.objects) {
14
+ if (object.ref.kind === "enum") {
15
+ const schema = object.ref.schema ?? "public";
16
+ const entry = { name: object.ref.name, schema };
17
+ shapes.enumsByQualifiedName.set(`${schema}.${object.ref.name}`, entry);
18
+ const bare = shapes.enumsByBareName.get(object.ref.name) ?? [];
19
+ bare.push(entry);
20
+ shapes.enumsByBareName.set(object.ref.name, bare);
21
+ const values = Array.isArray(object.metadata.values)
22
+ ? object.metadata.values.map((value) => String(value))
23
+ : [];
24
+ schemaOf(object).enums.push({ name: object.ref.name, values });
25
+ }
26
+ else if (object.ref.kind === "domain") {
27
+ const base = await domainBaseType(object);
28
+ if (base !== undefined) {
29
+ shapes.domains.set(`${object.ref.schema ?? "public"}.${object.ref.name}`, base);
30
+ }
31
+ }
32
+ }
33
+ const tablesByKey = new Map();
34
+ for (const object of model.objects) {
35
+ if (object.ref.kind === "table" || object.ref.kind === "foreign-table") {
36
+ const columns = object.ref.kind === "table" ? tableColumns(object) : await foreignTableColumns(object);
37
+ const table = {
38
+ columns,
39
+ name: object.ref.name,
40
+ relationships: [],
41
+ uniqueColumnSets: [],
42
+ };
43
+ tablesByKey.set(`${object.ref.schema ?? "public"}.${object.ref.name}`, table);
44
+ schemaOf(object).tables.push(table);
45
+ }
46
+ else if (object.ref.kind === "function") {
47
+ const shape = await functionShape(object);
48
+ if (shape) {
49
+ schemaOf(object).functions.push(shape);
50
+ }
51
+ }
52
+ }
53
+ for (const object of model.objects) {
54
+ if (object.ref.kind === "constraint") {
55
+ await applyConstraint(object, tablesByKey);
56
+ }
57
+ }
58
+ resolveRelationshipTargets(tablesByKey);
59
+ for (const object of model.objects) {
60
+ if (object.ref.kind === "view" || object.ref.kind === "materialized-view") {
61
+ schemaOf(object).views.push({
62
+ columns: await collectViewColumns(object, tablesByKey),
63
+ name: object.ref.name,
64
+ });
65
+ }
66
+ else if (object.ref.kind === "type") {
67
+ schemaOf(object).composites.push({
68
+ columns: await compositeColumns(object),
69
+ name: object.ref.name,
70
+ });
71
+ }
72
+ }
73
+ return shapes;
74
+ }
75
+ export function resolveColumnType(shapes, schemaName, sqlType) {
76
+ let base = sqlType.trim();
77
+ let arrayDepth = 0;
78
+ while (base.endsWith("[]")) {
79
+ base = base.slice(0, -2).trim();
80
+ arrayDepth += 1;
81
+ }
82
+ const parenStart = base.indexOf("(");
83
+ if (parenStart !== -1) {
84
+ base = base.slice(0, parenStart).trim();
85
+ }
86
+ for (let hops = 0; hops < 8; hops += 1) {
87
+ const domainBase = shapes.domains.get(base.includes(".") ? base : `${schemaName}.${base}`) ??
88
+ shapes.domains.get(base);
89
+ if (domainBase === undefined) {
90
+ break;
91
+ }
92
+ base = domainBase;
93
+ const innerParen = base.indexOf("(");
94
+ if (innerParen !== -1) {
95
+ base = base.slice(0, innerParen).trim();
96
+ }
97
+ while (base.endsWith("[]")) {
98
+ base = base.slice(0, -2).trim();
99
+ arrayDepth += 1;
100
+ }
101
+ }
102
+ const lowered = base.toLowerCase();
103
+ if (numberTypes.has(lowered)) {
104
+ return { arrayDepth, kind: "number" };
105
+ }
106
+ if (stringTypes.has(lowered)) {
107
+ return { arrayDepth, kind: "string" };
108
+ }
109
+ if (lowered === "boolean" || lowered === "bool") {
110
+ return { arrayDepth, kind: "boolean" };
111
+ }
112
+ if (lowered === "json" || lowered === "jsonb") {
113
+ return { arrayDepth, kind: "json" };
114
+ }
115
+ const enumRef = resolveEnum(shapes, schemaName, base);
116
+ if (enumRef) {
117
+ return { arrayDepth, enumRef, kind: "enum" };
118
+ }
119
+ return { arrayDepth, kind: "unknown" };
120
+ }
121
+ function resolveEnum(shapes, schemaName, base) {
122
+ if (base.includes(".")) {
123
+ return shapes.enumsByQualifiedName.get(base);
124
+ }
125
+ const local = shapes.enumsByQualifiedName.get(`${schemaName}.${base}`);
126
+ if (local) {
127
+ return local;
128
+ }
129
+ const candidates = shapes.enumsByBareName.get(base);
130
+ return candidates && candidates.length === 1 ? candidates[0] : undefined;
131
+ }
132
+ function schemaEntry(shapes, name) {
133
+ let entry = shapes.schemas.get(name);
134
+ if (!entry) {
135
+ entry = { composites: [], enums: [], functions: [], tables: [], views: [] };
136
+ shapes.schemas.set(name, entry);
137
+ }
138
+ return entry;
139
+ }
140
+ function tableColumns(object) {
141
+ const shape = asRecord(object.metadata.canonicalShape);
142
+ return readArray(shape?.columns).flatMap((item) => {
143
+ const column = asRecord(item);
144
+ const name = readString(column?.name);
145
+ const type = readString(column?.type);
146
+ if (!(column && name && type)) {
147
+ return [];
148
+ }
149
+ return [
150
+ {
151
+ name,
152
+ notNull: column.notNull === true,
153
+ type,
154
+ ...(column.default !== undefined ? { default: column.default } : {}),
155
+ ...(column.generated !== undefined ? { generated: column.generated } : {}),
156
+ ...(typeof column.identity === "string" ? { identity: column.identity } : {}),
157
+ },
158
+ ];
159
+ });
160
+ }
161
+ async function foreignTableColumns(object) {
162
+ const parsed = await parseSqlAst(object.sql, object.file);
163
+ const statements = readArray(asRecord(parsed.ast)?.stmts);
164
+ const stmt = asRecord(asRecord(statements[0])?.stmt);
165
+ const base = asRecord(asRecord(stmt?.CreateForeignTableStmt)?.base);
166
+ if (!base) {
167
+ return [];
168
+ }
169
+ const shape = canonicalTableShape(base);
170
+ return readArray(shape.columns).flatMap((item) => {
171
+ const column = asRecord(item);
172
+ const name = readString(column?.name);
173
+ const type = readString(column?.type);
174
+ return column && name && type ? [{ name, notNull: column.notNull === true, type }] : [];
175
+ });
176
+ }
177
+ async function compositeColumns(object) {
178
+ const parsed = await parseSqlAst(object.sql, object.file);
179
+ const statements = readArray(asRecord(parsed.ast)?.stmts);
180
+ const composite = asRecord(asRecord(asRecord(statements[0])?.stmt)?.CompositeTypeStmt);
181
+ return readArray(composite?.coldeflist).flatMap((item) => {
182
+ const columnDef = asRecord(asRecord(item)?.ColumnDef);
183
+ const name = readString(columnDef?.colname);
184
+ if (!(columnDef && name)) {
185
+ return [];
186
+ }
187
+ return [{ name, notNull: false, type: canonicalColumnType(columnDef.typeName) }];
188
+ });
189
+ }
190
+ async function domainBaseType(object) {
191
+ const parsed = await parseSqlAst(object.sql, object.file);
192
+ const statements = readArray(asRecord(parsed.ast)?.stmts);
193
+ const domain = asRecord(asRecord(asRecord(statements[0])?.stmt)?.CreateDomainStmt);
194
+ return domain ? canonicalColumnType(domain.typeName) : undefined;
195
+ }
196
+ async function functionShape(object) {
197
+ const parsed = await parseSqlAst(object.sql, object.file);
198
+ const statements = readArray(asRecord(parsed.ast)?.stmts);
199
+ const fn = asRecord(asRecord(asRecord(statements[0])?.stmt)?.CreateFunctionStmt);
200
+ if (!fn) {
201
+ return undefined;
202
+ }
203
+ const args = [];
204
+ for (const item of readArray(fn.parameters)) {
205
+ const parameter = asRecord(asRecord(item)?.FunctionParameter);
206
+ if (!parameter) {
207
+ continue;
208
+ }
209
+ const mode = readString(parameter.mode) ?? "FUNC_PARAM_DEFAULT";
210
+ if (mode !== "FUNC_PARAM_DEFAULT" && mode !== "FUNC_PARAM_IN" && mode !== "FUNC_PARAM_INOUT") {
211
+ continue;
212
+ }
213
+ const name = readString(parameter.name);
214
+ if (!name) {
215
+ return undefined;
216
+ }
217
+ args.push({
218
+ name,
219
+ optional: parameter.defexpr !== undefined,
220
+ type: typeNameToSql(parameter.argType),
221
+ });
222
+ }
223
+ const returns = asRecord(object.metadata.returns);
224
+ return {
225
+ args,
226
+ name: object.ref.name,
227
+ returns: returns && typeof returns.type === "string"
228
+ ? { setof: returns.setof === true, type: returns.type }
229
+ : undefined,
230
+ };
231
+ }
232
+ async function applyConstraint(object, tablesByKey) {
233
+ const parsed = await parseSqlAst(object.sql, object.file);
234
+ const statements = readArray(asRecord(parsed.ast)?.stmts);
235
+ const alter = asRecord(asRecord(asRecord(statements[0])?.stmt)?.AlterTableStmt);
236
+ const schemaName = object.ref.schema ?? "public";
237
+ const table = tablesByKey.get(`${schemaName}.${object.ref.table ?? ""}`);
238
+ if (!table) {
239
+ return;
240
+ }
241
+ for (const command of readArray(alter?.cmds)) {
242
+ const constraint = asRecord(asRecord(asRecord(asRecord(command)?.AlterTableCmd)?.def)?.Constraint);
243
+ const contype = readString(constraint?.contype);
244
+ if (!constraint || !contype) {
245
+ continue;
246
+ }
247
+ if (contype === "CONSTR_PRIMARY") {
248
+ const keys = stringList(constraint.keys);
249
+ for (const column of table.columns) {
250
+ if (keys.includes(column.name)) {
251
+ column.notNull = true;
252
+ }
253
+ }
254
+ table.primaryKey = keys;
255
+ table.uniqueColumnSets.push(keys);
256
+ }
257
+ else if (contype === "CONSTR_UNIQUE") {
258
+ table.uniqueColumnSets.push(stringList(constraint.keys));
259
+ }
260
+ else if (contype === "CONSTR_FOREIGN") {
261
+ const pkTable = asRecord(constraint.pktable);
262
+ if (!pkTable) {
263
+ continue;
264
+ }
265
+ table.relationships.push({
266
+ columns: stringList(constraint.fk_attrs),
267
+ foreignKeyName: readString(constraint.conname) ?? object.ref.name,
268
+ isOneToOne: false,
269
+ referencedColumns: stringList(constraint.pk_attrs),
270
+ referencedRelation: readString(pkTable.relname) ?? "",
271
+ referencedSchema: readString(pkTable.schemaname) ?? schemaName,
272
+ });
273
+ }
274
+ }
275
+ }
276
+ function resolveRelationshipTargets(tablesByKey) {
277
+ const primaryKeys = new Map();
278
+ for (const [key, table] of tablesByKey) {
279
+ if (table.primaryKey) {
280
+ primaryKeys.set(key, table.primaryKey);
281
+ }
282
+ }
283
+ for (const table of tablesByKey.values()) {
284
+ for (const relationship of table.relationships) {
285
+ if (relationship.referencedColumns.length === 0) {
286
+ const target = primaryKeys.get(`${relationship.referencedSchema}.${relationship.referencedRelation}`);
287
+ if (target) {
288
+ relationship.referencedColumns = [...target];
289
+ }
290
+ }
291
+ relationship.isOneToOne = table.uniqueColumnSets.some((set) => set.length === relationship.columns.length &&
292
+ relationship.columns.every((column) => set.includes(column)));
293
+ }
294
+ }
295
+ }
296
+ const numberTypes = new Set([
297
+ "smallint",
298
+ "int2",
299
+ "integer",
300
+ "int",
301
+ "int4",
302
+ "bigint",
303
+ "int8",
304
+ "real",
305
+ "float4",
306
+ "double precision",
307
+ "float8",
308
+ "numeric",
309
+ "decimal",
310
+ "oid",
311
+ ]);
312
+ const stringTypes = new Set([
313
+ "text",
314
+ "character varying",
315
+ "varchar",
316
+ "character",
317
+ "char",
318
+ '"char"',
319
+ "bpchar",
320
+ "uuid",
321
+ "citext",
322
+ "name",
323
+ "bytea",
324
+ "inet",
325
+ "cidr",
326
+ "macaddr",
327
+ "interval",
328
+ "date",
329
+ "time",
330
+ "timetz",
331
+ "time with time zone",
332
+ "time without time zone",
333
+ "timestamp",
334
+ "timestamptz",
335
+ "timestamp with time zone",
336
+ "timestamp without time zone",
337
+ "xml",
338
+ ]);
@@ -0,0 +1,7 @@
1
+ import type { SchemaObject } from "./core.js";
2
+ import type { TableShape } from "./typegen-model.js";
3
+ export declare function collectViewColumns(object: SchemaObject, tablesByKey: Map<string, TableShape>): Promise<{
4
+ name: string;
5
+ type: string;
6
+ }[]>;
7
+ //# sourceMappingURL=typegen-views.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typegen-views.d.ts","sourceRoot":"","sources":["../src/typegen-views.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,oBAAoB,CAAC;AASlE,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GACnC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAiB3C"}
@@ -0,0 +1,92 @@
1
+ import { asRecord, readArray, readString, stringList } from "./sql/ast.js";
2
+ import { parseSqlAst } from "./sql/parser.js";
3
+ export async function collectViewColumns(object, tablesByKey) {
4
+ const aliasNames = Array.isArray(object.metadata.viewColumns)
5
+ ? object.metadata.viewColumns.map((value) => String(value))
6
+ : undefined;
7
+ const parsed = await parseSqlAst(object.sql, object.file);
8
+ const select = firstSelect(parsed.ast);
9
+ const fromInfo = soleFromTable(select, object.ref.schema ?? "public", tablesByKey);
10
+ const targets = readArray(select?.targetList).map((target) => parseTarget(target));
11
+ const expanded = expandTargets(targets, fromInfo);
12
+ if (aliasNames) {
13
+ return aliasNames.map((name, index) => ({
14
+ name,
15
+ type: aliasNames.length === expanded.length ? (expanded[index]?.type ?? "unknown") : "unknown",
16
+ }));
17
+ }
18
+ return expanded;
19
+ }
20
+ function expandTargets(targets, fromInfo) {
21
+ const output = [];
22
+ for (const target of targets) {
23
+ if (target.isStar) {
24
+ const coversFrom = fromInfo &&
25
+ (target.starQualifier === undefined || fromInfo.names.has(target.starQualifier));
26
+ if (coversFrom && fromInfo) {
27
+ for (const column of fromInfo.columns) {
28
+ output.push({ name: column.name, type: column.type });
29
+ }
30
+ }
31
+ continue;
32
+ }
33
+ const name = target.alias ?? target.sourceColumn;
34
+ if (!name) {
35
+ continue;
36
+ }
37
+ const match = target.sourceColumn === undefined
38
+ ? undefined
39
+ : fromInfo?.columns.find((column) => column.name === target.sourceColumn);
40
+ output.push({ name, type: match?.type ?? "unknown" });
41
+ }
42
+ return output;
43
+ }
44
+ function parseTarget(target) {
45
+ const resTarget = asRecord(asRecord(target)?.ResTarget);
46
+ const columnRef = asRecord(asRecord(resTarget?.val)?.ColumnRef);
47
+ const fields = readArray(columnRef?.fields);
48
+ const lastField = fields.at(-1);
49
+ const isStar = asRecord(lastField)?.A_Star !== undefined;
50
+ const lastName = stringList(columnRef?.fields).at(-1);
51
+ const alias = readString(resTarget?.name);
52
+ if (isStar) {
53
+ return {
54
+ isStar: true,
55
+ ...(lastName !== undefined && fields.length > 1 ? { starQualifier: lastName } : {}),
56
+ };
57
+ }
58
+ return {
59
+ isStar: false,
60
+ ...(alias !== undefined ? { alias } : {}),
61
+ ...(lastName !== undefined ? { sourceColumn: lastName } : {}),
62
+ };
63
+ }
64
+ function firstSelect(ast) {
65
+ const statements = readArray(asRecord(ast)?.stmts);
66
+ const stmt = asRecord(asRecord(statements[0])?.stmt);
67
+ const view = asRecord(stmt?.ViewStmt);
68
+ const tableAs = asRecord(stmt?.CreateTableAsStmt);
69
+ return asRecord(asRecord(view?.query ?? tableAs?.query)?.SelectStmt);
70
+ }
71
+ function soleFromTable(select, defaultSchema, tablesByKey) {
72
+ const fromClause = readArray(select?.fromClause);
73
+ if (fromClause.length !== 1) {
74
+ return undefined;
75
+ }
76
+ const rangeVar = asRecord(asRecord(fromClause[0])?.RangeVar);
77
+ const relname = readString(rangeVar?.relname);
78
+ if (!(rangeVar && relname)) {
79
+ return undefined;
80
+ }
81
+ const schemaName = readString(rangeVar?.schemaname) ?? defaultSchema;
82
+ const table = tablesByKey.get(`${schemaName}.${relname}`);
83
+ if (!table) {
84
+ return undefined;
85
+ }
86
+ const aliasName = asRecord(rangeVar.alias)?.aliasname;
87
+ const names = new Set([relname]);
88
+ if (typeof aliasName === "string") {
89
+ names.add(aliasName);
90
+ }
91
+ return { columns: table.columns, names };
92
+ }
@@ -0,0 +1,3 @@
1
+ import type { SchemaModel } from "./core.js";
2
+ export declare function generateZodSchemas(model: SchemaModel): Promise<string>;
3
+ //# sourceMappingURL=typegen-zod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typegen-zod.d.ts","sourceRoot":"","sources":["../src/typegen-zod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAK7C,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA6F5E"}