@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
@@ -22,8 +22,9 @@
22
22
  * See `diff-plan.ts` for the whole of that argument.
23
23
  */
24
24
  import { type CollectionConfig } from "@rebasepro/types";
25
- import { type ConstraintPolicy, type DiffOptions, type EnsureAction, type EnsurePlan, type ExistingSchema, type LegacyForeignKey, type OrphanedRequiredColumn, type SearchColumnDrift, type WithheldConstraint } from "./plan/diff-plan.js";
26
- export type { ConstraintPolicy, EnsureAction, EnsurePlan, ExistingSchema, LegacyForeignKey, OrphanedRequiredColumn, SearchColumnDrift, WithheldConstraint };
25
+ import { type ConstraintPolicy, type DeferredIndexBuild, type DiffOptions, type EnsureAction, type EnsurePlan, type ExistingSchema, type LegacyForeignKey, type OrphanedRequiredColumn, type SearchColumnDrift, type WithheldConstraint } from "./plan/diff-plan.js";
26
+ import { type DeferredIndexOutcome, type OpenIndexBuildSession } from "./deferred-index-build.js";
27
+ export type { ConstraintPolicy, DeferredIndexBuild, EnsureAction, EnsurePlan, ExistingSchema, LegacyForeignKey, OrphanedRequiredColumn, SearchColumnDrift, WithheldConstraint };
27
28
  /**
28
29
  * The subset of a database handle this needs: run a statement, get rows back.
29
30
  *
@@ -51,6 +52,36 @@ export interface EnsureOptions extends DiffOptions {
51
52
  * reads the world.
52
53
  */
53
54
  databaseExtensions?: readonly string[];
55
+ /**
56
+ * Run each statement that changes an existing table under this
57
+ * `lock_timeout` (ms), retrying a timeout with backoff — see
58
+ * `schema/boot-ddl`. The boot sets it; absent, statements wait on locks for
59
+ * as long as the connection's `statement_timeout` allows, as the live schema
60
+ * editor's confirmed changes do.
61
+ */
62
+ lockTimeoutMs?: number;
63
+ /**
64
+ * Apply the statements that build tables this run creates — and the enum
65
+ * types they use — in transactions of up to {@link NEW_TABLE_BATCH}
66
+ * statements instead of one autocommit each. The boot sets it; see
67
+ * `ensureCollectionTables`.
68
+ */
69
+ batchNewTables?: boolean;
70
+ /**
71
+ * Leave the indexes a live table is missing (or holds INVALID) to the
72
+ * caller, in {@link EnsureOutcome.deferredIndexes}, instead of building
73
+ * them before returning. The boot sets it and builds them in the
74
+ * background once it serves — see `deferred-index-build`. A UNIQUE one is
75
+ * built before returning either way: see {@link DeferredIndexBuild.unique}.
76
+ */
77
+ deferIndexBuilds?: boolean;
78
+ /**
79
+ * The connection those builds run on when they are not deferred. Absent,
80
+ * they share `client` — fine for a small table, and cut short by the
81
+ * pool's `statement_timeout` on a large one, which is why the boot hands
82
+ * over a dedicated connection.
83
+ */
84
+ openIndexBuildSession?: OpenIndexBuildSession;
54
85
  }
55
86
  export interface EnsureOutcome extends EnsurePlan {
56
87
  /**
@@ -68,6 +99,14 @@ export interface EnsureOutcome extends EnsurePlan {
68
99
  target: string;
69
100
  error: string;
70
101
  }[];
102
+ /**
103
+ * What happened to the indexes built here rather than left to the caller —
104
+ * every one of {@link EnsurePlan.deferredIndexes}, or under
105
+ * `deferIndexBuilds` the UNIQUE ones only; absent when there were none.
106
+ * An index on a table that already exists reports here, not in
107
+ * {@link failures}.
108
+ */
109
+ indexBuilds?: DeferredIndexOutcome;
71
110
  }
72
111
  /**
73
112
  * Decide what to add. Pure — the caller supplies what exists and runs the
@@ -82,8 +121,22 @@ export interface EnsureOutcome extends EnsurePlan {
82
121
  * with no developer in the loop.
83
122
  */
84
123
  export declare function planCollectionSchemaEnsure(allCollections: CollectionConfig[], existing: ExistingSchema, options?: EnsureOptions): EnsurePlan;
124
+ export interface ReadSchemaOptions {
125
+ /**
126
+ * The existing tables worth probing for rows, given every table's columns.
127
+ *
128
+ * Whether a table holds rows decides one thing: whether a NOT NULL or a
129
+ * UNIQUE can be added with a column *being added* to it. So the boot asks
130
+ * only about tables gaining a column — none at all on a boot that changes
131
+ * nothing — rather than probing every table in the schema, adopted and
132
+ * unmanaged ones included, on every boot. A table that is not probed is
133
+ * reported as holding rows: the answer that never plans a constraint that
134
+ * could fail against data. Absent, every table is probed.
135
+ */
136
+ probeTables?: (tables: ReadonlyMap<string, ReadonlySet<string>>) => Iterable<string>;
137
+ }
85
138
  /** Read what the database has, for the schemas the collections live in. */
86
- export declare function readExistingSchema(client: Queryable, schemas: string[]): Promise<ExistingSchema>;
139
+ export declare function readExistingSchema(client: Queryable, schemas: string[], options?: ReadSchemaOptions): Promise<ExistingSchema>;
87
140
  /**
88
141
  * Read what the database looks like, for the schemas a set of collections
89
142
  * lives in.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Enum labels the database holds and the collections no longer declare.
3
+ *
4
+ * PostgreSQL has no `ALTER TYPE … DROP VALUE`, and Atlas plans nothing for a
5
+ * removed label — `db push` reported success while the database kept the old
6
+ * label, so writes of it (by SQL, or an older client) went on succeeding, and
7
+ * `rebase doctor` sent the developer back to the same push. Boot behaves the
8
+ * same way by design: it adds labels and never removes one. So this is not a
9
+ * refusal — the push stays what boot would do — but the push says it, with the
10
+ * real way to retire a label.
11
+ */
12
+ import { type CollectionConfig } from "@rebasepro/types";
13
+ import type { EnumPlan } from "./plan/types.js";
14
+ import { type Queryable } from "./ensure-collection-tables.js";
15
+ export interface KeptEnumLabels {
16
+ /** `schema.type`, as the catalogue names it. */
17
+ type: string;
18
+ /** Labels in the database that no collection declares, in the type's order. */
19
+ labels: string[];
20
+ }
21
+ /** Pure: the declared enums against the catalogue's labels, keyed `schema.type`. */
22
+ export declare function findKeptEnumLabels(declared: Pick<EnumPlan, "qualified" | "labels">[], existing: ReadonlyMap<string, string[]>): KeptEnumLabels[];
23
+ /** Plans the collections and reads the catalogue's enum labels. */
24
+ export declare function keptEnumLabels(client: Queryable, collections: CollectionConfig[]): Promise<KeptEnumLabels[]>;
25
+ /** What the push prints: which labels stay, and how one is actually retired. */
26
+ export declare function formatKeptEnumLabelsNotice(kept: KeptEnumLabels[]): string;
@@ -1,6 +1,6 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { t as main } from "../generate-drizzle-schema-93M0lUxK.js";
3
+ import { t as main } from "../generate-drizzle-schema-DrA-YgXv.js";
4
4
  //#region src/schema/bin/generate-drizzle-schema.ts
5
5
  /**
6
6
  * The built entry point for `generate-drizzle-schema`.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The two generators `rebase db push` runs as its step 1, as functions.
3
+ *
4
+ * They used to exist only as scripts, each its own `tsx` process: the driver
5
+ * (itself a `tsx` process) spawned one for the Drizzle schema and then one for
6
+ * the DDL, and each paid a Node start, tsx's loader and an import of the whole
7
+ * server package before reading a single collection — about 0.5 s apiece, a
8
+ * second per push. The driver process is one-shot, so a fresh module graph per
9
+ * generation bought nothing: the collections cannot change under it.
10
+ *
11
+ * The scripts remain (`generate-drizzle-schema.ts`, `generate-postgres-ddl.ts`)
12
+ * for `--watch`, which re-imports the collections on every save and does need a
13
+ * process of its own — Node's module cache would serve it the first version
14
+ * forever.
15
+ *
16
+ * Both functions print their own success lines and their own error, exactly as
17
+ * the scripts did, and throw {@link GenerationFailed} after printing — so a
18
+ * caller can tell "the generator said why" from "the generator never ran".
19
+ */
20
+ /** Thrown once the generator has printed why it failed. */
21
+ export declare class GenerationFailed extends Error {
22
+ constructor(message: string, options?: {
23
+ cause?: unknown;
24
+ });
25
+ }
26
+ export interface GenerateFileOptions {
27
+ /** The collections directory (or single module), resolved against the cwd. */
28
+ collectionsPath: string;
29
+ /** Where to write. Absent: print to stdout instead. */
30
+ outputPath?: string;
31
+ /**
32
+ * End on the line that says what to run next. Only `rebase schema
33
+ * generate` itself wants it: inside `db push` it told the push to run
34
+ * `db generate`, and inside `db generate` to run itself.
35
+ */
36
+ nextStep?: boolean;
37
+ }
38
+ /** `src/schema.generated.ts`, from the collections. */
39
+ export declare function writeDrizzleSchema(options: GenerateFileOptions): Promise<void>;
40
+ /**
41
+ * `schema.sql` (Atlas's desired state) and, beside it, `policies.sql`,
42
+ * `search.sql`, `vector.sql` and `triggers.sql` — the parts Atlas does not own.
43
+ */
44
+ export declare function writePostgresDdl(options: GenerateFileOptions): Promise<void>;
@@ -1,16 +1,3 @@
1
- /**
2
- * The line `rebase schema generate` ends on.
3
- *
4
- * Its own module because `generate-drizzle-schema.ts` uses `import.meta` to
5
- * decide whether it was executed directly, which the driver's jest suite cannot
6
- * load at all — so the one sentence in that file with a decision in it had no
7
- * test and could not have one where it stood.
8
- */
9
- export declare const formatTerminalText: (text: string, options?: {
10
- bold?: boolean;
11
- backgroundColor?: "blue" | "green" | "red" | "yellow" | "cyan" | "magenta";
12
- textColor?: "white" | "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan";
13
- }) => string;
14
1
  /**
15
2
  * What to do with the file that was just generated.
16
3
  *
@@ -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-BJsLaVNX.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"}
@@ -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,8 +103,20 @@ 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.
@@ -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`".
@@ -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,17 +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 { i as readSchemaMetadata, n as isUsableFileName, r as quoteRelation, t as countRowsUpTo } from "../introspect-db-queries-C_Q5VgQw.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 { 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-kCETE8TY.js";
8
- 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";
9
9
  import pg from "pg";
10
- import fs from "fs";
11
10
  import path from "path";
11
+ import fs from "fs";
12
12
  import * as dotenv from "dotenv";
13
13
  //#region src/schema/introspect-db-project.ts
14
- var import___vite_browser_external = /* @__PURE__ */ __toESM(require___vite_browser_external(), 1);
15
14
  var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
16
15
  /**
17
16
  * What the project being introspected into can actually import.
@@ -417,10 +416,9 @@ async function main() {
417
416
  }, { permissive: true });
418
417
  const cwd = process.cwd();
419
418
  const defaultOutDir = path.basename(cwd) === "backend" ? path.resolve(cwd, "..", "config", "collections") : path.resolve(cwd, "config", "collections");
420
- const outDir = args["--output"] || args["--collections"] || defaultOutDir;
419
+ const outDir = path.resolve(args["--output"] || args["--collections"] || defaultOutDir);
421
420
  const force = args["--force"] || false;
422
421
  const pgSchema = args["--schema"] || "public";
423
- if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
424
422
  const envPaths = [
425
423
  process.env.DOTENV_CONFIG_PATH,
426
424
  path.resolve(process.cwd(), ".env"),
@@ -443,10 +441,15 @@ async function main() {
443
441
  try {
444
442
  await client.connect();
445
443
  } catch (err) {
446
- outError(chalk.red(`✗ Failed to connect to database: ${err instanceof Error ? err.message : String(err)}`));
447
- 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
+ }
448
450
  process.exit(1);
449
451
  }
452
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
450
453
  const hostPart = safeHostFromUrl(databaseUrl);
451
454
  out(chalk.gray(`Connected to database: ${hostPart}`));
452
455
  out(chalk.gray(`Introspecting schema '${pgSchema}'...`));
@@ -475,14 +478,7 @@ async function main() {
475
478
  if (args["--no-data-inference"]) runDataInference = false;
476
479
  else if (args["--data-inference"] !== void 0) runDataInference = args["--data-inference"];
477
480
  else if (!process.stdin.isTTY) out(chalk.gray("Skipping data inference (non-interactive run; pass --data-inference to enable)."));
478
- else {
479
- const rl = import___vite_browser_external.default.createInterface({
480
- input: process.stdin,
481
- output: process.stdout
482
- });
483
- 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";
484
- rl.close();
485
- }
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) "));
486
482
  if (runDataInference) out(chalk.gray("Sampling database rows for data inference..."));
487
483
  const generatedFiles = [];
488
484
  const skippedFiles = [];