@rebasepro/server-postgres 0.24.0 → 0.24.1-canary.g19596ef

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 (193) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-BRt78gfa.js → BranchService-UgKa416x.js} +14 -3
  3. package/dist/BranchService-UgKa416x.js.map +1 -0
  4. package/dist/PostgresBackendDriver.d.ts +104 -2
  5. package/dist/PostgresBootstrapper.d.ts +101 -2
  6. package/dist/auth/ensure-tables.d.ts +12 -0
  7. package/dist/auth/services.d.ts +6 -0
  8. package/dist/backup/backup-cli.d.ts +75 -2
  9. package/dist/backup/backup-service.d.ts +2 -2
  10. package/dist/backup/pg-binary.d.ts +5 -0
  11. package/dist/{backup-cli-DW5p9_zv.js → backup-cli-yiKd1GsC.js} +168 -75
  12. package/dist/backup-cli-yiKd1GsC.js.map +1 -0
  13. package/dist/{backup-service-EwcDVG-8.js → backup-service-AZWc2wvI.js} +99 -28
  14. package/dist/backup-service-AZWc2wvI.js.map +1 -0
  15. package/dist/boot-ddl-B9ZmBJ75.js +105 -0
  16. package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
  17. package/dist/cli-console-DGee3Zl7.js +225 -0
  18. package/dist/cli-console-DGee3Zl7.js.map +1 -0
  19. package/dist/cli-console.d.ts +39 -0
  20. package/dist/cli-errors-oVUuD8k0.js +2180 -0
  21. package/dist/cli-errors-oVUuD8k0.js.map +1 -0
  22. package/dist/cli-errors.d.ts +91 -9
  23. package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
  24. package/dist/cli-flags-DnPHXjRH.js.map +1 -0
  25. package/dist/cli-flags.d.ts +15 -0
  26. package/dist/cli-helpers.d.ts +76 -22
  27. package/dist/cli-output.d.ts +16 -28
  28. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  29. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  30. package/dist/cli-prompt.d.ts +19 -0
  31. package/dist/cli-push.d.ts +87 -0
  32. package/dist/cli-scratch-database.d.ts +33 -8
  33. package/dist/cli.js +764 -222
  34. package/dist/cli.js.map +1 -1
  35. package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-D_V4oaZh.js} +11 -5
  36. package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-D_V4oaZh.js.map} +1 -1
  37. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
  38. package/dist/connection-C938nR3E.js.map +1 -0
  39. package/dist/connection.d.ts +13 -2
  40. package/dist/databasePoolManager-DZgnBOgj.js +119 -0
  41. package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
  42. package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-BqmEHGaD.js} +485 -347
  43. package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
  44. package/dist/{doctor-C-sYWbmt.js → doctor-CUAAOFZm.js} +65 -54
  45. package/dist/doctor-CUAAOFZm.js.map +1 -0
  46. package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
  47. package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
  48. package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
  49. package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
  50. package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-DMPWhJ6R.js} +174 -78
  51. package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
  52. package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
  53. package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
  54. package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
  55. package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
  56. package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +6 -6
  57. package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
  58. package/dist/generate-files-OoRJLEdk.js +199 -0
  59. package/dist/generate-files-OoRJLEdk.js.map +1 -0
  60. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B4k9djAV.js} +4 -4
  61. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B4k9djAV.js.map} +1 -1
  62. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
  63. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
  64. package/dist/generated-sql.d.ts +8 -2
  65. package/dist/history/HistoryService.d.ts +64 -5
  66. package/dist/history/ensure-history-table.d.ts +63 -4
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.es.js +139 -19236
  69. package/dist/index.es.js.map +1 -1
  70. package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-BCc1Jym2.js} +136 -9
  71. package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
  72. package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-BhhJg0b8.js} +48 -1
  73. package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
  74. package/dist/open-rebase-CAjWpPjs.js +20895 -0
  75. package/dist/open-rebase-CAjWpPjs.js.map +1 -0
  76. package/dist/open-rebase.d.ts +94 -0
  77. package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
  78. package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
  79. package/dist/{plan-schema-DU9exq6C.js → plan-schema-CsNgK0qi.js} +152 -42
  80. package/dist/plan-schema-CsNgK0qi.js.map +1 -0
  81. package/dist/{policy-drift-B-J2hhm0.js → policy-drift-BKC50y2p.js} +32 -8
  82. package/dist/policy-drift-BKC50y2p.js.map +1 -0
  83. package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
  84. package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
  85. package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
  86. package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
  87. package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CrfEe__B.js} +15 -10
  88. package/dist/render-ddl-CrfEe__B.js.map +1 -0
  89. package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1325
  90. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  91. package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-D7P4-ZYX.js} +130 -18
  92. package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
  93. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  94. package/dist/schema/atlas-exclude.d.ts +42 -0
  95. package/dist/schema/auth-schema.d.ts +2 -2
  96. package/dist/schema/boot-ddl.d.ts +47 -0
  97. package/dist/schema/carved-out-migration.d.ts +5 -4
  98. package/dist/schema/collection-index.d.ts +45 -0
  99. package/dist/schema/column-plan-helpers.d.ts +5 -0
  100. package/dist/schema/deferred-index-build.d.ts +131 -0
  101. package/dist/schema/destructive-sql.d.ts +30 -1
  102. package/dist/schema/doctor-cli.js +110 -6
  103. package/dist/schema/doctor-cli.js.map +1 -1
  104. package/dist/schema/doctor-search-scale.d.ts +45 -0
  105. package/dist/schema/doctor.d.ts +20 -1
  106. package/dist/schema/ensure-collection-policies.d.ts +73 -35
  107. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  108. package/dist/schema/enum-labels-kept.d.ts +26 -0
  109. package/dist/schema/generate-drizzle-schema.js +1 -1
  110. package/dist/schema/generate-files.d.ts +44 -0
  111. package/dist/schema/generate-next-step.d.ts +0 -13
  112. package/dist/schema/generate-postgres-ddl.js +10 -81
  113. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  114. package/dist/schema/generated-schema-diff.d.ts +7 -0
  115. package/dist/schema/introspect-db-logic.d.ts +32 -0
  116. package/dist/schema/introspect-db-queries.d.ts +20 -1
  117. package/dist/schema/introspect-db-structure.d.ts +10 -0
  118. package/dist/schema/introspect-db.js +16 -20
  119. package/dist/schema/introspect-db.js.map +1 -1
  120. package/dist/schema/migrate-baseline.d.ts +33 -0
  121. package/dist/schema/migration-post-process.d.ts +34 -0
  122. package/dist/schema/plan/diff-plan.d.ts +126 -0
  123. package/dist/schema/plan/render-ddl.d.ts +2 -1
  124. package/dist/schema/plan/types.d.ts +8 -0
  125. package/dist/schema/push-index-prebuild.d.ts +25 -0
  126. package/dist/schema/push-legacy-renames.d.ts +20 -0
  127. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  128. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  129. package/dist/security/rls-enforcement.d.ts +32 -4
  130. package/dist/services/BranchService.d.ts +7 -0
  131. package/dist/services/FetchService.d.ts +58 -8
  132. package/dist/services/PersistService.d.ts +15 -0
  133. package/dist/services/RelationService.d.ts +88 -3
  134. package/dist/services/cdc/CdcListener.d.ts +17 -6
  135. package/dist/services/cdc/trigger-cdc.d.ts +151 -27
  136. package/dist/services/channel-bus/index.d.ts +1 -1
  137. package/dist/services/channel-history.d.ts +15 -4
  138. package/dist/services/collection-helpers.d.ts +15 -0
  139. package/dist/services/field-viewer.d.ts +15 -1
  140. package/dist/services/pg-notify-listener.d.ts +7 -0
  141. package/dist/services/realtimeService.d.ts +314 -14
  142. package/dist/services/row-pipeline.d.ts +16 -9
  143. package/dist/services/socket-liveness.d.ts +42 -2
  144. package/dist/services/sql-script.d.ts +207 -3
  145. package/dist/services/vector-iterative-scan.d.ts +40 -0
  146. package/dist/services/write-depth.d.ts +6 -0
  147. package/dist/services/write-transaction-scope.d.ts +9 -0
  148. package/dist/socket-opt-ins.d.ts +64 -0
  149. package/dist/source-DB6xJv3p.js +873 -0
  150. package/dist/source-DB6xJv3p.js.map +1 -0
  151. package/dist/tsx-loader.d.ts +14 -0
  152. package/dist/utils/connection-string.d.ts +31 -0
  153. package/dist/utils/drizzle-conditions.d.ts +21 -1
  154. package/dist/utils/error-cause.d.ts +7 -0
  155. package/dist/utils/pg-error-utils.d.ts +2 -4
  156. package/dist/utils/result-rows.d.ts +10 -0
  157. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  158. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  159. package/dist/websocket-DUiiwQd2.js +1632 -0
  160. package/dist/websocket-DUiiwQd2.js.map +1 -0
  161. package/dist/websocket.d.ts +13 -0
  162. package/package.json +7 -7
  163. package/dist/BranchService-BRt78gfa.js.map +0 -1
  164. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  165. package/dist/arg-Dni7MzLB.js.map +0 -1
  166. package/dist/backup-cli-DW5p9_zv.js.map +0 -1
  167. package/dist/backup-service-EwcDVG-8.js.map +0 -1
  168. package/dist/cli-errors-DsA-K9uP.js +0 -893
  169. package/dist/cli-errors-DsA-K9uP.js.map +0 -1
  170. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  171. package/dist/cli-output-CNdMql-L.js +0 -47
  172. package/dist/cli-output-CNdMql-L.js.map +0 -1
  173. package/dist/collection-index-DdnsxiJ_.js +0 -286
  174. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  175. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  176. package/dist/doctor-C-sYWbmt.js.map +0 -1
  177. package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
  178. package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
  179. package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
  180. package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
  181. package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
  182. package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
  183. package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
  184. package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
  185. package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
  186. package/dist/plan-schema-DU9exq6C.js.map +0 -1
  187. package/dist/policy-drift-B-J2hhm0.js.map +0 -1
  188. package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
  189. package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
  190. package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
  191. package/dist/source-Br7L7GOI.js +0 -410
  192. package/dist/source-Br7L7GOI.js.map +0 -1
  193. package/dist/utils/sql-redaction.d.ts +0 -21
@@ -1 +1 @@
1
- {"version":3,"file":"introspect-db.js","names":[],"sources":["../../src/schema/introspect-db-project.ts","../../src/schema/introspect-db-constraints.ts","../../src/schema/introspect-db.ts"],"sourcesContent":["/**\n * What the project being introspected into can actually import.\n *\n * The generator is pure — `introspect-db-logic.ts` reads no files — so the one\n * question it cannot answer for itself lives here: which `defineCollection`, if\n * any, will resolve from the directory the collections are written to.\n */\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { ADMIN_TYPES_PACKAGE, COMMON_PACKAGE, type CollectionBuilder } from \"./introspect-db-logic\";\n\n/** How far up to look before giving up, if no project root announces itself. */\nconst MAX_LEVELS = 8;\n\n/** Every dependency name a manifest declares, in any of the three fields. */\nfunction declaredDependencies(manifestPath: string): Set<string> {\n const names = new Set<string>();\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(manifestPath, \"utf-8\"));\n } catch {\n // A malformed or unreadable package.json is not this function's problem\n // to report — it answers \"can this import resolve\", and the answer is no.\n return names;\n }\n if (typeof parsed !== \"object\" || parsed === null) return names;\n const manifest = parsed as Record<string, unknown>;\n for (const field of [\"dependencies\", \"devDependencies\", \"peerDependencies\"]) {\n const block = manifest[field];\n if (typeof block === \"object\" && block !== null) {\n for (const name of Object.keys(block)) names.add(name);\n }\n }\n return names;\n}\n\n/**\n * Which `defineCollection` to generate against, for collections written to `outDir`.\n *\n * **The detection is the package manifests above the output directory**, unioned from\n * `outDir` up to and including the project root — the first ancestor holding a\n * `rebase.json`, or `MAX_LEVELS` up if there is none.\n *\n * That is the rule Node itself applies: a file in `config/collections` resolves a bare\n * specifier through `config/node_modules`, then `<project>/node_modules`, and so on up.\n * Reading the manifests along that same path answers the only question that matters —\n * *will this import resolve in the project I am writing into* — from the state on disk\n * at the moment of generation. The alternatives are all proxies for it: `rebase.json`'s\n * `apps` block says a CMS scaffold declared an admin app, and a `frontend/` directory\n * says one was scaffolded, but neither is what the compiler consults, and either can be\n * true of a project whose `config` package does not depend on `@rebasepro/cms-types`.\n *\n * Ambiguity resolves towards the admin panel: a project that declares both packages has\n * a panel, and `@rebasepro/cms-types` is the flavour that keeps the `admin` block.\n */\nexport function detectCollectionBuilder(outDir: string): CollectionBuilder {\n let dir = path.resolve(outDir);\n const declared = new Set<string>();\n\n for (let level = 0; level < MAX_LEVELS; level++) {\n const manifest = path.join(dir, \"package.json\");\n if (fs.existsSync(manifest)) {\n for (const name of declaredDependencies(manifest)) declared.add(name);\n }\n // The project root, and the last level worth reading: anything above it\n // belongs to whatever the project happens to be checked out inside.\n if (fs.existsSync(path.join(dir, \"rebase.json\"))) break;\n\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n if (declared.has(ADMIN_TYPES_PACKAGE)) return \"admin-types\";\n if (declared.has(COMMON_PACKAGE)) return \"common\";\n return \"annotation\";\n}\n","/**\n * Reading validation rules out of CHECK constraints.\n *\n * A CHECK constraint is the schema author stating a rule the database already\n * enforces. Introspection has never read them, so a generated form let the user\n * type a value the database was always going to reject — the rule was written\n * down, in the schema, and the UI asked anyway.\n *\n * Everything here parses the *normalized* text `pg_get_constraintdef` produces,\n * not what the author typed: Postgres re-renders the expression from its parse\n * tree, so `CHECK (price > 0)` on a `numeric` column always comes back as\n * `CHECK ((price > (0)::numeric))`. That normalization is what makes a\n * string-level parser tractable — the input is generated, and the shapes are\n * few.\n *\n * Constraints this cannot read are skipped in full. A partially-understood\n * constraint is worse than an unread one: it would produce validation that\n * *narrows* differently from the database, so a value the UI accepts still\n * fails on write, or — worse — a value the database allows is refused in the\n * form with no way to see why.\n *\n * Pure module: no I/O, no logging.\n */\nimport type { CheckConstraintRow } from \"./introspect-db-logic\";\n\n/** What a table's CHECK constraints say about one column. */\nexport interface ColumnCheckFacts {\n /** Allowed values, from `IN (…)` / `= ANY (ARRAY[…])` / `= 'literal'`. */\n enumValues?: string[];\n /** `x >= n` */\n min?: number;\n /** `x <= n` */\n max?: number;\n /** `x > n` */\n moreThan?: number;\n /** `x < n` */\n lessThan?: number;\n /** `length(x) >= n` */\n lengthMin?: number;\n /** `length(x) <= n` */\n lengthMax?: number;\n}\n\n/** Per-table, per-column facts. */\nexport type CheckFactsByTable = Map<string, Map<string, ColumnCheckFacts>>;\n\n/** The length functions whose argument is the column being constrained. */\nconst LENGTH_FUNCTIONS = new Set([\"length\", \"char_length\", \"character_length\", \"octet_length\"]);\n\n/**\n * Splits on a separator that appears at paren depth 0 and outside string\n * literals.\n *\n * Depth tracking is the whole job: `ARRAY['a', 'b']` contains a comma that must\n * not split the enclosing argument list, and `'it''s'` contains a quote that\n * must not end the literal.\n */\nfunction splitTopLevel(input: string, separator: string): string[] {\n const parts: string[] = [];\n let depth = 0;\n let inString = false;\n let current = \"\";\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n\n if (inString) {\n current += ch;\n if (ch === \"'\") {\n // '' inside a literal is an escaped quote, not the end of it.\n if (input[i + 1] === \"'\") {\n current += input[++i];\n } else {\n inString = false;\n }\n }\n continue;\n }\n\n if (ch === \"'\") {\n inString = true;\n current += ch;\n continue;\n }\n if (ch === \"(\" || ch === \"[\") depth++;\n if (ch === \")\" || ch === \"]\") depth--;\n\n if (depth === 0 && input.startsWith(separator, i)) {\n // A word separator must not match inside an identifier: \"AND\" is a\n // separator in `a AND b` but not in `brand = 1`.\n const before = input[i - 1];\n const after = input[i + separator.length];\n const wordish = /[A-Za-z]/.test(separator[0]);\n const boundedOk = !wordish || ((before === undefined || !/[A-Za-z0-9_]/.test(before)) &&\n (after === undefined || !/[A-Za-z0-9_]/.test(after)));\n if (boundedOk) {\n parts.push(current);\n current = \"\";\n i += separator.length - 1;\n continue;\n }\n }\n\n current += ch;\n }\n\n parts.push(current);\n return parts.map((p) => p.trim()).filter((p) => p.length > 0);\n}\n\n/** Removes one layer of wrapping parentheses, repeatedly, when balanced. */\nexport function unwrapParens(input: string): string {\n let s = input.trim();\n while (s.startsWith(\"(\") && s.endsWith(\")\")) {\n let depth = 0;\n let balanced = true;\n let inString = false;\n for (let i = 0; i < s.length; i++) {\n const ch = s[i];\n if (inString) {\n if (ch === \"'\") {\n if (s[i + 1] === \"'\") i++;\n else inString = false;\n }\n continue;\n }\n if (ch === \"'\") { inString = true; continue; }\n if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n // The opening paren closed before the end, so the outer pair is\n // not a wrapper: `(a) AND (b)`.\n if (depth === 0 && i < s.length - 1) { balanced = false; break; }\n }\n }\n if (!balanced) break;\n s = s.slice(1, -1).trim();\n }\n return s;\n}\n\n/** Drops trailing `::type` casts, including array and quoted forms. */\nexport function stripCasts(input: string): string {\n let s = unwrapParens(input);\n let previous: string;\n do {\n previous = s;\n s = unwrapParens(\n s.replace(/::\\s*\"?[A-Za-z_][A-Za-z0-9_ ]*\"?(\\s*\\(\\s*\\d+\\s*(,\\s*\\d+\\s*)?\\))?(\\s*\\[\\s*\\])*\\s*$/, \"\").trim()\n );\n } while (s !== previous);\n return s;\n}\n\n/** A bare, unquoted column reference — anything else is an expression. */\nfunction asColumnName(input: string): string | null {\n const s = stripCasts(input);\n if (/^[a-z_][a-z0-9_$]*$/i.test(s)) return s;\n // A quoted identifier keeps its case: \"userId\".\n const quoted = s.match(/^\"([^\"]+)\"$/);\n return quoted ? quoted[1] : null;\n}\n\n/** `length(col)` and friends → the column they measure. */\nfunction asLengthOfColumn(input: string): string | null {\n const s = stripCasts(input);\n const match = s.match(/^([a-z_]+)\\s*\\((.*)\\)$/is);\n if (!match || !LENGTH_FUNCTIONS.has(match[1].toLowerCase())) return null;\n return asColumnName(match[2]);\n}\n\n/** A numeric literal, after casts are removed. Rejects anything else. */\nfunction asNumber(input: string): number | null {\n const s = stripCasts(input).replace(/^'(.*)'$/s, \"$1\");\n if (!/^[+-]?(\\d+\\.?\\d*|\\.\\d+)$/.test(s)) return null;\n const n = Number(s);\n return Number.isFinite(n) ? n : null;\n}\n\n/** A single-quoted string literal, with `''` unescaped. */\nfunction asStringLiteral(input: string): string | null {\n const s = stripCasts(input);\n if (!s.startsWith(\"'\") || !s.endsWith(\"'\") || s.length < 2) return null;\n const body = s.slice(1, -1);\n // Any unescaped quote inside means this was not one literal.\n if (/(^|[^'])'($|[^'])/.test(body)) return null;\n return body.replace(/''/g, \"'\");\n}\n\n/** The element list of `ARRAY[…]`, or of a parenthesised `IN (…)` list. */\nfunction asValueList(input: string): string[] | null {\n const s = stripCasts(input);\n const arrayMatch = s.match(/^ARRAY\\s*\\[(.*)\\]$/is);\n const body = arrayMatch ? arrayMatch[1] : (s.startsWith(\"(\") && s.endsWith(\")\") ? s.slice(1, -1) : null);\n if (body === null) return null;\n if (body.trim() === \"\") return null;\n\n const values: string[] = [];\n for (const element of splitTopLevel(body, \",\")) {\n const literal = asStringLiteral(element);\n if (literal !== null) {\n values.push(literal);\n continue;\n }\n const numeric = asNumber(element);\n if (numeric !== null) {\n values.push(String(numeric));\n continue;\n }\n return null; // A non-literal element means this is not an enumeration.\n }\n return values;\n}\n\n/** One understood comparison, already resolved to the column it constrains. */\ninterface CheckTerm {\n column: string;\n apply: (facts: ColumnCheckFacts) => void;\n}\n\n/**\n * Parses one conjunct. Returns null when the shape is not one of the few this\n * understands, which is the signal to abandon the whole constraint.\n */\nfunction parseTerm(rawTerm: string): CheckTerm | null {\n const term = unwrapParens(rawTerm);\n\n // ── Membership: `x = ANY (ARRAY[…])`, `x IN (…)` ──────────────────\n const anyMatch = term.match(/^(.*?)\\s*=\\s*ANY\\s*(\\(.*\\))$/is);\n const inMatch = anyMatch ? null : term.match(/^(.*?)\\s+IN\\s*(\\(.*\\))$/is);\n const membership = anyMatch ?? inMatch;\n if (membership) {\n const column = asColumnName(membership[1]);\n const values = asValueList(membership[2]);\n if (!column || !values || values.length === 0) return null;\n return {\n column,\n apply: (facts) => {\n // Two membership checks on one column mean both must hold.\n facts.enumValues = facts.enumValues\n ? facts.enumValues.filter((v) => values.includes(v))\n : values;\n }\n };\n }\n\n // ── Comparison: `x >= n`, `length(x) <= n`, `x = 'literal'` ───────\n // Longest operators first so `>=` is not read as `>`.\n for (const operator of [\">=\", \"<=\", \"<>\", \"!=\", \"=\", \">\", \"<\"] as const) {\n const parts = splitTopLevel(term, operator);\n if (parts.length !== 2) continue;\n const [rawLeft, rawRight] = parts;\n\n const lengthColumn = asLengthOfColumn(rawLeft);\n if (lengthColumn) {\n const bound = asNumber(rawRight);\n if (bound === null) return null;\n switch (operator) {\n case \">=\": return { column: lengthColumn, apply: (f) => { f.lengthMin = Math.max(f.lengthMin ?? bound, bound); } };\n case \">\": return { column: lengthColumn, apply: (f) => { f.lengthMin = Math.max(f.lengthMin ?? bound + 1, bound + 1); } };\n case \"<=\": return { column: lengthColumn, apply: (f) => { f.lengthMax = Math.min(f.lengthMax ?? bound, bound); } };\n case \"<\": return { column: lengthColumn, apply: (f) => { f.lengthMax = Math.min(f.lengthMax ?? bound - 1, bound - 1); } };\n case \"=\": return { column: lengthColumn, apply: (f) => { f.lengthMin = bound; f.lengthMax = bound; } };\n default: return null;\n }\n }\n\n const column = asColumnName(rawLeft);\n if (!column) continue;\n\n if (operator === \"=\") {\n const literal = asStringLiteral(rawRight);\n if (literal !== null) {\n return {\n column,\n apply: (facts) => {\n facts.enumValues = facts.enumValues\n ? facts.enumValues.filter((v) => v === literal)\n : [literal];\n }\n };\n }\n // `x = 5` pins a number. Expressed as both bounds rather than as a\n // one-value enum, which for a number would render as a dropdown.\n const pinned = asNumber(rawRight);\n if (pinned === null) return null;\n return { column, apply: (f) => { f.min = pinned; f.max = pinned; } };\n }\n\n const bound = asNumber(rawRight);\n if (bound === null) return null;\n switch (operator) {\n case \">=\": return { column, apply: (f) => { f.min = Math.max(f.min ?? bound, bound); } };\n case \">\": return { column, apply: (f) => { f.moreThan = Math.max(f.moreThan ?? bound, bound); } };\n case \"<=\": return { column, apply: (f) => { f.max = Math.min(f.max ?? bound, bound); } };\n case \"<\": return { column, apply: (f) => { f.lessThan = Math.min(f.lessThan ?? bound, bound); } };\n default: return null; // `<>` / `!=` exclude rather than bound.\n }\n }\n\n return null;\n}\n\n/**\n * Reads one constraint definition into per-column facts.\n *\n * Returns an empty map for anything not understood — including a constraint\n * that is understood but spans two columns (`start_date < end_date`), which no\n * per-property validation rule can express.\n */\nexport function parseCheckDefinition(definition: string): Map<string, ColumnCheckFacts> {\n const result = new Map<string, ColumnCheckFacts>();\n\n const body = definition\n .trim()\n .replace(/\\s+NOT\\s+VALID\\s*$/i, \"\")\n .replace(/^CHECK\\s*/i, \"\")\n .trim();\n if (body === definition.trim()) return result; // Not a CHECK definition.\n\n const expression = unwrapParens(body);\n if (!expression) return result;\n\n // Only conjunctions. A disjunction (`a OR b`) does not narrow a column: each\n // branch may allow what the other forbids, so no single bound follows.\n if (splitTopLevel(expression, \" OR \").length > 1) return result;\n\n const terms = splitTopLevel(expression, \" AND \");\n const parsed: CheckTerm[] = [];\n for (const term of terms) {\n const one = parseTerm(term);\n if (!one) return result; // One unreadable conjunct voids the constraint.\n parsed.push(one);\n }\n\n // A CHECK naming two columns constrains their *relationship*; keeping the\n // half that mentions one column would assert something the database does not.\n const columns = new Set(parsed.map((t) => t.column));\n if (columns.size !== 1) return result;\n\n const facts: ColumnCheckFacts = {};\n for (const term of parsed) term.apply(facts);\n if (Object.keys(facts).length === 0) return result;\n\n result.set(parsed[0].column, facts);\n return result;\n}\n\n/** Merges every readable CHECK in the schema into per-table, per-column facts. */\nexport function parseCheckConstraints(rows: CheckConstraintRow[]): CheckFactsByTable {\n const byTable: CheckFactsByTable = new Map();\n\n for (const row of rows) {\n const parsed = parseCheckDefinition(row.definition);\n if (parsed.size === 0) continue;\n\n let table = byTable.get(row.table_name);\n if (!table) {\n table = new Map();\n byTable.set(row.table_name, table);\n }\n\n for (const [column, facts] of parsed) {\n const existing = table.get(column);\n if (!existing) {\n table.set(column, facts);\n continue;\n }\n // Separate constraints on the same column all hold at once.\n if (facts.enumValues) {\n existing.enumValues = existing.enumValues\n ? existing.enumValues.filter((v) => facts.enumValues!.includes(v))\n : facts.enumValues;\n }\n if (facts.min !== undefined) existing.min = Math.max(existing.min ?? facts.min, facts.min);\n if (facts.max !== undefined) existing.max = Math.min(existing.max ?? facts.max, facts.max);\n if (facts.moreThan !== undefined) existing.moreThan = Math.max(existing.moreThan ?? facts.moreThan, facts.moreThan);\n if (facts.lessThan !== undefined) existing.lessThan = Math.min(existing.lessThan ?? facts.lessThan, facts.lessThan);\n if (facts.lengthMin !== undefined) existing.lengthMin = Math.max(existing.lengthMin ?? facts.lengthMin, facts.lengthMin);\n if (facts.lengthMax !== undefined) existing.lengthMax = Math.min(existing.lengthMax ?? facts.lengthMax, facts.lengthMax);\n }\n }\n\n return byTable;\n}\n","import chalk from \"chalk\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport pg from \"pg\";\nimport arg from \"arg\";\nimport * as dotenv from \"dotenv\";\nimport readline from \"readline\";\n\nimport {\n buildTablesMap,\n buildEnumMap,\n compositeKeySkipReason,\n generateCollectionFile,\n generateIndexContent,\n mergeIndexContent,\n safeHostFromUrl\n} from \"./introspect-db-logic\";\nimport { detectCollectionBuilder } from \"./introspect-db-project\";\nimport { countRowsUpTo, isUsableFileName, quoteRelation, readSchemaMetadata } from \"./introspect-db-queries\";\nimport { classifyTables, lookupCandidates, LOOKUP_MAX_ROWS } from \"./introspect-db-structure\";\nimport { parseCheckConstraints } from \"./introspect-db-constraints\";\nimport { out, outWarn, outError } from \"../cli-output\";\n\nasync function main() {\n const args = arg(\n {\n \"--output\": String,\n \"--collections\": String,\n \"--force\": Boolean,\n \"--schema\": String,\n \"--data-inference\": Boolean,\n \"--no-data-inference\": Boolean,\n \"-o\": \"--output\",\n \"-c\": \"--collections\",\n \"-f\": \"--force\"\n },\n { permissive: true }\n );\n\n const cwd = process.cwd();\n const isBackendDir = path.basename(cwd) === \"backend\";\n const defaultOutDir = isBackendDir\n ? path.resolve(cwd, \"..\", \"config\", \"collections\")\n : path.resolve(cwd, \"config\", \"collections\");\n\n const outDir = args[\"--output\"] || args[\"--collections\"] || defaultOutDir;\n const force = args[\"--force\"] || false;\n const pgSchema = args[\"--schema\"] || \"public\";\n\n if (!fs.existsSync(outDir)) {\n fs.mkdirSync(outDir, { recursive: true });\n }\n\n // Load env\n const envPaths = [\n process.env.DOTENV_CONFIG_PATH,\n path.resolve(process.cwd(), \".env\"),\n path.resolve(process.cwd(), \"../.env\"),\n path.resolve(process.cwd(), \"../../.env\")\n ].filter(Boolean) as string[];\n\n for (const p of envPaths) {\n if (fs.existsSync(p)) {\n dotenv.config({ path: p, quiet: true });\n break;\n }\n }\n\n const databaseUrl = process.env.DATABASE_URL || process.env.ADMIN_CONNECTION_STRING;\n if (!databaseUrl) {\n outError(chalk.red(\"✗ DATABASE_URL is not set. Make sure your .env file is configured.\"));\n process.exit(1);\n }\n\n const client = new pg.Client({ connectionString: databaseUrl });\n\n try {\n await client.connect();\n } catch (err) {\n outError(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));\n outError(chalk.gray(\" Check your DATABASE_URL and ensure the database is reachable.\"));\n process.exit(1);\n }\n\n // Log the host portion safely — handle URLs without \"@\"\n const hostPart = safeHostFromUrl(databaseUrl);\n out(chalk.gray(`Connected to database: ${hostPart}`));\n out(chalk.gray(`Introspecting schema '${pgSchema}'...`));\n\n try {\n const metadata = await readSchemaMetadata(client, pgSchema);\n const enumMap = buildEnumMap(metadata.enumValues);\n const tablesMap = buildTablesMap(metadata.tables, metadata.columns, metadata.pks, metadata.fks);\n const fks = metadata.fks;\n\n // Only tables that could structurally be a code list are counted, and\n // each count stops at the threshold — see `countRowsUpTo`. Introspection\n // runs against a database it does not own, so \"cheap on a table of any\n // size\" is a requirement, not an optimization.\n for (const table of lookupCandidates(metadata, tablesMap)) {\n try {\n metadata.rowCounts[table] = await countRowsUpTo(client, pgSchema, table, LOOKUP_MAX_ROWS);\n } catch (err) {\n // A table this run cannot read is simply not classified as a code\n // list; everything else about it still generates.\n out(chalk.gray(` (skipped row count for ${table}: ${err instanceof Error ? err.message : String(err)})`));\n }\n }\n\n const classifications = classifyTables(metadata, tablesMap);\n const checkFacts = parseCheckConstraints(metadata.checks);\n\n // Which builder the generated files may import — read from the manifests\n // above `outDir`, because a project without `@rebasepro/cms-types` cannot\n // resolve that import and has no `admin` block to write into either.\n const builder = detectCollectionBuilder(outDir);\n if (builder === \"annotation\") {\n outWarn(chalk.yellow(\n \"⚠ Neither @rebasepro/cms-types nor @rebasepro/common is declared above \" +\n `${outDir}.`\n ));\n outWarn(chalk.gray(\n \" Generating plain type annotations, which widen the property keys to `string`: \" +\n \"`propertiesOrder` and friends will not be checked. Add @rebasepro/common to the \" +\n \"config package to turn that checking on.\"\n ));\n }\n const joinTables = new Set(\n Array.from(classifications.values())\n .filter((c) => c.role === \"junction\")\n .map((c) => c.table)\n );\n\n const roleCount = (role: string) =>\n Array.from(classifications.values()).filter((c) => c.role === role).length;\n\n out(chalk.blue(`Found ${tablesMap.size} tables.`));\n out(chalk.gray(\n ` ${roleCount(\"entity\")} entities, ${joinTables.size} join tables (folded into relations), ` +\n `${roleCount(\"lookup\")} code lists, ${roleCount(\"owned-child\")} owned by another table (hidden from navigation).`\n ));\n\n let runDataInference = false;\n if (args[\"--no-data-inference\"]) {\n runDataInference = false;\n } else if (args[\"--data-inference\"] !== undefined) {\n runDataInference = args[\"--data-inference\"];\n } else if (!process.stdin.isTTY) {\n // No terminal to answer the question below (scaffolding scripts, CI,\n // `rebase init --introspect`) — asking would hang forever.\n out(chalk.gray(\"Skipping data inference (non-interactive run; pass --data-inference to enable).\"));\n } else {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout\n });\n const answer = await new Promise<string>((resolve) => rl.question(chalk.yellow(\"? Do you want to run comprehensive data inference on sampled rows to auto-detect types, formats, constraints, and UI configurations? (y/N) \"), resolve));\n runDataInference = answer.trim().toLowerCase() === \"y\";\n rl.close();\n }\n\n if (runDataInference) {\n out(chalk.gray(\"Sampling database rows for data inference...\"));\n }\n\n // Generate Collections\n const generatedFiles: string[] = [];\n const skippedFiles: string[] = [];\n\n // A table keyed on more than one column is generated with `isId` on\n // every key column — one composite key, which the planner builds the\n // same way — unless that cannot round-trip: a key column no `isId` can\n // mark, or a link pointing at the table (see `compositeKeySkipReason`).\n // Those are left out, with the reason. Generating one anyway produced a\n // file that read the table by an `id` column it does not have, and a\n // push that planned that column as its new primary key.\n const compositeKeyed = new Map(\n Array.from(tablesMap.entries())\n .filter(([tableName]) => !joinTables.has(tableName))\n .map(([tableName, meta]) => [tableName, compositeKeySkipReason(meta, fks)] as const)\n .filter((entry): entry is readonly [string, string] => entry[1] !== undefined)\n );\n for (const [tableName, reason] of compositeKeyed) {\n outWarn(chalk.yellow(\n `⚠ Skipping table ${JSON.stringify(tableName)}: it is keyed on ` +\n `(${tablesMap.get(tableName)?.pks.join(\", \")}), and ${reason}. ` +\n \"`rebase db push` leaves a table that is not a collection alone.\"\n ));\n }\n const notes: string[] = [];\n\n const tablesToProcess = Array.from(tablesMap.entries())\n .filter(([tableName]) => !joinTables.has(tableName) && !compositeKeyed.has(tableName));\n\n const BATCH_SIZE = 10;\n for (let i = 0; i < tablesToProcess.length; i += BATCH_SIZE) {\n const batch = tablesToProcess.slice(i, i + BATCH_SIZE);\n\n await Promise.all(batch.map(async ([tableName, meta]) => {\n // ── The name has to be usable as a filename ────────────────\n //\n // `tableName` came out of `pg_class` in a database this tool\n // did not create — adopting somebody else's database is the\n // whole point of introspection — and it is about to be joined\n // onto a path. Postgres will happily hold a table called\n // `../../../etc/cron.d/x`, and `path.join` resolves it: the\n // generator would write a TypeScript file wherever that\n // pointed, as whoever ran the command.\n //\n // Skipped rather than sanitised. A file named something other\n // than its table is a collection that does not round-trip, and\n // silently renaming it would be a worse surprise than one line\n // saying which table was left out.\n if (!isUsableFileName(tableName)) {\n outWarn(chalk.yellow(\n `⚠ Skipping table ${JSON.stringify(tableName)}: its name cannot be used as a ` +\n \"filename. Rename the table, or write this collection by hand.\"\n ));\n skippedFiles.push(tableName);\n return;\n }\n\n // ── File overwrite protection ──────────────────────────────\n const filePath = path.join(outDir, `${tableName}.ts`);\n if (fs.existsSync(filePath) && !force) {\n skippedFiles.push(tableName);\n return;\n }\n\n let sampleData: Record<string, unknown>[] | undefined = undefined;\n if (runDataInference) {\n try {\n // Quoted by `quoteRelation`, which doubles an embedded\n // quote. Wrapping a catalogue name in `\"` is not\n // escaping it — see that function.\n const { rows } = await client.query(`SELECT * FROM ${quoteRelation(pgSchema, tableName)} LIMIT 100`);\n sampleData = rows;\n } catch (err) {\n outError(chalk.yellow(`⚠ Failed to sample data for table ${tableName}: ${err instanceof Error ? err.message : String(err)}`));\n }\n }\n\n const fileContent = generateCollectionFile(\n tableName,\n meta,\n fks,\n joinTables,\n tablesMap,\n enumMap,\n sampleData,\n { metadata, classifications, checkFacts, builder, notes, skippedTables: new Set(compositeKeyed.keys()) }\n );\n\n fs.writeFileSync(filePath, fileContent, \"utf-8\");\n generatedFiles.push(tableName);\n out(chalk.green(` ✓ ${filePath}`));\n }));\n }\n\n // Every column the planner will not reproduce exactly, before anyone\n // pushes. Each line is also at the top of its file.\n if (notes.length > 0) {\n outWarn(\"\");\n outWarn(chalk.yellow(`⚠ ${notes.length} column(s) will not round-trip exactly — \\`rebase db push\\` would change them:`));\n for (const note of [...notes].sort()) outWarn(chalk.gray(` - ${note}`));\n outWarn(chalk.gray(\" Read these before the first push. Run `rebase db push --dry-run` to see the plan.\"));\n }\n\n // Generate index.ts (sorted alphabetically for deterministic output)\n if (generatedFiles.length > 0) {\n const indexPath = path.join(outDir, \"index.ts\");\n\n if (fs.existsSync(indexPath) && !force) {\n // Merge: read existing index, add new exports that don't already exist\n const existing = fs.readFileSync(indexPath, \"utf-8\");\n const merged = mergeIndexContent(existing, generatedFiles);\n fs.writeFileSync(indexPath, merged, \"utf-8\");\n } else {\n // --force replaces collections derived from the database, but the\n // directory can also hold hand-written ones with no table in the\n // introspected schema (the auth users collection lives in\n // \"rebase\"). The backend discovers the whole directory, so an\n // index listing only introspected tables would silently drop them\n // from the admin UI while the API still served them.\n const siblings = fs.readdirSync(outDir)\n .filter(f => f.endsWith(\".ts\") && f !== \"index.ts\")\n .map(f => f.replace(/\\.ts$/, \"\"));\n const allFiles = [...new Set([...generatedFiles, ...siblings])];\n const indexContent = generateIndexContent(allFiles);\n fs.writeFileSync(indexPath, indexContent, \"utf-8\");\n }\n out(chalk.green(` ✓ ${indexPath}`));\n }\n\n out(\"\");\n if (skippedFiles.length > 0) {\n out(chalk.yellow(`⚠ Skipped ${skippedFiles.length} existing file(s): ${skippedFiles.join(\", \")}`));\n out(chalk.gray(\" Use --force to overwrite existing files.\"));\n out(\"\");\n }\n out(chalk.bold.green(`✓ Introspected ${tablesMap.size} tables — generated ${generatedFiles.length} collection(s).`));\n out(chalk.gray(` Review the generated files in ${outDir} and customize properties as needed.`));\n out(\"\");\n\n } catch (e) {\n outError(chalk.red(`✗ Error introspecting database: ${e instanceof Error ? e.message : String(e)}`));\n process.exit(1);\n } finally {\n await client.end();\n }\n}\n\nmain().catch((err) => {\n outError(String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"introspect-db.js","names":[],"sources":["../../src/schema/introspect-db-project.ts","../../src/schema/introspect-db-constraints.ts","../../src/schema/introspect-db.ts"],"sourcesContent":["/**\n * What the project being introspected into can actually import.\n *\n * The generator is pure — `introspect-db-logic.ts` reads no files — so the one\n * question it cannot answer for itself lives here: which `defineCollection`, if\n * any, will resolve from the directory the collections are written to.\n */\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { ADMIN_TYPES_PACKAGE, COMMON_PACKAGE, type CollectionBuilder } from \"./introspect-db-logic\";\n\n/** How far up to look before giving up, if no project root announces itself. */\nconst MAX_LEVELS = 8;\n\n/** Every dependency name a manifest declares, in any of the three fields. */\nfunction declaredDependencies(manifestPath: string): Set<string> {\n const names = new Set<string>();\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(manifestPath, \"utf-8\"));\n } catch {\n // A malformed or unreadable package.json is not this function's problem\n // to report — it answers \"can this import resolve\", and the answer is no.\n return names;\n }\n if (typeof parsed !== \"object\" || parsed === null) return names;\n const manifest = parsed as Record<string, unknown>;\n for (const field of [\"dependencies\", \"devDependencies\", \"peerDependencies\"]) {\n const block = manifest[field];\n if (typeof block === \"object\" && block !== null) {\n for (const name of Object.keys(block)) names.add(name);\n }\n }\n return names;\n}\n\n/**\n * Which `defineCollection` to generate against, for collections written to `outDir`.\n *\n * **The detection is the package manifests above the output directory**, unioned from\n * `outDir` up to and including the project root — the first ancestor holding a\n * `rebase.json`, or `MAX_LEVELS` up if there is none.\n *\n * That is the rule Node itself applies: a file in `config/collections` resolves a bare\n * specifier through `config/node_modules`, then `<project>/node_modules`, and so on up.\n * Reading the manifests along that same path answers the only question that matters —\n * *will this import resolve in the project I am writing into* — from the state on disk\n * at the moment of generation. The alternatives are all proxies for it: `rebase.json`'s\n * `apps` block says a CMS scaffold declared an admin app, and a `frontend/` directory\n * says one was scaffolded, but neither is what the compiler consults, and either can be\n * true of a project whose `config` package does not depend on `@rebasepro/cms-types`.\n *\n * Ambiguity resolves towards the admin panel: a project that declares both packages has\n * a panel, and `@rebasepro/cms-types` is the flavour that keeps the `admin` block.\n */\nexport function detectCollectionBuilder(outDir: string): CollectionBuilder {\n let dir = path.resolve(outDir);\n const declared = new Set<string>();\n\n for (let level = 0; level < MAX_LEVELS; level++) {\n const manifest = path.join(dir, \"package.json\");\n if (fs.existsSync(manifest)) {\n for (const name of declaredDependencies(manifest)) declared.add(name);\n }\n // The project root, and the last level worth reading: anything above it\n // belongs to whatever the project happens to be checked out inside.\n if (fs.existsSync(path.join(dir, \"rebase.json\"))) break;\n\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n if (declared.has(ADMIN_TYPES_PACKAGE)) return \"admin-types\";\n if (declared.has(COMMON_PACKAGE)) return \"common\";\n return \"annotation\";\n}\n","/**\n * Reading validation rules out of CHECK constraints.\n *\n * A CHECK constraint is the schema author stating a rule the database already\n * enforces. Introspection has never read them, so a generated form let the user\n * type a value the database was always going to reject — the rule was written\n * down, in the schema, and the UI asked anyway.\n *\n * Everything here parses the *normalized* text `pg_get_constraintdef` produces,\n * not what the author typed: Postgres re-renders the expression from its parse\n * tree, so `CHECK (price > 0)` on a `numeric` column always comes back as\n * `CHECK ((price > (0)::numeric))`. That normalization is what makes a\n * string-level parser tractable — the input is generated, and the shapes are\n * few.\n *\n * Constraints this cannot read are skipped in full. A partially-understood\n * constraint is worse than an unread one: it would produce validation that\n * *narrows* differently from the database, so a value the UI accepts still\n * fails on write, or — worse — a value the database allows is refused in the\n * form with no way to see why.\n *\n * Pure module: no I/O, no logging.\n */\nimport type { CheckConstraintRow } from \"./introspect-db-logic\";\n\n/** What a table's CHECK constraints say about one column. */\nexport interface ColumnCheckFacts {\n /** Allowed values, from `IN (…)` / `= ANY (ARRAY[…])` / `= 'literal'`. */\n enumValues?: string[];\n /** `x >= n` */\n min?: number;\n /** `x <= n` */\n max?: number;\n /** `x > n` */\n moreThan?: number;\n /** `x < n` */\n lessThan?: number;\n /** `length(x) >= n` */\n lengthMin?: number;\n /** `length(x) <= n` */\n lengthMax?: number;\n}\n\n/** Per-table, per-column facts. */\nexport type CheckFactsByTable = Map<string, Map<string, ColumnCheckFacts>>;\n\n/** The length functions whose argument is the column being constrained. */\nconst LENGTH_FUNCTIONS = new Set([\"length\", \"char_length\", \"character_length\", \"octet_length\"]);\n\n/**\n * Splits on a separator that appears at paren depth 0 and outside string\n * literals.\n *\n * Depth tracking is the whole job: `ARRAY['a', 'b']` contains a comma that must\n * not split the enclosing argument list, and `'it''s'` contains a quote that\n * must not end the literal.\n */\nfunction splitTopLevel(input: string, separator: string): string[] {\n const parts: string[] = [];\n let depth = 0;\n let inString = false;\n let current = \"\";\n\n for (let i = 0; i < input.length; i++) {\n const ch = input[i];\n\n if (inString) {\n current += ch;\n if (ch === \"'\") {\n // '' inside a literal is an escaped quote, not the end of it.\n if (input[i + 1] === \"'\") {\n current += input[++i];\n } else {\n inString = false;\n }\n }\n continue;\n }\n\n if (ch === \"'\") {\n inString = true;\n current += ch;\n continue;\n }\n if (ch === \"(\" || ch === \"[\") depth++;\n if (ch === \")\" || ch === \"]\") depth--;\n\n if (depth === 0 && input.startsWith(separator, i)) {\n // A word separator must not match inside an identifier: \"AND\" is a\n // separator in `a AND b` but not in `brand = 1`.\n const before = input[i - 1];\n const after = input[i + separator.length];\n const wordish = /[A-Za-z]/.test(separator[0]);\n const boundedOk = !wordish || ((before === undefined || !/[A-Za-z0-9_]/.test(before)) &&\n (after === undefined || !/[A-Za-z0-9_]/.test(after)));\n if (boundedOk) {\n parts.push(current);\n current = \"\";\n i += separator.length - 1;\n continue;\n }\n }\n\n current += ch;\n }\n\n parts.push(current);\n return parts.map((p) => p.trim()).filter((p) => p.length > 0);\n}\n\n/** Removes one layer of wrapping parentheses, repeatedly, when balanced. */\nexport function unwrapParens(input: string): string {\n let s = input.trim();\n while (s.startsWith(\"(\") && s.endsWith(\")\")) {\n let depth = 0;\n let balanced = true;\n let inString = false;\n for (let i = 0; i < s.length; i++) {\n const ch = s[i];\n if (inString) {\n if (ch === \"'\") {\n if (s[i + 1] === \"'\") i++;\n else inString = false;\n }\n continue;\n }\n if (ch === \"'\") { inString = true; continue; }\n if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n // The opening paren closed before the end, so the outer pair is\n // not a wrapper: `(a) AND (b)`.\n if (depth === 0 && i < s.length - 1) { balanced = false; break; }\n }\n }\n if (!balanced) break;\n s = s.slice(1, -1).trim();\n }\n return s;\n}\n\n/** Drops trailing `::type` casts, including array and quoted forms. */\nexport function stripCasts(input: string): string {\n let s = unwrapParens(input);\n let previous: string;\n do {\n previous = s;\n s = unwrapParens(\n s.replace(/::\\s*\"?[A-Za-z_][A-Za-z0-9_ ]*\"?(\\s*\\(\\s*\\d+\\s*(,\\s*\\d+\\s*)?\\))?(\\s*\\[\\s*\\])*\\s*$/, \"\").trim()\n );\n } while (s !== previous);\n return s;\n}\n\n/** A bare, unquoted column reference — anything else is an expression. */\nfunction asColumnName(input: string): string | null {\n const s = stripCasts(input);\n if (/^[a-z_][a-z0-9_$]*$/i.test(s)) return s;\n // A quoted identifier keeps its case: \"userId\".\n const quoted = s.match(/^\"([^\"]+)\"$/);\n return quoted ? quoted[1] : null;\n}\n\n/** `length(col)` and friends → the column they measure. */\nfunction asLengthOfColumn(input: string): string | null {\n const s = stripCasts(input);\n const match = s.match(/^([a-z_]+)\\s*\\((.*)\\)$/is);\n if (!match || !LENGTH_FUNCTIONS.has(match[1].toLowerCase())) return null;\n return asColumnName(match[2]);\n}\n\n/** A numeric literal, after casts are removed. Rejects anything else. */\nfunction asNumber(input: string): number | null {\n const s = stripCasts(input).replace(/^'(.*)'$/s, \"$1\");\n if (!/^[+-]?(\\d+\\.?\\d*|\\.\\d+)$/.test(s)) return null;\n const n = Number(s);\n return Number.isFinite(n) ? n : null;\n}\n\n/** A single-quoted string literal, with `''` unescaped. */\nfunction asStringLiteral(input: string): string | null {\n const s = stripCasts(input);\n if (!s.startsWith(\"'\") || !s.endsWith(\"'\") || s.length < 2) return null;\n const body = s.slice(1, -1);\n // Any unescaped quote inside means this was not one literal.\n if (/(^|[^'])'($|[^'])/.test(body)) return null;\n return body.replace(/''/g, \"'\");\n}\n\n/** The element list of `ARRAY[…]`, or of a parenthesised `IN (…)` list. */\nfunction asValueList(input: string): string[] | null {\n const s = stripCasts(input);\n const arrayMatch = s.match(/^ARRAY\\s*\\[(.*)\\]$/is);\n const body = arrayMatch ? arrayMatch[1] : (s.startsWith(\"(\") && s.endsWith(\")\") ? s.slice(1, -1) : null);\n if (body === null) return null;\n if (body.trim() === \"\") return null;\n\n const values: string[] = [];\n for (const element of splitTopLevel(body, \",\")) {\n const literal = asStringLiteral(element);\n if (literal !== null) {\n values.push(literal);\n continue;\n }\n const numeric = asNumber(element);\n if (numeric !== null) {\n values.push(String(numeric));\n continue;\n }\n return null; // A non-literal element means this is not an enumeration.\n }\n return values;\n}\n\n/** One understood comparison, already resolved to the column it constrains. */\ninterface CheckTerm {\n column: string;\n apply: (facts: ColumnCheckFacts) => void;\n}\n\n/**\n * Parses one conjunct. Returns null when the shape is not one of the few this\n * understands, which is the signal to abandon the whole constraint.\n */\nfunction parseTerm(rawTerm: string): CheckTerm | null {\n const term = unwrapParens(rawTerm);\n\n // ── Membership: `x = ANY (ARRAY[…])`, `x IN (…)` ──────────────────\n const anyMatch = term.match(/^(.*?)\\s*=\\s*ANY\\s*(\\(.*\\))$/is);\n const inMatch = anyMatch ? null : term.match(/^(.*?)\\s+IN\\s*(\\(.*\\))$/is);\n const membership = anyMatch ?? inMatch;\n if (membership) {\n const column = asColumnName(membership[1]);\n const values = asValueList(membership[2]);\n if (!column || !values || values.length === 0) return null;\n return {\n column,\n apply: (facts) => {\n // Two membership checks on one column mean both must hold.\n facts.enumValues = facts.enumValues\n ? facts.enumValues.filter((v) => values.includes(v))\n : values;\n }\n };\n }\n\n // ── Comparison: `x >= n`, `length(x) <= n`, `x = 'literal'` ───────\n // Longest operators first so `>=` is not read as `>`.\n for (const operator of [\">=\", \"<=\", \"<>\", \"!=\", \"=\", \">\", \"<\"] as const) {\n const parts = splitTopLevel(term, operator);\n if (parts.length !== 2) continue;\n const [rawLeft, rawRight] = parts;\n\n const lengthColumn = asLengthOfColumn(rawLeft);\n if (lengthColumn) {\n const bound = asNumber(rawRight);\n if (bound === null) return null;\n switch (operator) {\n case \">=\": return { column: lengthColumn, apply: (f) => { f.lengthMin = Math.max(f.lengthMin ?? bound, bound); } };\n case \">\": return { column: lengthColumn, apply: (f) => { f.lengthMin = Math.max(f.lengthMin ?? bound + 1, bound + 1); } };\n case \"<=\": return { column: lengthColumn, apply: (f) => { f.lengthMax = Math.min(f.lengthMax ?? bound, bound); } };\n case \"<\": return { column: lengthColumn, apply: (f) => { f.lengthMax = Math.min(f.lengthMax ?? bound - 1, bound - 1); } };\n case \"=\": return { column: lengthColumn, apply: (f) => { f.lengthMin = bound; f.lengthMax = bound; } };\n default: return null;\n }\n }\n\n const column = asColumnName(rawLeft);\n if (!column) continue;\n\n if (operator === \"=\") {\n const literal = asStringLiteral(rawRight);\n if (literal !== null) {\n return {\n column,\n apply: (facts) => {\n facts.enumValues = facts.enumValues\n ? facts.enumValues.filter((v) => v === literal)\n : [literal];\n }\n };\n }\n // `x = 5` pins a number. Expressed as both bounds rather than as a\n // one-value enum, which for a number would render as a dropdown.\n const pinned = asNumber(rawRight);\n if (pinned === null) return null;\n return { column, apply: (f) => { f.min = pinned; f.max = pinned; } };\n }\n\n const bound = asNumber(rawRight);\n if (bound === null) return null;\n switch (operator) {\n case \">=\": return { column, apply: (f) => { f.min = Math.max(f.min ?? bound, bound); } };\n case \">\": return { column, apply: (f) => { f.moreThan = Math.max(f.moreThan ?? bound, bound); } };\n case \"<=\": return { column, apply: (f) => { f.max = Math.min(f.max ?? bound, bound); } };\n case \"<\": return { column, apply: (f) => { f.lessThan = Math.min(f.lessThan ?? bound, bound); } };\n default: return null; // `<>` / `!=` exclude rather than bound.\n }\n }\n\n return null;\n}\n\n/**\n * Reads one constraint definition into per-column facts.\n *\n * Returns an empty map for anything not understood — including a constraint\n * that is understood but spans two columns (`start_date < end_date`), which no\n * per-property validation rule can express.\n */\nexport function parseCheckDefinition(definition: string): Map<string, ColumnCheckFacts> {\n const result = new Map<string, ColumnCheckFacts>();\n\n const body = definition\n .trim()\n .replace(/\\s+NOT\\s+VALID\\s*$/i, \"\")\n .replace(/^CHECK\\s*/i, \"\")\n .trim();\n if (body === definition.trim()) return result; // Not a CHECK definition.\n\n const expression = unwrapParens(body);\n if (!expression) return result;\n\n // Only conjunctions. A disjunction (`a OR b`) does not narrow a column: each\n // branch may allow what the other forbids, so no single bound follows.\n if (splitTopLevel(expression, \" OR \").length > 1) return result;\n\n const terms = splitTopLevel(expression, \" AND \");\n const parsed: CheckTerm[] = [];\n for (const term of terms) {\n const one = parseTerm(term);\n if (!one) return result; // One unreadable conjunct voids the constraint.\n parsed.push(one);\n }\n\n // A CHECK naming two columns constrains their *relationship*; keeping the\n // half that mentions one column would assert something the database does not.\n const columns = new Set(parsed.map((t) => t.column));\n if (columns.size !== 1) return result;\n\n const facts: ColumnCheckFacts = {};\n for (const term of parsed) term.apply(facts);\n if (Object.keys(facts).length === 0) return result;\n\n result.set(parsed[0].column, facts);\n return result;\n}\n\n/** Merges every readable CHECK in the schema into per-table, per-column facts. */\nexport function parseCheckConstraints(rows: CheckConstraintRow[]): CheckFactsByTable {\n const byTable: CheckFactsByTable = new Map();\n\n for (const row of rows) {\n const parsed = parseCheckDefinition(row.definition);\n if (parsed.size === 0) continue;\n\n let table = byTable.get(row.table_name);\n if (!table) {\n table = new Map();\n byTable.set(row.table_name, table);\n }\n\n for (const [column, facts] of parsed) {\n const existing = table.get(column);\n if (!existing) {\n table.set(column, facts);\n continue;\n }\n // Separate constraints on the same column all hold at once.\n if (facts.enumValues) {\n existing.enumValues = existing.enumValues\n ? existing.enumValues.filter((v) => facts.enumValues!.includes(v))\n : facts.enumValues;\n }\n if (facts.min !== undefined) existing.min = Math.max(existing.min ?? facts.min, facts.min);\n if (facts.max !== undefined) existing.max = Math.min(existing.max ?? facts.max, facts.max);\n if (facts.moreThan !== undefined) existing.moreThan = Math.max(existing.moreThan ?? facts.moreThan, facts.moreThan);\n if (facts.lessThan !== undefined) existing.lessThan = Math.min(existing.lessThan ?? facts.lessThan, facts.lessThan);\n if (facts.lengthMin !== undefined) existing.lengthMin = Math.max(existing.lengthMin ?? facts.lengthMin, facts.lengthMin);\n if (facts.lengthMax !== undefined) existing.lengthMax = Math.min(existing.lengthMax ?? facts.lengthMax, facts.lengthMax);\n }\n }\n\n return byTable;\n}\n","import chalk from \"chalk\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport pg from \"pg\";\nimport arg from \"arg\";\nimport * as dotenv from \"dotenv\";\n\nimport {\n buildTablesMap,\n buildEnumMap,\n compositeKeySkipReason,\n generateCollectionFile,\n generateIndexContent,\n mergeIndexContent,\n safeHostFromUrl\n} from \"./introspect-db-logic\";\nimport { detectCollectionBuilder } from \"./introspect-db-project\";\nimport { countRowsUpTo, isUsableFileName, quoteRelation, readSchemaMetadata } from \"./introspect-db-queries\";\nimport { classifyTables, lookupCandidates, LOOKUP_MAX_ROWS } from \"./introspect-db-structure\";\nimport { parseCheckConstraints } from \"./introspect-db-constraints\";\nimport { out, outWarn, outError } from \"../cli-output\";\nimport { promptConfirm } from \"../cli-prompt\";\nimport { diagnoseDbError } from \"../cli-errors\";\n\nasync function main() {\n const args = arg(\n {\n \"--output\": String,\n \"--collections\": String,\n \"--force\": Boolean,\n \"--schema\": String,\n \"--data-inference\": Boolean,\n \"--no-data-inference\": Boolean,\n \"-o\": \"--output\",\n \"-c\": \"--collections\",\n \"-f\": \"--force\"\n },\n { permissive: true }\n );\n\n const cwd = process.cwd();\n const isBackendDir = path.basename(cwd) === \"backend\";\n const defaultOutDir = isBackendDir\n ? path.resolve(cwd, \"..\", \"config\", \"collections\")\n : path.resolve(cwd, \"config\", \"collections\");\n\n // Absolute, because it is printed: the driver runs in `backend/`, and the\n // default `../config/collections` read from the project root named a\n // directory beside it.\n const outDir = path.resolve(args[\"--output\"] || args[\"--collections\"] || defaultOutDir);\n const force = args[\"--force\"] || false;\n const pgSchema = args[\"--schema\"] || \"public\";\n\n // Load env\n const envPaths = [\n process.env.DOTENV_CONFIG_PATH,\n path.resolve(process.cwd(), \".env\"),\n path.resolve(process.cwd(), \"../.env\"),\n path.resolve(process.cwd(), \"../../.env\")\n ].filter(Boolean) as string[];\n\n for (const p of envPaths) {\n if (fs.existsSync(p)) {\n dotenv.config({ path: p, quiet: true });\n break;\n }\n }\n\n const databaseUrl = process.env.DATABASE_URL || process.env.ADMIN_CONNECTION_STRING;\n if (!databaseUrl) {\n outError(chalk.red(\"✗ DATABASE_URL is not set. Make sure your .env file is configured.\"));\n process.exit(1);\n }\n\n const client = new pg.Client({ connectionString: databaseUrl });\n\n try {\n await client.connect();\n } catch (err) {\n // The banner `db push` prints for the same failure, when it is one of\n // the ones it knows.\n const banner = diagnoseDbError(err, databaseUrl);\n if (banner) {\n outError(banner);\n } else {\n outError(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));\n outError(chalk.gray(\" Check your DATABASE_URL and ensure the database is reachable.\"));\n }\n process.exit(1);\n }\n\n // Only once there is something to write into: created before connecting,\n // a run against a database that was down left an empty directory behind.\n if (!fs.existsSync(outDir)) {\n fs.mkdirSync(outDir, { recursive: true });\n }\n\n // Log the host portion safely — handle URLs without \"@\"\n const hostPart = safeHostFromUrl(databaseUrl);\n out(chalk.gray(`Connected to database: ${hostPart}`));\n out(chalk.gray(`Introspecting schema '${pgSchema}'...`));\n\n try {\n const metadata = await readSchemaMetadata(client, pgSchema);\n const enumMap = buildEnumMap(metadata.enumValues);\n const tablesMap = buildTablesMap(metadata.tables, metadata.columns, metadata.pks, metadata.fks);\n const fks = metadata.fks;\n\n // Only tables that could structurally be a code list are counted, and\n // each count stops at the threshold — see `countRowsUpTo`. Introspection\n // runs against a database it does not own, so \"cheap on a table of any\n // size\" is a requirement, not an optimization.\n for (const table of lookupCandidates(metadata, tablesMap)) {\n try {\n metadata.rowCounts[table] = await countRowsUpTo(client, pgSchema, table, LOOKUP_MAX_ROWS);\n } catch (err) {\n // A table this run cannot read is simply not classified as a code\n // list; everything else about it still generates.\n out(chalk.gray(` (skipped row count for ${table}: ${err instanceof Error ? err.message : String(err)})`));\n }\n }\n\n const classifications = classifyTables(metadata, tablesMap);\n const checkFacts = parseCheckConstraints(metadata.checks);\n\n // Which builder the generated files may import — read from the manifests\n // above `outDir`, because a project without `@rebasepro/cms-types` cannot\n // resolve that import and has no `admin` block to write into either.\n const builder = detectCollectionBuilder(outDir);\n if (builder === \"annotation\") {\n outWarn(chalk.yellow(\n \"⚠ Neither @rebasepro/cms-types nor @rebasepro/common is declared above \" +\n `${outDir}.`\n ));\n outWarn(chalk.gray(\n \" Generating plain type annotations, which widen the property keys to `string`: \" +\n \"`propertiesOrder` and friends will not be checked. Add @rebasepro/common to the \" +\n \"config package to turn that checking on.\"\n ));\n }\n const joinTables = new Set(\n Array.from(classifications.values())\n .filter((c) => c.role === \"junction\")\n .map((c) => c.table)\n );\n\n const roleCount = (role: string) =>\n Array.from(classifications.values()).filter((c) => c.role === role).length;\n\n out(chalk.blue(`Found ${tablesMap.size} tables.`));\n out(chalk.gray(\n ` ${roleCount(\"entity\")} entities, ${joinTables.size} join tables (folded into relations), ` +\n `${roleCount(\"lookup\")} code lists, ${roleCount(\"owned-child\")} owned by another table (hidden from navigation).`\n ));\n\n let runDataInference = false;\n if (args[\"--no-data-inference\"]) {\n runDataInference = false;\n } else if (args[\"--data-inference\"] !== undefined) {\n runDataInference = args[\"--data-inference\"];\n } else if (!process.stdin.isTTY) {\n // No terminal to answer the question below (scaffolding scripts, CI,\n // `rebase init --introspect`) — asking would hang forever.\n out(chalk.gray(\"Skipping data inference (non-interactive run; pass --data-inference to enable).\"));\n } else {\n runDataInference = await promptConfirm(chalk.yellow(\"? Do you want to run comprehensive data inference on sampled rows to auto-detect types, formats, constraints, and UI configurations? (y/N) \"));\n }\n\n if (runDataInference) {\n out(chalk.gray(\"Sampling database rows for data inference...\"));\n }\n\n // Generate Collections\n const generatedFiles: string[] = [];\n const skippedFiles: string[] = [];\n\n // A table keyed on more than one column is generated with `isId` on\n // every key column — one composite key, which the planner builds the\n // same way — unless that cannot round-trip: a key column no `isId` can\n // mark, or a link pointing at the table (see `compositeKeySkipReason`).\n // Those are left out, with the reason. Generating one anyway produced a\n // file that read the table by an `id` column it does not have, and a\n // push that planned that column as its new primary key.\n const compositeKeyed = new Map(\n Array.from(tablesMap.entries())\n .filter(([tableName]) => !joinTables.has(tableName))\n .map(([tableName, meta]) => [tableName, compositeKeySkipReason(meta, fks)] as const)\n .filter((entry): entry is readonly [string, string] => entry[1] !== undefined)\n );\n for (const [tableName, reason] of compositeKeyed) {\n outWarn(chalk.yellow(\n `⚠ Skipping table ${JSON.stringify(tableName)}: it is keyed on ` +\n `(${tablesMap.get(tableName)?.pks.join(\", \")}), and ${reason}. ` +\n \"`rebase db push` leaves a table that is not a collection alone.\"\n ));\n }\n const notes: string[] = [];\n\n const tablesToProcess = Array.from(tablesMap.entries())\n .filter(([tableName]) => !joinTables.has(tableName) && !compositeKeyed.has(tableName));\n\n const BATCH_SIZE = 10;\n for (let i = 0; i < tablesToProcess.length; i += BATCH_SIZE) {\n const batch = tablesToProcess.slice(i, i + BATCH_SIZE);\n\n await Promise.all(batch.map(async ([tableName, meta]) => {\n // ── The name has to be usable as a filename ────────────────\n //\n // `tableName` came out of `pg_class` in a database this tool\n // did not create — adopting somebody else's database is the\n // whole point of introspection — and it is about to be joined\n // onto a path. Postgres will happily hold a table called\n // `../../../etc/cron.d/x`, and `path.join` resolves it: the\n // generator would write a TypeScript file wherever that\n // pointed, as whoever ran the command.\n //\n // Skipped rather than sanitised. A file named something other\n // than its table is a collection that does not round-trip, and\n // silently renaming it would be a worse surprise than one line\n // saying which table was left out.\n if (!isUsableFileName(tableName)) {\n outWarn(chalk.yellow(\n `⚠ Skipping table ${JSON.stringify(tableName)}: its name cannot be used as a ` +\n \"filename. Rename the table, or write this collection by hand.\"\n ));\n skippedFiles.push(tableName);\n return;\n }\n\n // ── File overwrite protection ──────────────────────────────\n const filePath = path.join(outDir, `${tableName}.ts`);\n if (fs.existsSync(filePath) && !force) {\n skippedFiles.push(tableName);\n return;\n }\n\n let sampleData: Record<string, unknown>[] | undefined = undefined;\n if (runDataInference) {\n try {\n // Quoted by `quoteRelation`, which doubles an embedded\n // quote. Wrapping a catalogue name in `\"` is not\n // escaping it — see that function.\n const { rows } = await client.query(`SELECT * FROM ${quoteRelation(pgSchema, tableName)} LIMIT 100`);\n sampleData = rows;\n } catch (err) {\n outError(chalk.yellow(`⚠ Failed to sample data for table ${tableName}: ${err instanceof Error ? err.message : String(err)}`));\n }\n }\n\n const fileContent = generateCollectionFile(\n tableName,\n meta,\n fks,\n joinTables,\n tablesMap,\n enumMap,\n sampleData,\n { metadata, classifications, checkFacts, builder, notes, skippedTables: new Set(compositeKeyed.keys()) }\n );\n\n fs.writeFileSync(filePath, fileContent, \"utf-8\");\n generatedFiles.push(tableName);\n out(chalk.green(` ✓ ${filePath}`));\n }));\n }\n\n // Every column the planner will not reproduce exactly, before anyone\n // pushes. Each line is also at the top of its file.\n if (notes.length > 0) {\n outWarn(\"\");\n outWarn(chalk.yellow(`⚠ ${notes.length} column(s) will not round-trip exactly — \\`rebase db push\\` would change them:`));\n for (const note of [...notes].sort()) outWarn(chalk.gray(` - ${note}`));\n outWarn(chalk.gray(\" Read these before the first push. Run `rebase db push --dry-run` to see the plan.\"));\n }\n\n // Generate index.ts (sorted alphabetically for deterministic output)\n if (generatedFiles.length > 0) {\n const indexPath = path.join(outDir, \"index.ts\");\n\n if (fs.existsSync(indexPath) && !force) {\n // Merge: read existing index, add new exports that don't already exist\n const existing = fs.readFileSync(indexPath, \"utf-8\");\n const merged = mergeIndexContent(existing, generatedFiles);\n fs.writeFileSync(indexPath, merged, \"utf-8\");\n } else {\n // --force replaces collections derived from the database, but the\n // directory can also hold hand-written ones with no table in the\n // introspected schema (the auth users collection lives in\n // \"rebase\"). The backend discovers the whole directory, so an\n // index listing only introspected tables would silently drop them\n // from the admin UI while the API still served them.\n const siblings = fs.readdirSync(outDir)\n .filter(f => f.endsWith(\".ts\") && f !== \"index.ts\")\n .map(f => f.replace(/\\.ts$/, \"\"));\n const allFiles = [...new Set([...generatedFiles, ...siblings])];\n const indexContent = generateIndexContent(allFiles);\n fs.writeFileSync(indexPath, indexContent, \"utf-8\");\n }\n out(chalk.green(` ✓ ${indexPath}`));\n }\n\n out(\"\");\n if (skippedFiles.length > 0) {\n out(chalk.yellow(`⚠ Skipped ${skippedFiles.length} existing file(s): ${skippedFiles.join(\", \")}`));\n out(chalk.gray(\" Use --force to overwrite existing files.\"));\n out(\"\");\n }\n out(chalk.bold.green(`✓ Introspected ${tablesMap.size} tables — generated ${generatedFiles.length} collection(s).`));\n out(chalk.gray(` Review the generated files in ${outDir} and customize properties as needed.`));\n out(\"\");\n\n } catch (e) {\n outError(chalk.red(`✗ Error introspecting database: ${e instanceof Error ? e.message : String(e)}`));\n process.exit(1);\n } finally {\n await client.end();\n }\n}\n\nmain().catch((err) => {\n outError(String(err));\n process.exit(1);\n});\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ export type BaselinePlan =
2
+ /** No history: `migrate apply --baseline <version>`, Atlas's own mechanism. */
3
+ {
4
+ kind: "atlas-baseline";
5
+ }
6
+ /** History behind `<version>`: `migrate set <version>`, then a plain apply. */
7
+ | {
8
+ kind: "set";
9
+ latest: string;
10
+ }
11
+ /** History at or past `<version>`: nothing to record, a plain apply. */
12
+ | {
13
+ kind: "already-recorded";
14
+ latest: string;
15
+ };
16
+ /**
17
+ * Atlas's version order for the versions Rebase writes: numeric timestamps,
18
+ * compared as numbers so a shorter one is not read as later; anything else as
19
+ * text.
20
+ */
21
+ export declare function compareMigrationVersions(a: string, b: string): number;
22
+ /** How to keep `--baseline <version>`'s promise, given the newest recorded version. */
23
+ export declare function planBaseline(version: string, latestRecorded: string | null): BaselinePlan;
24
+ /** What the command says before it migrates, for each plan. */
25
+ export declare function describeBaselinePlan(version: string, plan: BaselinePlan): string;
26
+ /**
27
+ * The newest version Atlas has recorded on this database, or `null` when it
28
+ * has recorded none (no revisions table yet included).
29
+ *
30
+ * The table is `rebase.atlas_schema_revisions`: the CLI passes
31
+ * `--revisions-schema rebase` to every `migrate apply`.
32
+ */
33
+ export declare function latestRecordedMigration(databaseUrl: string): Promise<string | null>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * What became of a migration `db generate` was finishing.
3
+ *
4
+ * `removed` says whether the half-finished file is gone; `rehashError` is set
5
+ * when it is gone but `atlas.sum` could not be brought back in line with the
6
+ * directory, which `atlas migrate` then refuses with a checksum error — loud,
7
+ * which is the point.
8
+ */
9
+ export type MigrationPostProcessOutcome = {
10
+ ok: true;
11
+ } | {
12
+ ok: false;
13
+ error: string;
14
+ removed: boolean;
15
+ rehashError?: string;
16
+ };
17
+ /**
18
+ * Run the steps that finish a migration Atlas just wrote — the search, vector
19
+ * and trigger DDL, the RLS bootstrap and policies, the re-hash — and take the
20
+ * migration back out when any of them fails.
21
+ *
22
+ * Atlas's own file creates the tables and nothing else: no `ENABLE ROW LEVEL
23
+ * SECURITY`, no policy. Those arrive in the append. A failure here used to be a
24
+ * yellow warning followed by "You can now run rebase db migrate", and when it
25
+ * struck before the file was rewritten the checksum still matched — so the
26
+ * migration applied cleanly and created every new table with RLS off while
27
+ * the schema's default privileges handed `rebase_user` full DML on it. On a
28
+ * deployment that runs migrations and never provisions at boot
29
+ * (`REBASE_MIGRATE_ON_BOOT=none`), nothing closed it again.
30
+ *
31
+ * Deleting the file is safe because it is the one this run created: running
32
+ * `db generate` again, once the cause is fixed, writes the same diff.
33
+ */
34
+ export declare function finishNewMigration(file: string, steps: () => Promise<void>, rehash: () => Promise<void>): Promise<MigrationPostProcessOutcome>;
@@ -81,7 +81,83 @@ export interface ExistingSchema {
81
81
  columnTypes?: Map<string, string>;
82
82
  /** `schema.table.trigger` of every trigger that already exists. */
83
83
  triggers?: Set<string>;
84
+ /**
85
+ * `schema.name` of every index (any relation of kind `i`/`I`) that already
86
+ * exists. Absent means unknown, and every index is planned as before —
87
+ * `IF NOT EXISTS` makes that correct, just not free.
88
+ */
89
+ indexes?: Set<string>;
90
+ /**
91
+ * `schema.name` of every index whose `pg_index.indisvalid` is false — the
92
+ * husk a `CREATE INDEX CONCURRENTLY` leaves behind when it is cancelled,
93
+ * times out, or loses a race with a peer. Such an index is in
94
+ * {@link indexes} too, maintained on every write and used by no query, and
95
+ * `IF NOT EXISTS` skips it by name forever. Absent means unknown.
96
+ */
97
+ invalidIndexes?: Set<string>;
98
+ /**
99
+ * Names of the extensions installed in the database. `CREATE EXTENSION IF
100
+ * NOT EXISTS` skips by name whatever schema the extension is in, so the name
101
+ * is the whole test. Absent means unknown.
102
+ */
103
+ extensions?: Set<string>;
104
+ /**
105
+ * {@link HELPER_FUNCTIONS} as they are in the catalogue: `schema.name(args)`
106
+ * → {@link describeHelperFunction} of what `CREATE OR REPLACE` would set.
107
+ * Absent means unknown, and every helper is re-created as before.
108
+ */
109
+ functions?: Map<string, string>;
84
110
  }
111
+ /**
112
+ * The functions the plan creates with `CREATE OR REPLACE`, which the catalogue
113
+ * reader looks up so a boot that finds them unchanged plans nothing.
114
+ *
115
+ * A helper missing from this list is not wrong, only re-created on every boot,
116
+ * which is what all of them were before.
117
+ */
118
+ export declare const HELPER_FUNCTIONS: readonly string[];
119
+ /**
120
+ * Everything `CREATE OR REPLACE FUNCTION` sets on a function, as one string.
121
+ *
122
+ * Built from the catalogue by the reader and from the statement by
123
+ * {@link parseHelperFunction}, so the two meet in one shape. It covers every
124
+ * attribute a `CREATE OR REPLACE` resets — body, language, volatility,
125
+ * strictness, parallel safety, security, leakproofness, cost and `SET` options —
126
+ * so a function someone altered by hand (`ALTER FUNCTION … VOLATILE`) still
127
+ * compares unequal and is re-created, exactly as the unconditional statement
128
+ * used to re-create it.
129
+ */
130
+ export interface HelperFunctionFacts {
131
+ returns: string;
132
+ language: string;
133
+ /** `provolatile`: `i`mmutable, `s`table, `v`olatile. */
134
+ volatility: string;
135
+ strict: boolean;
136
+ /** `proparallel`: `s`afe, `r`estricted, `u`nsafe. */
137
+ parallel: string;
138
+ securityDefiner: boolean;
139
+ leakproof: boolean;
140
+ cost: number;
141
+ /** Whether the function carries any `SET` options (`proconfig`). */
142
+ hasConfig: boolean;
143
+ /** `prosrc`: the text between the dollar quotes, exactly. */
144
+ body: string;
145
+ }
146
+ export declare const helperFunctionKey: (schema: string, name: string, args: string) => string;
147
+ export declare const describeHelperFunction: (facts: HelperFunctionFacts) => string;
148
+ /**
149
+ * What a helper's `CREATE OR REPLACE FUNCTION` statement would leave in the
150
+ * catalogue, or `undefined` for a statement this does not recognise — which is
151
+ * then planned unconditionally, as every statement was before.
152
+ *
153
+ * Only ever given statements this package renders (`searchHelperFunctions`,
154
+ * `setUpdatedAtFunction`), so a strict pattern is the right tool: anything it
155
+ * does not match exactly falls back to the safe behaviour.
156
+ */
157
+ export declare function parseHelperFunction(statement: string): {
158
+ key: string;
159
+ definition: string;
160
+ } | undefined;
85
161
  /**
86
162
  * How far the planner may go in making the database's constraints match the
87
163
  * configuration.
@@ -100,6 +176,18 @@ export type ConstraintPolicy = "additive" | "converge";
100
176
  export interface DiffOptions {
101
177
  /** Defaults to `additive`. See {@link ConstraintPolicy}. */
102
178
  constraints?: ConstraintPolicy;
179
+ /**
180
+ * Plan every index a table that already exists needs — declared, search
181
+ * and vector, missing or left INVALID under its name — as a
182
+ * {@link DeferredIndexBuild} rather than an action, as the derived
183
+ * foreign-key indexes always are. `ensureCollectionTables` sets it: it
184
+ * hands them to `buildDeferredIndexes`, which builds them on a connection
185
+ * of its own with no `statement_timeout`, and drops and rebuilds a husk.
186
+ *
187
+ * Absent — the live schema editor, which applies `statements` as text —
188
+ * they stay `CREATE INDEX CONCURRENTLY IF NOT EXISTS` actions, as before.
189
+ */
190
+ deferLiveIndexes?: boolean;
103
191
  }
104
192
  export interface EnsureAction {
105
193
  kind: "create-enum" | "create-table" | "enable-rls" | "add-column" | "add-constraint" | "rename-column" | "create-extension" | "create-function" | "create-index" | "comment-column" | "add-enum-value" | "set-not-null" | "drop-not-null" | "set-default" | "create-trigger";
@@ -166,6 +254,42 @@ export interface LegacyForeignKey {
166
254
  /** The name the old rule derived, which the table already has. */
167
255
  legacy: string;
168
256
  }
257
+ /**
258
+ * An index a table that already exists is missing, or holds INVALID — a
259
+ * derived foreign-key index always, and under
260
+ * {@link DiffOptions.deferLiveIndexes} a declared, search or vector one too.
261
+ *
262
+ * Not an {@link EnsureAction}, because it must not hold the boot: on a table
263
+ * with millions of rows `CREATE INDEX CONCURRENTLY` takes minutes, and every
264
+ * minute of it would be a minute the deployment is not serving. The caller
265
+ * builds these after the fact — boot in the background once it serves, every
266
+ * other caller inline after the actions — through `buildDeferredIndexes`. A
267
+ * {@link unique} one is the exception: it is built before the ensure returns.
268
+ *
269
+ * Kept out of {@link EnsurePlan.statements} for the same reason: a caller that
270
+ * applies statements as text (the live schema editor) may run them inside a
271
+ * transaction, which `CONCURRENTLY` refuses.
272
+ */
273
+ export interface DeferredIndexBuild {
274
+ schema: string;
275
+ /** Bare table name. */
276
+ table: string;
277
+ indexName: string;
278
+ /** `CREATE [UNIQUE] INDEX CONCURRENTLY IF NOT EXISTS …;` */
279
+ sql: string;
280
+ /**
281
+ * The catalogue already holds an INVALID index under this name, which has
282
+ * to be dropped before the build — `IF NOT EXISTS` would skip it.
283
+ */
284
+ invalid: boolean;
285
+ /**
286
+ * A declared `unique: true` index. Not an optimization but the collection
287
+ * promising two rows cannot share a value, so `ensureCollectionTables`
288
+ * builds it before it returns — never after the server is serving — and
289
+ * fails when it cannot, as it did when this was an action. Absent is false.
290
+ */
291
+ unique?: boolean;
292
+ }
169
293
  export interface EnsurePlan {
170
294
  actions: EnsureAction[];
171
295
  /** Every statement, in dependency order. Empty when the schema is current. */
@@ -217,5 +341,7 @@ export interface EnsurePlan {
217
341
  * exactly once, which is not a decision to take unattended.
218
342
  */
219
343
  orphanedRequiredColumns: OrphanedRequiredColumn[];
344
+ /** @see DeferredIndexBuild */
345
+ deferredIndexes: DeferredIndexBuild[];
220
346
  }
221
347
  export declare function diffPlanAgainstCatalogue(plan: SchemaPlan, existing: ExistingSchema, options?: DiffOptions): EnsurePlan;
@@ -51,7 +51,8 @@ export declare function renderSearchDdl(plan: SchemaPlan): string;
51
51
  * two-part form is what Atlas wants when the connection URL scopes it to one
52
52
  * schema and is *silently ignored* otherwise: it reads `posts.search_vector` as
53
53
  * a table named `search_vector` in a schema named `posts`, matches nothing, and
54
- * reports no error for the pattern that never fired.
54
+ * reports no error for the pattern that never fired. Each name is spelled by
55
+ * `atlasExcludePattern`, so one holding a `.` or glob syntax still matches.
55
56
  */
56
57
  export declare const searchExcludePatternsOf: (plan: SchemaPlan) => string[];
57
58
  export declare function renderVectorDdl(plan: SchemaPlan): string;
@@ -280,6 +280,14 @@ export interface PolicyPlan {
280
280
  * what `securityRules` reads like — `policies.sql` says so in a comment.
281
281
  */
282
282
  injected: boolean;
283
+ /**
284
+ * Policy names, as Postgres stored them, that this policy replaces — set
285
+ * only on the first policy of a rule whose per-operation names used to
286
+ * collide at 63 bytes (see `getPolicySupersededNames`). Rendered as a
287
+ * `DROP POLICY IF EXISTS` ahead of the policy, so a boot or push swaps the
288
+ * one truncated survivor for a policy per operation. Absent otherwise.
289
+ */
290
+ supersedes?: string[];
283
291
  }
284
292
  /** A `BEFORE UPDATE` trigger stamping `now()` onto an `on_update` column. */
285
293
  export interface TriggerPlan {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The foreign-key indexes `rebase db push` builds before Atlas plans.
3
+ *
4
+ * Rebase derives a btree for every `belongsTo` key and every junction's target
5
+ * column (15b00afcd). On a table that already exists — every table of a
6
+ * project upgraded across that release — the index is missing, and a push left
7
+ * it to Atlas, which builds it inside the push's one transaction: a plain
8
+ * `CREATE INDEX`, holding writes to the table for the whole build, about 0.4 s
9
+ * at a million rows and 6 s at ten million. Atlas's own concurrent mode would
10
+ * run the entire apply outside a transaction, which the push relies on.
11
+ *
12
+ * So the push builds those first, `CONCURRENTLY`, with the builder the boot
13
+ * uses for the same indexes (`deferred-index-build.ts`: one at a time, a lock
14
+ * budget for getting the table, an INVALID leftover dropped and rebuilt, a
15
+ * claim shared with booting instances). Atlas then finds them and plans
16
+ * nothing for them. One it could not build is still Atlas's, as before.
17
+ *
18
+ * Only an index whose columns are already there: a column this push adds is
19
+ * created by Atlas, and its index with it, on a column that holds no values.
20
+ */
21
+ import { type CollectionConfig } from "@rebasepro/types";
22
+ import { type DeferredIndexBuild } from "./plan/diff-plan.js";
23
+ import { type Queryable } from "./ensure-collection-tables.js";
24
+ /** The derived foreign-key indexes a live table lacks (or holds INVALID) whose columns it already has. */
25
+ export declare function missingForeignKeyIndexes(client: Queryable, collections: CollectionConfig[]): Promise<DeferredIndexBuild[]>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The relation columns an aged database still holds under the name Rebase
3
+ * derived before 0.13 (`categorie_id`, `people_id`), for `db push` to rename
4
+ * before Atlas plans.
5
+ *
6
+ * Boot renames them losslessly (`diff-plan.ts`, `renameLegacyColumn`). Push
7
+ * runs Atlas, which sees only "drop `categorie_id`, add `category_id` NOT
8
+ * NULL": it listed the links as destructive changes to approve, and with
9
+ * `--allow-destructive` dropped them. A pipeline that runs only `db push`
10
+ * could not upgrade an aged database at all. Renaming first — the same
11
+ * metadata-only statement boot runs — leaves Atlas nothing to plan.
12
+ */
13
+ import { type CollectionConfig } from "@rebasepro/types";
14
+ import { type LegacyForeignKey } from "./plan/diff-plan.js";
15
+ import { type Queryable } from "./ensure-collection-tables.js";
16
+ export interface LegacyRename extends LegacyForeignKey {
17
+ /** `ALTER TABLE … RENAME COLUMN …;` */
18
+ sql: string;
19
+ }
20
+ export declare function legacyForeignKeyRenames(client: Queryable, collections: CollectionConfig[]): Promise<LegacyRename[]>;
@@ -45,6 +45,21 @@
45
45
  * a parser for a problem that does not need one.
46
46
  */
47
47
  export declare const RLS_BOOTSTRAP_STATEMENTS: readonly string[];
48
+ /**
49
+ * Whether the `rebase` schema and every RLS helper function are exactly what
50
+ * {@link RLS_BOOTSTRAP_STATEMENTS} would make them — one catalogue read.
51
+ *
52
+ * The boot ran those statements unconditionally, on every boot and on every
53
+ * source: four `CREATE OR REPLACE FUNCTION`s in a transaction under an
54
+ * advisory lock. Replacing a function rewrites its `pg_proc` row even when
55
+ * nothing changed, and every cached plan that inlined it — every plan of every
56
+ * statement a policy applies to, on every connection — is invalidated by it.
57
+ * A function that differs in anything `CREATE OR REPLACE` sets (body,
58
+ * language, volatility, strictness, parallel safety, security, leakproofness,
59
+ * cost, `SET` options), or is missing, answers `false`, and the statements run
60
+ * as before.
61
+ */
62
+ export declare function rlsRuntimeIsCurrent(run: (sql: string) => Promise<Record<string, unknown>[]>): Promise<boolean>;
48
63
  /** The same statements as one script, for migration files and raw clients. */
49
64
  export declare const RLS_BOOTSTRAP_SQL: string;
50
65
  /**
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The constraints a collection cannot declare, and how `db push` keeps Atlas
3
+ * from dropping them.
4
+ *
5
+ * `db push` is declarative: Atlas makes each managed table match `schema.sql`,
6
+ * and anything on the table that `schema.sql` does not mention is planned as a
7
+ * drop. The collection model declares a table's primary key, its columns'
8
+ * `NOT NULL`, `UNIQUE` and foreign keys, and nothing else. So a hand-written
9
+ * `CHECK (total >= 0)` — or one an adopted database arrived with, which
10
+ * `schema introspect` reads as `validation.min: 0` — was dropped by the first
11
+ * push, in the same `ALTER TABLE` as whatever else the push changed, with no
12
+ * prompt. From then on the database accepted every row the constraint used to
13
+ * refuse, while the form still enforced the rule and so hid that it had gone.
14
+ *
15
+ * EXCLUDE constraints are the same case: Atlas inspects them and plans their
16
+ * drop the same way. Triggers and functions are not: Atlas's free tier does
17
+ * not inspect either, so it never plans a drop for one. Measured on the pinned
18
+ * atlas 1.2.3 against PG 18; `introspect-check-constraints-e2e` pins all four.
19
+ *
20
+ * Ownership, as with indexes (`getForeignIndexExcludes` in `cli-helpers.ts`)
21
+ * and policies: a constraint the desired state declares is
22
+ * Atlas's to manage, and every other CHECK or EXCLUDE is somebody else's and is
23
+ * kept out of the plan with `--exclude`. Today the desired state declares
24
+ * neither kind, so every one is kept. A rule nobody wants any more is dropped
25
+ * by hand (`ALTER TABLE … DROP CONSTRAINT …`), which is the only statement that
26
+ * can say so.
27
+ *
28
+ * Side-effect free, so it is unit-tested without Atlas or a database.
29
+ */
30
+ /** A constraint kind the collection model has no way to declare. */
31
+ export type UndeclarableConstraintKind = "check" | "exclude";
32
+ /** A CHECK or EXCLUDE constraint, as the database holds it now. */
33
+ export interface ExistingConstraint {
34
+ schema: string;
35
+ table: string;
36
+ name: string;
37
+ kind: UndeclarableConstraintKind;
38
+ }
39
+ /**
40
+ * Every CHECK and EXCLUDE constraint on an ordinary or partitioned table
41
+ * outside the system schemas.
42
+ *
43
+ * `contype` `c` and `x` only. A primary key (`p`), `UNIQUE` (`u`), foreign key
44
+ * (`f`) and, from PG 18, a catalogued `NOT NULL` (`n`) are what a collection
45
+ * declares, so Atlas keeps managing them.
46
+ */
47
+ export declare const UNDECLARABLE_CONSTRAINTS_SQL = "\n SELECT n.nspname AS \"schema\",\n c.relname AS \"table\",\n con.conname AS \"name\",\n CASE con.contype WHEN 'c' THEN 'check' ELSE 'exclude' END AS \"kind\"\n FROM pg_constraint con\n JOIN pg_class c ON c.oid = con.conrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE con.contype IN ('c', 'x')\n AND c.relkind IN ('r', 'p')\n AND n.nspname NOT IN ('pg_catalog', 'information_schema')\n AND n.nspname NOT LIKE 'pg\\_%'\n";
48
+ /**
49
+ * The CHECK and EXCLUDE constraint names a desired state (`schema.sql`)
50
+ * declares — Atlas's to manage, and so never excluded.
51
+ */
52
+ export declare function declaredConstraintNames(desiredSql: string): Set<string>;
53
+ /**
54
+ * The `--exclude` patterns that keep Atlas from dropping the CHECK and EXCLUDE
55
+ * constraints the desired state does not declare. Three-part
56
+ * `schema.table.constraint`, which is what Atlas matches a constraint by: a
57
+ * two-part pattern reads as a table, matches nothing, and reports no error.
58
+ */
59
+ export declare function undeclaredConstraintExcludes(existing: readonly ExistingConstraint[], desiredSql: string): string[];