@rebasepro/server-postgres 0.23.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 (208) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-DGPL6G_C.js → BranchService-UgKa416x.js} +47 -19
  3. package/dist/BranchService-UgKa416x.js.map +1 -0
  4. package/dist/PostgresBackendDriver.d.ts +187 -13
  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 +78 -14
  8. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  9. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  10. package/dist/backup/backup-cli.d.ts +75 -2
  11. package/dist/backup/backup-service.d.ts +2 -2
  12. package/dist/backup/pg-binary.d.ts +5 -0
  13. package/dist/{backup-cli-24v4OlSp.js → backup-cli-yiKd1GsC.js} +168 -75
  14. package/dist/backup-cli-yiKd1GsC.js.map +1 -0
  15. package/dist/{backup-service-HQ9GC4tN.js → backup-service-AZWc2wvI.js} +104 -35
  16. package/dist/backup-service-AZWc2wvI.js.map +1 -0
  17. package/dist/boot-ddl-B9ZmBJ75.js +105 -0
  18. package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
  19. package/dist/cli-console-DGee3Zl7.js +225 -0
  20. package/dist/cli-console-DGee3Zl7.js.map +1 -0
  21. package/dist/cli-console.d.ts +39 -0
  22. package/dist/cli-errors-oVUuD8k0.js +2180 -0
  23. package/dist/cli-errors-oVUuD8k0.js.map +1 -0
  24. package/dist/cli-errors.d.ts +133 -9
  25. package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
  26. package/dist/cli-flags-DnPHXjRH.js.map +1 -0
  27. package/dist/cli-flags.d.ts +15 -0
  28. package/dist/cli-helpers.d.ts +88 -34
  29. package/dist/cli-output.d.ts +16 -28
  30. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  31. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  32. package/dist/cli-prompt.d.ts +19 -0
  33. package/dist/cli-push.d.ts +87 -0
  34. package/dist/cli-scratch-database.d.ts +33 -8
  35. package/dist/cli.js +826 -230
  36. package/dist/cli.js.map +1 -1
  37. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-D_V4oaZh.js} +43 -27
  38. package/dist/column-plan-helpers-D_V4oaZh.js.map +1 -0
  39. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
  40. package/dist/connection-C938nR3E.js.map +1 -0
  41. package/dist/connection.d.ts +13 -2
  42. package/dist/data-transformer.d.ts +0 -8
  43. package/dist/databasePoolManager-DZgnBOgj.js +119 -0
  44. package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
  45. package/dist/deferred-index-build-BqmEHGaD.js +1051 -0
  46. package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
  47. package/dist/{doctor-Cb2thZ8s.js → doctor-CUAAOFZm.js} +281 -89
  48. package/dist/doctor-CUAAOFZm.js.map +1 -0
  49. package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
  50. package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
  51. package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
  52. package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
  53. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-DMPWhJ6R.js} +180 -77
  54. package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
  55. package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
  56. package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
  57. package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
  58. package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
  59. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +29 -11
  60. package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
  61. package/dist/generate-files-OoRJLEdk.js +199 -0
  62. package/dist/generate-files-OoRJLEdk.js.map +1 -0
  63. package/dist/generate-postgres-ddl-logic-B4k9djAV.js +152 -0
  64. package/dist/generate-postgres-ddl-logic-B4k9djAV.js.map +1 -0
  65. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
  66. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
  67. package/dist/generated-sql.d.ts +34 -0
  68. package/dist/history/HistoryService.d.ts +64 -5
  69. package/dist/history/ensure-history-table.d.ts +63 -4
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.es.js +139 -16738
  72. package/dist/index.es.js.map +1 -1
  73. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-BCc1Jym2.js} +654 -353
  74. package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
  75. package/dist/introspect-db-queries-BhhJg0b8.js +364 -0
  76. package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
  77. package/dist/open-rebase-CAjWpPjs.js +20895 -0
  78. package/dist/open-rebase-CAjWpPjs.js.map +1 -0
  79. package/dist/open-rebase.d.ts +94 -0
  80. package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
  81. package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
  82. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-CsNgK0qi.js} +279 -534
  83. package/dist/plan-schema-CsNgK0qi.js.map +1 -0
  84. package/dist/{policy-drift-DljYdrpW.js → policy-drift-BKC50y2p.js} +33 -9
  85. package/dist/policy-drift-BKC50y2p.js.map +1 -0
  86. package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
  87. package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
  88. package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
  89. package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
  90. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CrfEe__B.js} +24 -157
  91. package/dist/render-ddl-CrfEe__B.js.map +1 -0
  92. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1101
  93. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  94. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-D7P4-ZYX.js} +138 -18
  95. package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
  96. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  97. package/dist/schema/atlas-exclude.d.ts +42 -0
  98. package/dist/schema/auth-schema.d.ts +172 -2
  99. package/dist/schema/boot-ddl.d.ts +47 -0
  100. package/dist/schema/carved-out-migration.d.ts +5 -4
  101. package/dist/schema/classify-change.d.ts +29 -1
  102. package/dist/schema/collection-index.d.ts +45 -0
  103. package/dist/schema/column-plan-helpers.d.ts +38 -21
  104. package/dist/schema/deferred-index-build.d.ts +131 -0
  105. package/dist/schema/destructive-sql.d.ts +50 -2
  106. package/dist/schema/doctor-cli.js +110 -6
  107. package/dist/schema/doctor-cli.js.map +1 -1
  108. package/dist/schema/doctor-search-scale.d.ts +45 -0
  109. package/dist/schema/doctor.d.ts +53 -1
  110. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  111. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  112. package/dist/schema/enum-labels-kept.d.ts +26 -0
  113. package/dist/schema/generate-drizzle-schema.js +1 -1
  114. package/dist/schema/generate-files.d.ts +44 -0
  115. package/dist/schema/generate-next-step.d.ts +0 -13
  116. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  117. package/dist/schema/generate-postgres-ddl.js +10 -81
  118. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  119. package/dist/schema/generate-schema-commit.d.ts +12 -0
  120. package/dist/schema/generated-schema-diff.d.ts +7 -0
  121. package/dist/schema/introspect-db-logic.d.ts +63 -0
  122. package/dist/schema/introspect-db-queries.d.ts +21 -2
  123. package/dist/schema/introspect-db-search.d.ts +22 -0
  124. package/dist/schema/introspect-db-storage.d.ts +83 -0
  125. package/dist/schema/introspect-db-structure.d.ts +10 -0
  126. package/dist/schema/introspect-db.js +29 -21
  127. package/dist/schema/introspect-db.js.map +1 -1
  128. package/dist/schema/migrate-baseline.d.ts +33 -0
  129. package/dist/schema/migration-post-process.d.ts +34 -0
  130. package/dist/schema/plan/diff-plan.d.ts +127 -1
  131. package/dist/schema/plan/plan-schema.d.ts +16 -8
  132. package/dist/schema/plan/render-ddl.d.ts +8 -1
  133. package/dist/schema/plan/types.d.ts +45 -11
  134. package/dist/schema/push-index-prebuild.d.ts +25 -0
  135. package/dist/schema/push-legacy-renames.d.ts +20 -0
  136. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  137. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  138. package/dist/search-column-BM-GV6vH.js +442 -0
  139. package/dist/search-column-BM-GV6vH.js.map +1 -0
  140. package/dist/security/policy-drift.d.ts +1 -1
  141. package/dist/security/rls-enforcement.d.ts +39 -4
  142. package/dist/services/BranchService.d.ts +29 -1
  143. package/dist/services/FetchService.d.ts +63 -12
  144. package/dist/services/PersistService.d.ts +15 -0
  145. package/dist/services/RelationService.d.ts +106 -3
  146. package/dist/services/cdc/CdcListener.d.ts +26 -10
  147. package/dist/services/cdc/identity-columns.d.ts +19 -0
  148. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  149. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  150. package/dist/services/channel-bus/index.d.ts +1 -1
  151. package/dist/services/channel-history.d.ts +32 -5
  152. package/dist/services/collection-helpers.d.ts +26 -1
  153. package/dist/services/field-viewer.d.ts +15 -1
  154. package/dist/services/pg-notify-listener.d.ts +95 -6
  155. package/dist/services/realtimeService.d.ts +467 -53
  156. package/dist/services/row-pipeline.d.ts +16 -9
  157. package/dist/services/socket-liveness.d.ts +85 -0
  158. package/dist/services/soft-delete.d.ts +12 -0
  159. package/dist/services/sql-script.d.ts +275 -0
  160. package/dist/services/vector-iterative-scan.d.ts +40 -0
  161. package/dist/services/write-depth.d.ts +22 -0
  162. package/dist/services/write-transaction-scope.d.ts +15 -3
  163. package/dist/socket-opt-ins.d.ts +64 -0
  164. package/dist/source-DB6xJv3p.js +873 -0
  165. package/dist/source-DB6xJv3p.js.map +1 -0
  166. package/dist/tsx-loader.d.ts +14 -0
  167. package/dist/utils/connection-string.d.ts +31 -0
  168. package/dist/utils/drizzle-conditions.d.ts +63 -3
  169. package/dist/utils/error-cause.d.ts +7 -0
  170. package/dist/utils/pg-error-utils.d.ts +2 -4
  171. package/dist/utils/result-rows.d.ts +10 -0
  172. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  173. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  174. package/dist/websocket-DUiiwQd2.js +1632 -0
  175. package/dist/websocket-DUiiwQd2.js.map +1 -0
  176. package/dist/websocket.d.ts +63 -18
  177. package/package.json +7 -7
  178. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  179. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  180. package/dist/arg-Dni7MzLB.js.map +0 -1
  181. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  182. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  183. package/dist/cli-errors-B8qHg02P.js +0 -798
  184. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  185. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  186. package/dist/cli-output-CNdMql-L.js +0 -47
  187. package/dist/cli-output-CNdMql-L.js.map +0 -1
  188. package/dist/collection-index-DdnsxiJ_.js +0 -286
  189. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  190. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  191. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  192. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  193. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  194. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  195. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  196. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  197. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  198. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  199. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  200. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  201. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  202. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  203. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  204. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  205. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  206. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  207. package/dist/source-Br7L7GOI.js +0 -410
  208. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -1,94 +1,23 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { a as generatePostgresVectorDdl, i as generatePostgresTriggersDdl, n as generatePostgresPoliciesDdl, r as generatePostgresSearchDdl, t as generatePostgresDdl } from "../generate-postgres-ddl-logic-Bt2d2mRH.js";
4
- import { n as outError, t as out } from "../cli-output-CNdMql-L.js";
5
- import { loadCollectionsFromDirectory } from "@rebasepro/server";
6
- import { declaredDatabaseExtensions } from "@rebasepro/types";
7
- import * as fs$1 from "fs";
8
- import { promises } from "fs";
3
+ import { r as out } from "../source-DB6xJv3p.js";
4
+ import { r as writePostgresDdl } from "../generate-files-OoRJLEdk.js";
9
5
  import path from "path";
10
- import { pathToFileURL } from "url";
11
6
  //#region src/schema/generate-postgres-ddl.ts
12
- /** Where a project declares its resources, relative to the config directory. */
13
- var RESOURCE_ENTRIES = [
14
- "resources.ts",
15
- "resources.js",
16
- "resources/index.ts",
17
- "resources/index.js"
18
- ];
19
7
  /**
20
- * Evaluate the project's `resources.ts`, so `declaredDatabaseExtensions()` can
21
- * answer.
22
- *
23
- * This generator runs as its own tsx subprocess and loads only the collections
24
- * directory, so nothing else in it would ever import the resources module — and
25
- * a permission that reads as absent because nobody loaded it is the worst shape
26
- * this could take: the push succeeds, `vector.sql` quietly omits the
27
- * `CREATE EXTENSION`, and the failure lands one statement later looking like a
28
- * database problem.
29
- *
30
- * Imported directly rather than through the config index, for the reason
31
- * `boot/resource-loading.ts` gives: re-exporting from the index would work only
32
- * for projects that remember to.
33
- *
34
- * A project with no resources module is the normal case and declares nothing.
35
- * An import that *throws* is not swallowed — a resources module that does not
36
- * evaluate is a configuration error, and treating it as "declared nothing"
37
- * would turn it into a silently missing extension.
8
+ * The generator as its own process — what `--watch` runs, because a watch has
9
+ * to re-import the collections on every save and Node's module cache would
10
+ * serve it the first version forever. One-shot generation runs in the driver's
11
+ * own process instead; see `generate-files.ts`.
38
12
  */
39
- async function loadDeclaredResources(configDir) {
40
- const entry = RESOURCE_ENTRIES.map((name) => path.join(configDir, name)).find((candidate) => fs$1.existsSync(candidate));
41
- if (!entry) return;
42
- await import(pathToFileURL(entry).href);
43
- }
44
13
  var runGeneration = async (collectionsFilePath, outputPath) => {
14
+ if (!collectionsFilePath) throw new Error("No collections file path provided.");
45
15
  try {
46
- if (!collectionsFilePath) throw new Error("No collections file path provided.");
47
- const resolvedPath = path.resolve(collectionsFilePath);
48
- await loadDeclaredResources(path.dirname(resolvedPath));
49
- let collections = await loadCollectionsFromDirectory(resolvedPath);
50
- if (!collections || !Array.isArray(collections)) collections = [];
51
- collections.sort((a, b) => a.slug.localeCompare(b.slug));
52
- const ddlContent = generatePostgresDdl(collections, {
53
- includePolicies: false,
54
- includeSearch: false,
55
- includeVector: false
16
+ await writePostgresDdl({
17
+ collectionsPath: path.resolve(collectionsFilePath),
18
+ outputPath
56
19
  });
57
- const policiesContent = generatePostgresPoliciesDdl(collections);
58
- const searchContent = generatePostgresSearchDdl(collections);
59
- const vectorContent = generatePostgresVectorDdl(collections, { extensions: declaredDatabaseExtensions() });
60
- const triggersContent = generatePostgresTriggersDdl(collections);
61
- if (outputPath) {
62
- const outputDir = path.dirname(outputPath);
63
- await promises.mkdir(outputDir, { recursive: true });
64
- await promises.writeFile(outputPath, ddlContent);
65
- out(`✅ PostgreSQL DDL generated successfully at ${outputPath}`);
66
- const policiesPath = path.join(outputDir, "policies.sql");
67
- await promises.writeFile(policiesPath, policiesContent);
68
- out(`✅ PostgreSQL Policies DDL generated successfully at ${policiesPath}`);
69
- const searchPath = path.join(outputDir, "search.sql");
70
- if (searchContent) {
71
- await promises.writeFile(searchPath, searchContent);
72
- out(`✅ PostgreSQL search DDL generated successfully at ${searchPath}`);
73
- } else if (fs$1.existsSync(searchPath)) await promises.rm(searchPath);
74
- const vectorPath = path.join(outputDir, "vector.sql");
75
- if (vectorContent) {
76
- await promises.writeFile(vectorPath, vectorContent);
77
- out(`✅ PostgreSQL vector DDL generated successfully at ${vectorPath}`);
78
- } else if (fs$1.existsSync(vectorPath)) await promises.rm(vectorPath);
79
- const triggersPath = path.join(outputDir, "triggers.sql");
80
- if (triggersContent) {
81
- await promises.writeFile(triggersPath, triggersContent);
82
- out(`✅ PostgreSQL trigger DDL generated successfully at ${triggersPath}`);
83
- } else if (fs$1.existsSync(triggersPath)) await promises.rm(triggersPath);
84
- } else {
85
- out("✅ PostgreSQL DDL generated successfully.");
86
- out(String(ddlContent));
87
- out("\n✅ PostgreSQL Policies DDL generated successfully.");
88
- out(String(policiesContent));
89
- }
90
20
  } catch (error) {
91
- outError(`Error generating DDL schema: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
92
21
  process.exitCode = 1;
93
22
  throw error;
94
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-postgres-ddl.js","names":[],"sources":["../../src/schema/generate-postgres-ddl.ts","../../src/schema/bin/generate-postgres-ddl.ts"],"sourcesContent":["import { promises as fsPromises } from \"fs\";\nimport * as fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport {\n generatePostgresDdl,\n generatePostgresPoliciesDdl,\n generatePostgresSearchDdl,\n generatePostgresTriggersDdl,\n generatePostgresVectorDdl\n} from \"./generate-postgres-ddl-logic\";\nimport { CollectionConfig, declaredDatabaseExtensions } from \"@rebasepro/types\";\nimport { loadCollectionsFromDirectory } from \"@rebasepro/server\";\nimport { out, outError } from \"../cli-output\";\n\n/** Where a project declares its resources, relative to the config directory. */\nconst RESOURCE_ENTRIES = [\"resources.ts\", \"resources.js\", \"resources/index.ts\", \"resources/index.js\"];\n\n/**\n * Evaluate the project's `resources.ts`, so `declaredDatabaseExtensions()` can\n * answer.\n *\n * This generator runs as its own tsx subprocess and loads only the collections\n * directory, so nothing else in it would ever import the resources module — and\n * a permission that reads as absent because nobody loaded it is the worst shape\n * this could take: the push succeeds, `vector.sql` quietly omits the\n * `CREATE EXTENSION`, and the failure lands one statement later looking like a\n * database problem.\n *\n * Imported directly rather than through the config index, for the reason\n * `boot/resource-loading.ts` gives: re-exporting from the index would work only\n * for projects that remember to.\n *\n * A project with no resources module is the normal case and declares nothing.\n * An import that *throws* is not swallowed — a resources module that does not\n * evaluate is a configuration error, and treating it as \"declared nothing\"\n * would turn it into a silently missing extension.\n */\nasync function loadDeclaredResources(configDir: string): Promise<void> {\n const entry = RESOURCE_ENTRIES\n .map(name => path.join(configDir, name))\n .find(candidate => fs.existsSync(candidate));\n if (!entry) return;\n await import(pathToFileURL(entry).href);\n}\n\nconst runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {\n try {\n if (!collectionsFilePath) {\n throw new Error(\"No collections file path provided.\");\n }\n\n const resolvedPath = path.resolve(collectionsFilePath);\n\n // `<config>/collections` is the path this is given, so the config\n // directory is its parent — the same relationship `rebase resources`\n // and the boot loader assume.\n await loadDeclaredResources(path.dirname(resolvedPath));\n\n // Shared with the runtime and the doctor: what gets generated here must\n // be exactly what the server serves, including directory-level defaults.\n let collections: CollectionConfig[] = await loadCollectionsFromDirectory(resolvedPath);\n\n\n if (!collections || !Array.isArray(collections)) {\n collections = [];\n }\n\n // Sort collections by slug alphabetically to ensure deterministic DDL generation\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\n\n // `schema.sql` is Atlas's desired state, and it carries neither the RLS\n // policies, nor the search apparatus, nor the vector columns: Atlas\n // manages none of them, and in the search and vector cases cannot. All\n // three are written beside it and applied by the CLI in their own right.\n const ddlContent = generatePostgresDdl(collections, {\n includePolicies: false,\n includeSearch: false,\n includeVector: false\n });\n const policiesContent = generatePostgresPoliciesDdl(collections);\n const searchContent = generatePostgresSearchDdl(collections);\n const vectorContent = generatePostgresVectorDdl(collections, {\n extensions: declaredDatabaseExtensions()\n });\n const triggersContent = generatePostgresTriggersDdl(collections);\n\n if (outputPath) {\n const outputDir = path.dirname(outputPath);\n await fsPromises.mkdir(outputDir, { recursive: true });\n await fsPromises.writeFile(outputPath, ddlContent);\n out(`✅ PostgreSQL DDL generated successfully at ${outputPath}`);\n\n const policiesPath = path.join(outputDir, \"policies.sql\");\n await fsPromises.writeFile(policiesPath, policiesContent);\n out(`✅ PostgreSQL Policies DDL generated successfully at ${policiesPath}`);\n\n // Removed when the last `search` block goes: the CLI applies this\n // file whenever it exists, and a stale one would keep re-creating\n // functions for collections that no longer want them.\n const searchPath = path.join(outputDir, \"search.sql\");\n if (searchContent) {\n await fsPromises.writeFile(searchPath, searchContent);\n out(`✅ PostgreSQL search DDL generated successfully at ${searchPath}`);\n } else if (fs.existsSync(searchPath)) {\n await fsPromises.rm(searchPath);\n }\n\n // Removed when the last `vector` property goes, for the same reason\n // `search.sql` is: the CLI applies whichever of these files exists,\n // and a stale one would keep re-adding a column the collections no\n // longer declare — which Atlas, no longer told to exclude it, would\n // then plan a DROP for on the very next push.\n const vectorPath = path.join(outputDir, \"vector.sql\");\n if (vectorContent) {\n await fsPromises.writeFile(vectorPath, vectorContent);\n out(`✅ PostgreSQL vector DDL generated successfully at ${vectorPath}`);\n } else if (fs.existsSync(vectorPath)) {\n await fsPromises.rm(vectorPath);\n }\n\n // `autoValue: \"on_update\"`, on the same terms as the two above: a\n // trigger is a function plus a binding, which Atlas's free tier\n // will not parse, and a stale file would keep re-installing a\n // trigger for a column nothing declares any more.\n const triggersPath = path.join(outputDir, \"triggers.sql\");\n if (triggersContent) {\n await fsPromises.writeFile(triggersPath, triggersContent);\n out(`✅ PostgreSQL trigger DDL generated successfully at ${triggersPath}`);\n } else if (fs.existsSync(triggersPath)) {\n await fsPromises.rm(triggersPath);\n }\n } else {\n out(\"✅ PostgreSQL DDL generated successfully.\");\n out(String(ddlContent));\n out(\"\\n✅ PostgreSQL Policies DDL generated successfully.\");\n out(String(policiesContent));\n }\n\n } catch (error) {\n outError(`Error generating DDL schema: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n // Step 1 of `rebase db push` is this process, and its exit code is the\n // only thing the push reads. Printing and exiting 0 made a resources\n // module that does not evaluate — the single most common cause here —\n // report `✓ rebase db push completed successfully` while Atlas applied\n // whatever `drizzle/schema.sql` happened to be on disk, sometimes\n // minutes old. With no prior file the same run died inside Atlas on\n // `stat drizzle/schema.sql: no such file or directory`, which hides the\n // real cause two screens up.\n process.exitCode = 1;\n throw error;\n }\n};\n\nexport const main = async () => {\n const collectionsFilePathArg = process.argv.find(arg => arg.startsWith(\"--collections=\"));\n const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split(\"=\")[1] : process.argv[2];\n\n const outputPathArg = process.argv.find(arg => arg.startsWith(\"--output=\"));\n const outputPath = outputPathArg ? outputPathArg.split(\"=\")[1] : undefined;\n\n const watch = process.argv.includes(\"--watch\");\n\n if (!collectionsFilePath) {\n out(\"Usage: ts-node generate-postgres-ddl.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]\");\n return;\n }\n\n const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);\n const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;\n\n if (watch) {\n out(`Watching for changes in ${resolvedPath}...`);\n // Imported here rather than at module scope, and this is not a style\n // choice: chokidar is needed only by `--watch`, which is a\n // schema-authoring path that never runs inside the runtime image. A\n // top-level import puts it on the boot path of the published driver\n // bundle, and the image installs a hand-listed set of runtime\n // dependencies that does not include it — so the whole driver failed to\n // load with \"Cannot find package 'chokidar'\", and every self-hosted\n // container answered 500 with a stack trace about a file watcher.\n //\n // Same reasoning the image already applies to @ariga/atlas: an\n // authoring-only dependency does not belong on a boot path.\n const { default: chokidar } = await import(\"chokidar\");\n const watcher = chokidar.watch(resolvedPath, {\n persistent: true,\n ignoreInitial: false\n });\n\n watcher.on(\"all\", (event, filePath) => {\n out(`[${event}] ${filePath}. Regenerating DDL schema...`);\n // A watch session outlives a bad edit: the cause is already on\n // stderr, and the next save is the retry. Only the one-shot run\n // below turns a failure into an exit code.\n void runGeneration(resolvedPath, resolvedOutputPath).catch(() => undefined);\n });\n } else {\n await runGeneration(resolvedPath, resolvedOutputPath);\n }\n};\n\n// This check ensures the script only runs when executed directly\nif (import.meta.url.endsWith(process.argv[1])) {\n // `runGeneration` has already printed the cause and set the exit code; the\n // handler is here so the last thing on the terminal is that message rather\n // than Node's unhandled-rejection dump of the same stack.\n main().catch(() => {\n process.exitCode = 1;\n });\n}\n","/**\n * The built entry point for `generate-postgres-ddl`.\n *\n * `generate-postgres-ddl.ts` guards its own `main()` with\n * `import.meta.url.endsWith(process.argv[1])`, because `src/index.ts` re-exports\n * the module and importing the driver must not run the generator. That guard is\n * correct when the file IS the process entry — which is how it runs from source\n * under tsx — and silently false once the module is bundled: the executed file\n * is a stub, the code lives in a shared chunk, and the chunk's `import.meta.url`\n * is not the stub's path. The generator then did nothing, exited 0, and the\n * caller reported success over an unwritten schema.\n *\n * So the built path gets an entry that says what it means.\n */\nimport { main } from \"../generate-postgres-ddl\";\n\nmain().catch(() => {\n process.exitCode = 1;\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"generate-postgres-ddl.js","names":[],"sources":["../../src/schema/generate-postgres-ddl.ts","../../src/schema/bin/generate-postgres-ddl.ts"],"sourcesContent":["import path from \"path\";\nimport { out } from \"../cli-output\";\nimport { writePostgresDdl } from \"./generate-files\";\n\n/**\n * The generator as its own process — what `--watch` runs, because a watch has\n * to re-import the collections on every save and Node's module cache would\n * serve it the first version forever. One-shot generation runs in the driver's\n * own process instead; see `generate-files.ts`.\n */\nconst runGeneration = async (collectionsFilePath?: string, outputPath?: string) => {\n if (!collectionsFilePath) {\n throw new Error(\"No collections file path provided.\");\n }\n try {\n await writePostgresDdl({ collectionsPath: path.resolve(collectionsFilePath), outputPath });\n } catch (error) {\n // A caller that reads nothing but this process's exit code must not\n // see 0 after an error was printed — see `writePostgresDdl`.\n process.exitCode = 1;\n throw error;\n }\n};\n\nexport const main = async () => {\n const collectionsFilePathArg = process.argv.find(arg => arg.startsWith(\"--collections=\"));\n const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split(\"=\")[1] : process.argv[2];\n\n const outputPathArg = process.argv.find(arg => arg.startsWith(\"--output=\"));\n const outputPath = outputPathArg ? outputPathArg.split(\"=\")[1] : undefined;\n\n const watch = process.argv.includes(\"--watch\");\n\n if (!collectionsFilePath) {\n out(\"Usage: ts-node generate-postgres-ddl.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]\");\n return;\n }\n\n const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);\n const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : undefined;\n\n if (watch) {\n out(`Watching for changes in ${resolvedPath}...`);\n // Imported here rather than at module scope, and this is not a style\n // choice: chokidar is needed only by `--watch`, which is a\n // schema-authoring path that never runs inside the runtime image. A\n // top-level import puts it on the boot path of the published driver\n // bundle, and the image installs a hand-listed set of runtime\n // dependencies that does not include it — so the whole driver failed to\n // load with \"Cannot find package 'chokidar'\", and every self-hosted\n // container answered 500 with a stack trace about a file watcher.\n //\n // Same reasoning the image already applies to @ariga/atlas: an\n // authoring-only dependency does not belong on a boot path.\n const { default: chokidar } = await import(\"chokidar\");\n const watcher = chokidar.watch(resolvedPath, {\n persistent: true,\n ignoreInitial: false\n });\n\n watcher.on(\"all\", (event, filePath) => {\n out(`[${event}] ${filePath}. Regenerating DDL schema...`);\n // A watch session outlives a bad edit: the cause is already on\n // stderr, and the next save is the retry. Only the one-shot run\n // below turns a failure into an exit code.\n void runGeneration(resolvedPath, resolvedOutputPath).catch(() => undefined);\n });\n } else {\n await runGeneration(resolvedPath, resolvedOutputPath);\n }\n};\n\n// This check ensures the script only runs when executed directly\nif (import.meta.url.endsWith(process.argv[1])) {\n // `runGeneration` has already printed the cause and set the exit code; the\n // handler is here so the last thing on the terminal is that message rather\n // than Node's unhandled-rejection dump of the same stack.\n main().catch(() => {\n process.exitCode = 1;\n });\n}\n","/**\n * The built entry point for `generate-postgres-ddl`.\n *\n * `generate-postgres-ddl.ts` guards its own `main()` with\n * `import.meta.url.endsWith(process.argv[1])`, because `src/index.ts` re-exports\n * the module and importing the driver must not run the generator. That guard is\n * correct when the file IS the process entry — which is how it runs from source\n * under tsx — and silently false once the module is bundled: the executed file\n * is a stub, the code lives in a shared chunk, and the chunk's `import.meta.url`\n * is not the stub's path. The generator then did nothing, exited 0, and the\n * caller reported success over an unwritten schema.\n *\n * So the built path gets an entry that says what it means.\n */\nimport { main } from \"../generate-postgres-ddl\";\n\nmain().catch(() => {\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;;;;;;AAUA,IAAM,gBAAgB,OAAO,qBAA8B,eAAwB;CAC/E,IAAI,CAAC,qBACD,MAAM,IAAI,MAAM,oCAAoC;CAExD,IAAI;EACA,MAAM,iBAAiB;GAAE,iBAAiB,KAAK,QAAQ,mBAAmB;GAAG;EAAW,CAAC;CAC7F,SAAS,OAAO;EAGZ,QAAQ,WAAW;EACnB,MAAM;CACV;AACJ;AAEA,IAAa,OAAO,YAAY;CAC5B,MAAM,yBAAyB,QAAQ,KAAK,MAAK,QAAO,IAAI,WAAW,gBAAgB,CAAC;CACxF,MAAM,sBAAsB,yBAAyB,uBAAuB,MAAM,GAAG,CAAC,CAAC,KAAK,QAAQ,KAAK;CAEzG,MAAM,gBAAgB,QAAQ,KAAK,MAAK,QAAO,IAAI,WAAW,WAAW,CAAC;CAC1E,MAAM,aAAa,gBAAgB,cAAc,MAAM,GAAG,CAAC,CAAC,KAAK,KAAA;CAEjE,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS;CAE7C,IAAI,CAAC,qBAAqB;EACtB,IAAI,+GAA+G;EACnH;CACJ;CAEA,MAAM,eAAe,KAAK,QAAQ,QAAQ,IAAI,GAAG,mBAAmB;CACpE,MAAM,qBAAqB,aAAa,KAAK,QAAQ,QAAQ,IAAI,GAAG,UAAU,IAAI,KAAA;CAElF,IAAI,OAAO;EACP,IAAI,2BAA2B,aAAa,IAAI;EAYhD,MAAM,EAAE,SAAS,aAAa,MAAM,OAAO;EAM3C,SALyB,MAAM,cAAc;GACzC,YAAY;GACZ,eAAe;EACnB,CAEA,CAAA,CAAQ,GAAG,QAAQ,OAAO,aAAa;GACnC,IAAI,IAAI,MAAM,IAAI,SAAS,6BAA6B;GAIxD,cAAmB,cAAc,kBAAkB,CAAC,CAAC,YAAY,KAAA,CAAS;EAC9E,CAAC;CACL,OACI,MAAM,cAAc,cAAc,kBAAkB;AAE5D;AAGA,IAAI,OAAO,KAAK,IAAI,SAAS,QAAQ,KAAK,EAAE,GAIxC,KAAK,CAAC,CAAC,YAAY;CACf,QAAQ,WAAW;AACvB,CAAC;;;;;;;;;;;;;;;;;AC/DL,KAAK,CAAC,CAAC,YAAY;CACf,QAAQ,WAAW;AACvB,CAAC"}
@@ -72,6 +72,12 @@ export interface SchemaCommitInput {
72
72
  * a statement that applies and one that is rejected.
73
73
  */
74
74
  existing?: ExistingSchema;
75
+ /**
76
+ * Plan a commit of the source alone — "Edit source only". An unapplicable
77
+ * change is not refused: it is committed with no statements, under a
78
+ * message that names what the database keeps.
79
+ */
80
+ sourceOnly?: boolean;
75
81
  }
76
82
  export interface SchemaCommit {
77
83
  /** Every file the commit writes, source and generated alike. */
@@ -123,6 +129,12 @@ export declare class SchemaCommitError extends Error {
123
129
  * where there is no database to describe.
124
130
  */
125
131
  export declare function additiveStatements(before: CollectionConfig[], after: CollectionConfig[], existing?: ExistingSchema, options?: EnsureOptions): string[];
132
+ /**
133
+ * The commit of an "Edit source only": what changed in the source, and what
134
+ * the database keeps — `chore(schema): remove sku from products (source only —
135
+ * column products.sku kept)`. The body lists what each change leaves behind.
136
+ */
137
+ export declare function sourceOnlyCommitMessage(classified: ClassifiedChanges): string;
126
138
  /** A commit message that says what changed rather than that something did. */
127
139
  export declare function commitMessage(classified: ClassifiedChanges): string;
128
140
  /**
@@ -52,5 +52,12 @@ export declare function describeSchemaDifference(difference: SchemaDifference):
52
52
  * Per difference rather than one summary block on purpose: these are read out of
53
53
  * a log aggregator as often as a terminal, and a single multi-line message
54
54
  * cannot be filtered down to the one collection somebody is looking at.
55
+ *
56
+ * The fix it names is `rebase db push`. `rebase dev` rewrites
57
+ * schema.generated.ts from the collections on every change, and boot applies
58
+ * only additive changes, so what is still different at boot is the database
59
+ * behind the file: a rename, a dropped column, a narrowed type, a NOT NULL.
60
+ * `rebase schema generate` rewrites the file and leaves the database as it is,
61
+ * so it is named only for collections that changed since the file was written.
55
62
  */
56
63
  export declare function warnOnGeneratedSchemaDrift(differences: SchemaDifference[]): void;
@@ -56,6 +56,11 @@ export interface UniqueConstraintRow {
56
56
  constraint_name: string;
57
57
  column_names: string[];
58
58
  }
59
+ /** One index, by name. */
60
+ export interface IndexRow {
61
+ table_name: string;
62
+ index_name: string;
63
+ }
59
64
  /** A CHECK constraint, as `pg_get_constraintdef` renders it. */
60
65
  export interface CheckConstraintRow {
61
66
  table_name: string;
@@ -68,6 +73,21 @@ export interface CommentRow {
68
73
  column_name: string | null;
69
74
  comment: string;
70
75
  }
76
+ /** A row-level security policy, as `pg_policies` reports it. */
77
+ export interface PolicyRow {
78
+ table_name: string;
79
+ policy_name: string;
80
+ /** `PERMISSIVE` or `RESTRICTIVE`. */
81
+ permissive: string;
82
+ /** The `TO` list: database roles, `public` for every role. */
83
+ roles: string[];
84
+ /** `SELECT`, `INSERT`, `UPDATE`, `DELETE` or `ALL`. */
85
+ cmd: string;
86
+ /** The `USING` expression, as Postgres renders it back. */
87
+ qual: string | null;
88
+ /** The `WITH CHECK` expression, as Postgres renders it back. */
89
+ with_check: string | null;
90
+ }
71
91
  /**
72
92
  * Everything one introspection run reads from the database.
73
93
  *
@@ -83,14 +103,44 @@ export interface SchemaMetadata {
83
103
  pks: PrimaryKeyRow[];
84
104
  fks: ForeignKeyRow[];
85
105
  uniques: UniqueConstraintRow[];
106
+ /**
107
+ * Every index on the introspected tables, by name — how a foreign key
108
+ * whose column Rebase would index is told apart from one it already has.
109
+ * Absent when the caller did not read them, and then nothing is said
110
+ * about foreign-key indexes either way.
111
+ */
112
+ indexes?: IndexRow[];
86
113
  checks: CheckConstraintRow[];
87
114
  comments: CommentRow[];
115
+ /**
116
+ * The row-level security policies on the introspected tables. Absent when
117
+ * the caller did not read them, and then no collection is given rules.
118
+ */
119
+ policies?: PolicyRow[];
88
120
  /**
89
121
  * Row counts for the tables that needed one, capped — see `countRowsUpTo`.
90
122
  * Absent for every table introspection never had a reason to count.
91
123
  */
92
124
  rowCounts: Record<string, number>;
93
125
  }
126
+ /**
127
+ * Why a table keyed on several columns is not generated as a collection, or
128
+ * `undefined` when it is.
129
+ *
130
+ * Several `isId` properties are one composite key, and the planner builds that
131
+ * table under the same `PRIMARY KEY (a, b)` — so such a table round-trips when
132
+ * two things hold:
133
+ *
134
+ * - **Every key column can carry `isId`,** which exists on string and number
135
+ * properties only. Read from the column's SQL type, not a data sample: a
136
+ * `line_no` holding only 1s is not a boolean. A key with a column of another
137
+ * type — pagila's `payment` is keyed on a timestamp too — can be marked on
138
+ * some of its columns only, and those would be read as the whole key.
139
+ * - **Nothing points at it.** A foreign key into it, or a junction, is a link
140
+ * into a composite key, which the planner refuses: the relation has one
141
+ * column to point with.
142
+ */
143
+ export declare function compositeKeySkipReason(meta: TableMeta, allFks: ForeignKeyRow[]): string | undefined;
94
144
  export interface TableMeta {
95
145
  name: string;
96
146
  columns: TableColumn[];
@@ -232,6 +282,19 @@ export interface GenerationContext {
232
282
  * from the target project and passes it. See `detectCollectionBuilder`.
233
283
  */
234
284
  builder?: CollectionBuilder;
285
+ /**
286
+ * Collects one line per column the planner will not reproduce exactly —
287
+ * what `rebase db push` would do to it, and how to avoid that when there is
288
+ * a way. The CLI prints them; the same lines are written at the top of the
289
+ * generated file.
290
+ */
291
+ notes?: string[];
292
+ /**
293
+ * Tables this run does not generate a collection for, so nothing may point
294
+ * at them: a relation whose target file does not exist is an import that
295
+ * does not resolve.
296
+ */
297
+ skippedTables?: ReadonlySet<string>;
235
298
  }
236
299
  /**
237
300
  * Generate the full TypeScript file content for a single collection.
@@ -71,7 +71,7 @@ export declare const TABLES_QUERY = "\n SELECT c.relname AS table_name,\n
71
71
  * errors. `character_maximum_length` is the declared `varchar(n)` bound —
72
72
  * already enforced by the database, and free to surface as validation.
73
73
  */
74
- export declare const COLUMNS_QUERY = "\n SELECT\n c.table_name,\n c.column_name,\n c.data_type,\n c.udt_name,\n c.is_nullable,\n c.column_default,\n c.ordinal_position,\n c.is_generated,\n c.is_identity,\n c.identity_generation,\n c.character_maximum_length,\n c.numeric_precision,\n c.numeric_scale,\n (SELECT a.atttypmod FROM pg_attribute a\n JOIN pg_class pc ON a.attrelid = pc.oid\n WHERE pc.relname = c.table_name\n AND a.attname = c.column_name\n AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)) as atttypmod\n FROM information_schema.columns c\n WHERE c.table_schema = $1\n ORDER BY c.table_name, c.ordinal_position\n";
74
+ export declare const COLUMNS_QUERY = "\n SELECT\n c.table_name,\n c.column_name,\n c.data_type,\n c.udt_name,\n c.is_nullable,\n c.column_default,\n c.ordinal_position,\n c.is_generated,\n c.generation_expression,\n c.is_identity,\n c.identity_generation,\n c.character_maximum_length,\n c.numeric_precision,\n c.numeric_scale,\n (SELECT a.atttypmod FROM pg_attribute a\n JOIN pg_class pc ON a.attrelid = pc.oid\n WHERE pc.relname = c.table_name\n AND a.attname = c.column_name\n AND pc.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)) as atttypmod\n FROM information_schema.columns c\n WHERE c.table_schema = $1\n ORDER BY c.table_name, c.ordinal_position\n";
75
75
  export declare const ENUMS_QUERY = "\n SELECT t.typname AS enum_name,\n e.enumlabel AS enum_value,\n e.enumsortorder AS sort_order\n FROM pg_type t\n JOIN pg_enum e ON t.oid = e.enumtypid\n JOIN pg_namespace n ON t.typnamespace = n.oid\n WHERE n.nspname = $1\n ORDER BY t.typname, e.enumsortorder\n";
76
76
  /**
77
77
  * Primary key columns, in key order.
@@ -116,6 +116,12 @@ export declare const FOREIGN_KEYS_QUERY = "\n WITH fk AS (\n SELECT co
116
116
  * `validation: { unique: true }` field makes.
117
117
  */
118
118
  export declare const UNIQUE_CONSTRAINTS_QUERY = "\n SELECT t.relname AS table_name,\n ic.relname AS constraint_name,\n -- ::text, because the driver has no parser for an array of `name`\n -- and hands back the raw `{a,b}` literal as a string. Every consumer\n -- here indexes and compares it as an array, and a string of that\n -- shape fails those silently rather than loudly.\n array_agg(a.attname::text ORDER BY k.ord) AS column_names\n FROM pg_index i\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_class ic ON ic.oid = i.indexrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\n WHERE i.indisunique\n AND NOT i.indisprimary\n AND i.indpred IS NULL\n AND n.nspname = $1\n AND NOT t.relispartition\n GROUP BY t.relname, ic.relname\n ORDER BY t.relname, ic.relname\n";
119
+ /**
120
+ * Every index on the schema's tables, by name. Only the name is needed: it is
121
+ * what tells a foreign key Rebase already indexed apart from one it would
122
+ * index on the first push — see `foreignKeyIndexSpec`.
123
+ */
124
+ export declare const INDEXES_QUERY = "\n SELECT t.relname AS table_name, ic.relname AS index_name\n FROM pg_index i\n JOIN pg_class t ON t.oid = i.indrelid\n JOIN pg_class ic ON ic.oid = i.indexrelid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n WHERE n.nspname = $1\n AND NOT t.relispartition\n ORDER BY t.relname, ic.relname\n";
119
125
  /**
120
126
  * CHECK constraints, as the source text Postgres reproduces them from.
121
127
  *
@@ -127,8 +133,21 @@ export declare const CHECK_CONSTRAINTS_QUERY = "\n SELECT DISTINCT\n
127
133
  /**
128
134
  * `COMMENT ON TABLE` / `COMMENT ON COLUMN`, which is documentation the author
129
135
  * already wrote and which nothing downstream has ever read.
136
+ *
137
+ * Only comments on relations: `pg_description` also holds the stamps boot
138
+ * writes on each policy (`rebase:<hash>`), and an OID is unique only within
139
+ * its own catalogue, so a policy's could name a table.
140
+ */
141
+ export declare const COMMENTS_QUERY = "\n SELECT t.relname AS table_name,\n a.attname AS column_name,\n d.description AS comment\n FROM pg_description d\n JOIN pg_class t ON t.oid = d.objoid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.objsubid\n WHERE n.nspname = $1\n AND d.classoid = 'pg_catalog.pg_class'::regclass\n AND t.relkind IN ('r', 'p')\n AND (d.objsubid = 0 OR a.attname IS NOT NULL)\n ORDER BY t.relname, d.objsubid\n";
142
+ /**
143
+ * Every row-level security policy in the schema, as `pg_policies` reports it.
144
+ *
145
+ * A table's policies are its access model. Introspection that leaves them out
146
+ * generates collections describing no rules, and the database's own policies
147
+ * then read as orphans: `doctor` fails on them, and a `db push` drops any
148
+ * whose name has the shape an unnamed rule compiles to.
130
149
  */
131
- export declare const COMMENTS_QUERY = "\n SELECT t.relname AS table_name,\n a.attname AS column_name,\n d.description AS comment\n FROM pg_description d\n JOIN pg_class t ON t.oid = d.objoid\n JOIN pg_namespace n ON n.oid = t.relnamespace\n LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.objsubid\n WHERE n.nspname = $1\n AND t.relkind IN ('r', 'p')\n AND (d.objsubid = 0 OR a.attname IS NOT NULL)\n ORDER BY t.relname, d.objsubid\n";
150
+ export declare const POLICIES_QUERY = "\n SELECT tablename AS table_name,\n policyname AS policy_name,\n permissive,\n roles,\n cmd,\n qual,\n with_check\n FROM pg_policies\n WHERE schemaname = $1\n ORDER BY tablename, policyname\n";
132
151
  /**
133
152
  * Counts a table's rows, stopping once the answer can only be "more than
134
153
  * `limit`".
@@ -0,0 +1,22 @@
1
+ import type { TableColumn } from "./introspect-db-logic.js";
2
+ /** What introspection makes of a table's search columns. */
3
+ export interface ReconstructedSearch {
4
+ /** The generated columns that belong to the search block — never emitted as properties. */
5
+ columns: Set<string>;
6
+ /** The `search: { … }` source, when the stamp confirmed it. */
7
+ block?: string;
8
+ note?: string;
9
+ }
10
+ /**
11
+ * The search block a table's stamped generated columns were built from, or a
12
+ * note saying why it could not be read back.
13
+ */
14
+ export declare function reconstructSearchBlock(input: {
15
+ schema: string;
16
+ table: string;
17
+ columns: TableColumn[];
18
+ /** column → `COMMENT ON COLUMN`. */
19
+ comments: Map<string, string>;
20
+ /** column → the property key it is generated under. */
21
+ keyByColumn: Map<string, string>;
22
+ }): ReconstructedSearch;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The property keys a column needs for the planner to build that same column.
3
+ *
4
+ * Introspection writes collections, and `rebase db push` plans them back into
5
+ * DDL. The two used to read a column differently: introspection wrote
6
+ * `type: "number"` for an `integer`, and the planner reads an unqualified
7
+ * number as `NUMERIC`; `validation.max: 40` for a `varchar(40)`, and the
8
+ * planner reads an unqualified string as `TEXT`; a `time` as a plain `date`,
9
+ * which the planner makes a `timestamptz` — a change Postgres refuses to cast.
10
+ * So adopting a database — introspect, then push, the documented path — planned
11
+ * a type change on nearly every column, and some it could not even dry-run.
12
+ *
13
+ * Each function here answers one question about one column: what to write so
14
+ * that `planSchema` produces exactly what the catalogue holds. Where no
15
+ * property can say it, the answer is a note naming the column and what a push
16
+ * would do to it, so the reader decides before anything runs.
17
+ *
18
+ * Pure: catalogue rows in, keys and notes out.
19
+ */
20
+ import type { ForeignKeyRow, TableColumn } from "./introspect-db-logic.js";
21
+ /** Keys to write on a property, as `key: value` source fragments, plus what could not be said. */
22
+ export interface ColumnStorage {
23
+ /** e.g. `columnType: "varchar"`, `precision: 10`. */
24
+ keys: string[];
25
+ /** Why the planner will not reproduce this column exactly, when it will not. */
26
+ note?: string;
27
+ }
28
+ /**
29
+ * The name Postgres gives a `SERIAL` column's sequence — `ChooseRelationName`
30
+ * with the `seq` label: `<table>_<column>_seq`, and when that is over 63 bytes
31
+ * the longer of the two names is shortened, one character at a time, until it
32
+ * fits.
33
+ */
34
+ export declare function serialSequenceName(table: string, column: string): string;
35
+ /**
36
+ * The `isId` (and `columnType`) a single-column primary key needs.
37
+ *
38
+ * `isId: "increment"` is an identity column, and only an INTEGER one. A serial
39
+ * key — an integer with a `nextval` default — is `SERIAL`/`BIGSERIAL`, declared
40
+ * through `columnType` beside `isId: "manual"`. A uuid key is `"uuid"` only when
41
+ * the database generates it; a key with no default is `"manual"`, which keeps
42
+ * the planner from inventing one.
43
+ */
44
+ export declare function primaryKeyStorage(col: TableColumn, propType: string): ColumnStorage;
45
+ /**
46
+ * The keys a non-key column needs, for the property type introspection chose.
47
+ *
48
+ * `enumTypeName` is the type the planner would name for this column
49
+ * (`<table>_<column>`, cut to 63 bytes); an enum column whose type is called
50
+ * anything else cannot be declared, and the note says how to rename it.
51
+ */
52
+ export declare function columnStorage(col: TableColumn, propType: string, context: {
53
+ isEnumColumn: boolean;
54
+ enumTypeName: string;
55
+ enumTypeShared: boolean;
56
+ }): ColumnStorage;
57
+ /**
58
+ * The `columnType` an array column needs, or a note when its element type is
59
+ * not one the planner stores natively.
60
+ *
61
+ * Only `text[]`, `integer[]`, `boolean[]` and `numeric[]` are. Reading
62
+ * `bigint[]` as `integer[]` — what this used to do — planned a narrowing.
63
+ */
64
+ export declare function arrayStorage(col: TableColumn): {
65
+ columnType?: string;
66
+ innerType: string;
67
+ note?: string;
68
+ };
69
+ /**
70
+ * A column default as a `defaultValue`, when it is a literal a property can carry.
71
+ *
72
+ * `undefined` when there is no default to carry, or when the default is one the
73
+ * generator expresses another way (`now()` is `autoValue`, a key's default is
74
+ * its `isId`). `{ note }` when there is a default and no property can say it —
75
+ * a push would plan dropping it.
76
+ */
77
+ export declare function literalDefaultOf(col: TableColumn, propType: string): {
78
+ source: string;
79
+ } | {
80
+ note: string;
81
+ } | undefined;
82
+ /** `ON DELETE` as the relation's `onDelete`, when it is not the default the planner would choose. */
83
+ export declare function onDeleteOf(fk: ForeignKeyRow, required: boolean): string | undefined;
@@ -240,6 +240,16 @@ export declare function deriveTitleProperty(facts: Map<string, ColumnFacts>): st
240
240
  * is stable across runs.
241
241
  */
242
242
  export declare function deriveKanbanProperty(facts: Map<string, ColumnFacts>): string | undefined;
243
+ /**
244
+ * The string column that can hold a board's card order, or nothing.
245
+ *
246
+ * A board without `orderProperty` cannot keep a card's position within a
247
+ * column, and the config validator says so on every generate — so a board is
248
+ * emitted only when the table already has somewhere to store the order. A
249
+ * legacy table almost never does; asking its owner to add a hidden column
250
+ * just to satisfy generated output was the wrong way round.
251
+ */
252
+ export declare function deriveKanbanOrderProperty(facts: Map<string, ColumnFacts>): string | undefined;
243
253
  /**
244
254
  * The column a list should be sorted by, newest first.
245
255
  *
@@ -1,16 +1,16 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { a as __toESM, t as require___vite_browser_external } from "../__vite-browser-external-BnuHet1e.js";
4
- import { _ as isUsableFileName, a as generateCollectionFile, f as classifyTables, g as countRowsUpTo, i as buildTablesMap, l as mergeIndexContent, o as generateIndexContent, p as lookupCandidates, r as buildEnumMap, u as safeHostFromUrl, v as quoteRelation, y as readSchemaMetadata } from "../introspect-db-logic-WMuAfxvw.js";
5
- import { n as outError, r as outWarn, t as out } from "../cli-output-CNdMql-L.js";
6
- import { t as chalk } from "../source-Br7L7GOI.js";
7
- import { t as require_arg } from "../arg-Dni7MzLB.js";
3
+ import { i as __toESM } from "../rolldown-runtime-V7rFbMRe.js";
4
+ import { i as diagnoseDbError } from "../cli-errors-oVUuD8k0.js";
5
+ import { a as outWarn, i as outError, r as out, t as chalk } from "../source-DB6xJv3p.js";
6
+ import { i as readSchemaMetadata, n as isUsableFileName, r as quoteRelation, t as countRowsUpTo } from "../introspect-db-queries-BhhJg0b8.js";
7
+ import { a as compositeKeySkipReason, d as safeHostFromUrl, i as buildTablesMap, m as lookupCandidates, o as generateCollectionFile, p as classifyTables, r as buildEnumMap, s as generateIndexContent, u as mergeIndexContent } from "../introspect-db-logic-BCc1Jym2.js";
8
+ import { n as require_arg, t as promptConfirm } from "../cli-prompt-egtNKkZl.js";
8
9
  import pg from "pg";
9
- import fs from "fs";
10
10
  import path from "path";
11
+ import fs from "fs";
11
12
  import * as dotenv from "dotenv";
12
13
  //#region src/schema/introspect-db-project.ts
13
- var import___vite_browser_external = /* @__PURE__ */ __toESM(require___vite_browser_external(), 1);
14
14
  var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
15
15
  /**
16
16
  * What the project being introspected into can actually import.
@@ -416,10 +416,9 @@ async function main() {
416
416
  }, { permissive: true });
417
417
  const cwd = process.cwd();
418
418
  const defaultOutDir = path.basename(cwd) === "backend" ? path.resolve(cwd, "..", "config", "collections") : path.resolve(cwd, "config", "collections");
419
- const outDir = args["--output"] || args["--collections"] || defaultOutDir;
419
+ const outDir = path.resolve(args["--output"] || args["--collections"] || defaultOutDir);
420
420
  const force = args["--force"] || false;
421
421
  const pgSchema = args["--schema"] || "public";
422
- if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
423
422
  const envPaths = [
424
423
  process.env.DOTENV_CONFIG_PATH,
425
424
  path.resolve(process.cwd(), ".env"),
@@ -442,10 +441,15 @@ async function main() {
442
441
  try {
443
442
  await client.connect();
444
443
  } catch (err) {
445
- outError(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));
446
- outError(chalk.gray(" Check your DATABASE_URL and ensure the database is reachable."));
444
+ const banner = diagnoseDbError(err, databaseUrl);
445
+ if (banner) outError(banner);
446
+ else {
447
+ outError(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));
448
+ outError(chalk.gray(" Check your DATABASE_URL and ensure the database is reachable."));
449
+ }
447
450
  process.exit(1);
448
451
  }
452
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
449
453
  const hostPart = safeHostFromUrl(databaseUrl);
450
454
  out(chalk.gray(`Connected to database: ${hostPart}`));
451
455
  out(chalk.gray(`Introspecting schema '${pgSchema}'...`));
@@ -474,18 +478,14 @@ async function main() {
474
478
  if (args["--no-data-inference"]) runDataInference = false;
475
479
  else if (args["--data-inference"] !== void 0) runDataInference = args["--data-inference"];
476
480
  else if (!process.stdin.isTTY) out(chalk.gray("Skipping data inference (non-interactive run; pass --data-inference to enable)."));
477
- else {
478
- const rl = import___vite_browser_external.default.createInterface({
479
- input: process.stdin,
480
- output: process.stdout
481
- });
482
- runDataInference = (await new Promise((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))).trim().toLowerCase() === "y";
483
- rl.close();
484
- }
481
+ else 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) "));
485
482
  if (runDataInference) out(chalk.gray("Sampling database rows for data inference..."));
486
483
  const generatedFiles = [];
487
484
  const skippedFiles = [];
488
- const tablesToProcess = Array.from(tablesMap.entries()).filter(([tableName]) => !joinTables.has(tableName));
485
+ const compositeKeyed = new Map(Array.from(tablesMap.entries()).filter(([tableName]) => !joinTables.has(tableName)).map(([tableName, meta]) => [tableName, compositeKeySkipReason(meta, fks)]).filter((entry) => entry[1] !== void 0));
486
+ for (const [tableName, reason] of compositeKeyed) outWarn(chalk.yellow(`⚠ Skipping table ${JSON.stringify(tableName)}: it is keyed on (${tablesMap.get(tableName)?.pks.join(", ")}), and ${reason}. \`rebase db push\` leaves a table that is not a collection alone.`));
487
+ const notes = [];
488
+ const tablesToProcess = Array.from(tablesMap.entries()).filter(([tableName]) => !joinTables.has(tableName) && !compositeKeyed.has(tableName));
489
489
  const BATCH_SIZE = 10;
490
490
  for (let i = 0; i < tablesToProcess.length; i += BATCH_SIZE) {
491
491
  const batch = tablesToProcess.slice(i, i + BATCH_SIZE);
@@ -511,13 +511,21 @@ async function main() {
511
511
  metadata,
512
512
  classifications,
513
513
  checkFacts,
514
- builder
514
+ builder,
515
+ notes,
516
+ skippedTables: new Set(compositeKeyed.keys())
515
517
  });
516
518
  fs.writeFileSync(filePath, fileContent, "utf-8");
517
519
  generatedFiles.push(tableName);
518
520
  out(chalk.green(` ✓ ${filePath}`));
519
521
  }));
520
522
  }
523
+ if (notes.length > 0) {
524
+ outWarn("");
525
+ outWarn(chalk.yellow(`⚠ ${notes.length} column(s) will not round-trip exactly — \`rebase db push\` would change them:`));
526
+ for (const note of [...notes].sort()) outWarn(chalk.gray(` - ${note}`));
527
+ outWarn(chalk.gray(" Read these before the first push. Run `rebase db push --dry-run` to see the plan."));
528
+ }
521
529
  if (generatedFiles.length > 0) {
522
530
  const indexPath = path.join(outDir, "index.ts");
523
531
  if (fs.existsSync(indexPath) && !force) {