@rebasepro/server-postgres 0.17.3 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/BranchService-ByLfM_Tt.js +360 -0
  4. package/dist/BranchService-ByLfM_Tt.js.map +1 -0
  5. package/dist/__vite-browser-external-BnuHet1e.js +46 -0
  6. package/dist/__vite-browser-external-BnuHet1e.js.map +1 -0
  7. package/dist/arg-Dni7MzLB.js +87 -0
  8. package/dist/arg-Dni7MzLB.js.map +1 -0
  9. package/dist/auth-users-columns-D2LBFrMH.js +116 -0
  10. package/dist/auth-users-columns-D2LBFrMH.js.map +1 -0
  11. package/dist/backup-argv.d.ts +19 -0
  12. package/dist/backup-cli-oM9WQVlV.js +408 -0
  13. package/dist/backup-cli-oM9WQVlV.js.map +1 -0
  14. package/dist/backup-service-Br3tqwsy.js +715 -0
  15. package/dist/backup-service-Br3tqwsy.js.map +1 -0
  16. package/dist/branch-argv.d.ts +28 -0
  17. package/dist/branch-prune.d.ts +77 -0
  18. package/dist/cli-collections-path.d.ts +31 -0
  19. package/dist/cli-errors-DxN4yzdH.js +657 -0
  20. package/dist/cli-errors-DxN4yzdH.js.map +1 -0
  21. package/dist/cli-errors.d.ts +123 -0
  22. package/dist/cli-flags.d.ts +101 -0
  23. package/dist/cli-helpers.d.ts +94 -10
  24. package/dist/cli-output-CNdMql-L.js +47 -0
  25. package/dist/cli-output-CNdMql-L.js.map +1 -0
  26. package/dist/cli.js +2020 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/collection-index-BHgT_TKd.js +276 -0
  29. package/dist/collection-index-BHgT_TKd.js.map +1 -0
  30. package/dist/{collections-schema-version-BMeu3cgv.js → collections-schema-version-T6cGvrDi.js} +1 -2
  31. package/dist/{collections-schema-version-BMeu3cgv.js.map → collections-schema-version-T6cGvrDi.js.map} +1 -1
  32. package/dist/{connection-GOKU3Hu5.js → databasePoolManager-Bj5FbeAs.js} +112 -40
  33. package/dist/databasePoolManager-Bj5FbeAs.js.map +1 -0
  34. package/dist/databasePoolManager.d.ts +27 -0
  35. package/dist/doctor-D7HWmGzs.js +974 -0
  36. package/dist/doctor-D7HWmGzs.js.map +1 -0
  37. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-2c2NV8zX.js} +7 -27
  38. package/dist/ensure-collection-policies-2c2NV8zX.js.map +1 -0
  39. package/dist/ensure-collection-tables-D4wJzM4y.js +820 -0
  40. package/dist/ensure-collection-tables-D4wJzM4y.js.map +1 -0
  41. package/dist/ensure-tables-CIojK-Rx.js +650 -0
  42. package/dist/ensure-tables-CIojK-Rx.js.map +1 -0
  43. package/dist/generate-drizzle-schema-CwEgVyjO.js +123 -0
  44. package/dist/generate-drizzle-schema-CwEgVyjO.js.map +1 -0
  45. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +528 -0
  46. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +1 -0
  47. package/dist/{ensure-collection-tables-DgVixhX3.js → generate-postgres-ddl-logic-CtnpIBVv.js} +126 -984
  48. package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +1 -0
  49. package/dist/generated-schema-staleness-DQdfJl2A.js +157 -0
  50. package/dist/generated-schema-staleness-DQdfJl2A.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.es.js +7423 -1133
  53. package/dist/index.es.js.map +1 -1
  54. package/dist/introspect-db-logic-C6LQdTxj.js +1378 -0
  55. package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -0
  56. package/dist/policy-drift-kNJ6X2Uj.js +313 -0
  57. package/dist/policy-drift-kNJ6X2Uj.js.map +1 -0
  58. package/dist/{backup-service-DCk7KhhL.js → rls-bootstrap-sql-DLDnUjtx.js} +737 -2689
  59. package/dist/rls-bootstrap-sql-DLDnUjtx.js.map +1 -0
  60. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-DeYEjYLt.js} +65 -190
  61. package/dist/rls-enforcement-DeYEjYLt.js.map +1 -0
  62. package/dist/schema/bin/generate-drizzle-schema.d.ts +1 -0
  63. package/dist/schema/bin/generate-postgres-ddl.d.ts +1 -0
  64. package/dist/schema/column-type-drift.d.ts +79 -0
  65. package/dist/schema/doctor-cli.js +129 -0
  66. package/dist/schema/doctor-cli.js.map +1 -0
  67. package/dist/schema/doctor.d.ts +42 -0
  68. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  69. package/dist/schema/generate-drizzle-schema.d.ts +1 -1
  70. package/dist/schema/generate-drizzle-schema.js +24 -0
  71. package/dist/schema/generate-drizzle-schema.js.map +1 -0
  72. package/dist/schema/generate-next-step.d.ts +30 -0
  73. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  74. package/dist/schema/generate-postgres-ddl.d.ts +1 -1
  75. package/dist/schema/generate-postgres-ddl.js +138 -0
  76. package/dist/schema/generate-postgres-ddl.js.map +1 -0
  77. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  78. package/dist/schema/introspect-db-queries.d.ts +34 -0
  79. package/dist/schema/introspect-db.js +866 -0
  80. package/dist/schema/introspect-db.js.map +1 -0
  81. package/dist/security/rls-enforcement.d.ts +63 -2
  82. package/dist/services/BranchService.d.ts +86 -1
  83. package/dist/services/collection-helpers.d.ts +1 -1
  84. package/dist/services/realtimeService.d.ts +30 -2
  85. package/dist/services/row-pipeline.d.ts +12 -1
  86. package/dist/source-Br7L7GOI.js +410 -0
  87. package/dist/source-Br7L7GOI.js.map +1 -0
  88. package/dist/utils/pg-error-utils.d.ts +3 -1
  89. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  90. package/dist/vector-index-DkWxm2de.js +309 -0
  91. package/dist/vector-index-DkWxm2de.js.map +1 -0
  92. package/package.json +32 -27
  93. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  94. package/dist/collection-index-BRUg10H5.js +0 -5690
  95. package/dist/collection-index-BRUg10H5.js.map +0 -1
  96. package/dist/connection-GOKU3Hu5.js.map +0 -1
  97. package/dist/ensure-collection-policies-UCqgv_8c.js.map +0 -1
  98. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  99. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  100. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  101. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  102. package/dist/src-DiB5RP2Z.js +0 -541
  103. package/dist/src-DiB5RP2Z.js.map +0 -1
  104. package/dist/websocket-BZ4H5wUz.js +0 -8198
  105. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  106. package/src/PostgresAdapter.ts +0 -91
  107. package/src/PostgresBackendDriver.ts +0 -1857
  108. package/src/PostgresBootstrapper.ts +0 -1157
  109. package/src/auth/ensure-tables.ts +0 -896
  110. package/src/auth/schema-version.ts +0 -260
  111. package/src/auth/services.ts +0 -1610
  112. package/src/backup/backup-cli.ts +0 -520
  113. package/src/backup/backup-cron.ts +0 -212
  114. package/src/backup/backup-logic.ts +0 -93
  115. package/src/backup/backup-service.ts +0 -485
  116. package/src/backup/index.ts +0 -12
  117. package/src/backup/pg-tools.ts +0 -403
  118. package/src/backup/retention.ts +0 -86
  119. package/src/cli-errors.ts +0 -265
  120. package/src/cli-helpers.ts +0 -569
  121. package/src/cli-output.ts +0 -43
  122. package/src/cli.ts +0 -1339
  123. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  124. package/src/collections/buildRegistry.ts +0 -68
  125. package/src/collections/validate-relations.ts +0 -402
  126. package/src/connection.ts +0 -333
  127. package/src/data-transformer.ts +0 -845
  128. package/src/databasePoolManager.ts +0 -95
  129. package/src/history/HistoryService.ts +0 -254
  130. package/src/history/ensure-history-table.ts +0 -53
  131. package/src/index.ts +0 -19
  132. package/src/interfaces.ts +0 -60
  133. package/src/module-dir.ts +0 -7
  134. package/src/schema/atlas-argv.ts +0 -94
  135. package/src/schema/auth-schema.ts +0 -298
  136. package/src/schema/auth-users-columns.ts +0 -131
  137. package/src/schema/carved-out-migration.ts +0 -404
  138. package/src/schema/classify-change.ts +0 -436
  139. package/src/schema/collection-index.ts +0 -427
  140. package/src/schema/collections-schema-version.ts +0 -103
  141. package/src/schema/destructive-sql.ts +0 -94
  142. package/src/schema/doctor-cli.ts +0 -66
  143. package/src/schema/doctor-policy-checks.ts +0 -105
  144. package/src/schema/doctor.ts +0 -837
  145. package/src/schema/drizzle-ddl.ts +0 -33
  146. package/src/schema/dynamic-tables.test.ts +0 -302
  147. package/src/schema/dynamic-tables.ts +0 -293
  148. package/src/schema/ensure-collection-policies.ts +0 -198
  149. package/src/schema/ensure-collection-tables.test.ts +0 -518
  150. package/src/schema/ensure-collection-tables.ts +0 -1267
  151. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  152. package/src/schema/generate-drizzle-schema.ts +0 -143
  153. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  154. package/src/schema/generate-postgres-ddl.ts +0 -180
  155. package/src/schema/generate-schema-commit.ts +0 -267
  156. package/src/schema/generated-schema-staleness.ts +0 -284
  157. package/src/schema/introspect-db-constraints.ts +0 -385
  158. package/src/schema/introspect-db-inference.ts +0 -255
  159. package/src/schema/introspect-db-logic.ts +0 -1387
  160. package/src/schema/introspect-db-naming.ts +0 -15
  161. package/src/schema/introspect-db-project.ts +0 -78
  162. package/src/schema/introspect-db-queries.ts +0 -326
  163. package/src/schema/introspect-db-structure.ts +0 -670
  164. package/src/schema/introspect-db-types.ts +0 -56
  165. package/src/schema/introspect-db.ts +0 -257
  166. package/src/schema/introspect-runtime.test.ts +0 -260
  167. package/src/schema/introspect-runtime.ts +0 -327
  168. package/src/schema/non-sql-collections.test.ts +0 -131
  169. package/src/schema/rls-bootstrap-sql.ts +0 -288
  170. package/src/schema/search-column.ts +0 -643
  171. package/src/schema/test-schema.ts +0 -11
  172. package/src/schema/vector-index.ts +0 -491
  173. package/src/security/anonymous-grants.test.ts +0 -73
  174. package/src/security/policy-drift.test.ts +0 -435
  175. package/src/security/policy-drift.ts +0 -473
  176. package/src/security/rls-enforcement.ts +0 -505
  177. package/src/services/BranchService.ts +0 -326
  178. package/src/services/FetchService.ts +0 -2115
  179. package/src/services/PersistService.ts +0 -557
  180. package/src/services/RelationService.ts +0 -978
  181. package/src/services/RelationWriteService.ts +0 -653
  182. package/src/services/cdc/CdcListener.ts +0 -103
  183. package/src/services/cdc/junction-tables.ts +0 -91
  184. package/src/services/cdc/trigger-cdc.ts +0 -173
  185. package/src/services/channel-bus/ChannelBus.ts +0 -44
  186. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  187. package/src/services/channel-bus/index.ts +0 -114
  188. package/src/services/channel-history.ts +0 -424
  189. package/src/services/channel-presence.ts +0 -185
  190. package/src/services/collection-helpers.ts +0 -420
  191. package/src/services/dataService.ts +0 -256
  192. package/src/services/index.ts +0 -14
  193. package/src/services/junction-writes.ts +0 -295
  194. package/src/services/nested-path.ts +0 -145
  195. package/src/services/pg-notify-listener.ts +0 -151
  196. package/src/services/realtimeService.ts +0 -2545
  197. package/src/services/row-pipeline.ts +0 -240
  198. package/src/services/write-denial.ts +0 -55
  199. package/src/types.ts +0 -4
  200. package/src/utils/connection-string.ts +0 -58
  201. package/src/utils/drizzle-conditions.ts +0 -2743
  202. package/src/utils/pg-array-null-patch.ts +0 -42
  203. package/src/utils/pg-error-utils.ts +0 -391
  204. package/src/websocket.ts +0 -855
@@ -41,6 +41,17 @@ export interface DoctorPhase {
41
41
  * a comparison, and no comparison happened.
42
42
  */
43
43
  notApplicable?: string;
44
+ /**
45
+ * The phase was skipped by a fault rather than by a choice.
46
+ *
47
+ * `skipped` covers both: "no DATABASE_URL, so nothing to compare against"
48
+ * is a local situation a developer may be perfectly happy with, and "the
49
+ * DATABASE_URL you set refuses connections" is not. The report renders them
50
+ * the same way — a check that did not run is a check that did not run — but
51
+ * the exit code must not, or `rebase doctor` in CI goes green against a
52
+ * database it never reached.
53
+ */
54
+ blocked?: boolean;
44
55
  }
45
56
  export interface DoctorReport {
46
57
  collectionsToSchema: DoctorPhase;
@@ -64,6 +75,37 @@ export declare function getExpectedColumnType(prop: Property): string | null;
64
75
  export declare function loadCollections(collectionsPath: string): Promise<CollectionConfig[]>;
65
76
  export declare function checkCollectionsVsSchema(collections: CollectionConfig[], schemaFilePath: string): Promise<DoctorPhase>;
66
77
  export declare function checkCollectionsVsSdk(collections: CollectionConfig[], sdkFilePath: string): Promise<DoctorPhase>;
78
+ /**
79
+ * Extensions the collections need, that the database does not have installed.
80
+ *
81
+ * Only `vector` today, and only because a `{ type: "vector" }` property plans a
82
+ * column of a type that will not exist. Rebase installs it only where a project
83
+ * has said it may (`database({ extensions: ["vector"] })`), so the absence is
84
+ * often correct configuration and a wrong deployment — which is exactly why the
85
+ * report has to name it rather than the first failing INSERT.
86
+ *
87
+ * Exported for its test.
88
+ */
89
+ export declare function missingExtensionIssues(collections: CollectionConfig[], installed: ReadonlySet<string>): DoctorIssue[];
90
+ /**
91
+ * The stamp the runtime wrote, against the collections on disk.
92
+ *
93
+ * A stamp that disagrees means this database was provisioned from a different
94
+ * set of collections than the ones in this checkout — a colleague's branch, an
95
+ * older deploy, a tenant somebody else migrated. Everything else in this report
96
+ * is then describing a schema somebody else created, which is worth knowing
97
+ * before acting on any of it.
98
+ *
99
+ * A warning, not an error, and never a direction: the stamp is a hash, so it
100
+ * can say the two disagree and never which is ahead. `null` — never stamped —
101
+ * is not a finding: every database provisioned before the stamp existed reads
102
+ * that way, and so does every fresh one.
103
+ */
104
+ export declare function schemaStampIssues(pool: {
105
+ query<T>(text: string): Promise<{
106
+ rows: T[];
107
+ }>;
108
+ }, collections: CollectionConfig[]): Promise<DoctorIssue[]>;
67
109
  export declare function checkCollectionsVsDatabase(collections: CollectionConfig[], databaseUrl: string): Promise<DoctorPhase>;
68
110
  export declare function renderReport(report: DoctorReport): void;
69
111
  export declare function runDoctor(options: {
@@ -27,6 +27,7 @@
27
27
  */
28
28
  import { type CollectionConfig } from "@rebasepro/types";
29
29
  import { type SkippedVectorIndex } from "./vector-index.js";
30
+ import { type ColumnTypeDrift } from "./column-type-drift.js";
30
31
  /**
31
32
  * The subset of a database handle this needs: run a statement, get rows back.
32
33
  *
@@ -75,6 +76,15 @@ export interface ExistingSchema {
75
76
  enumValues?: Map<string, string[]>;
76
77
  /** `schema.table.column` for every column the database marks NOT NULL. */
77
78
  notNullColumns?: Set<string>;
79
+ /**
80
+ * `schema.table.column` → the column's `udt_name` as the database reports
81
+ * it (`int4`, `jsonb`, `_numeric` for an array of numeric).
82
+ *
83
+ * The only evidence that a column's declared type and its real type have
84
+ * parted company. Absent is read as "unknown", which reports no drift —
85
+ * see `column-type-drift.ts` for why silence is the right unknown here.
86
+ */
87
+ columnTypes?: Map<string, string>;
78
88
  /**
79
89
  * Tables known to hold at least one row.
80
90
  *
@@ -85,6 +95,14 @@ export interface ExistingSchema {
85
95
  * constraint rather than attempting one that aborts the boot.
86
96
  */
87
97
  populatedTables?: Set<string>;
98
+ /**
99
+ * `schema.table.column` for every column the database gives a DEFAULT.
100
+ *
101
+ * Paired with {@link notNullColumns} to decide whether a column no property
102
+ * declares can still accept a write: NOT NULL with a default can, NOT NULL
103
+ * without one cannot. Absent is read as "unknown", which reports nothing.
104
+ */
105
+ columnDefaults?: Set<string>;
88
106
  }
89
107
  /**
90
108
  * How far the planner may go in making the database's constraints match the
@@ -179,6 +197,52 @@ export interface EnsurePlan {
179
197
  * to read.
180
198
  */
181
199
  withheldConstraints: WithheldConstraint[];
200
+ /**
201
+ * Columns whose type in the database is in a different family from the one
202
+ * the collection declares.
203
+ *
204
+ * Reported and never acted on — see `column-type-drift.ts`. This is the one
205
+ * divergence that presents as a *working* deploy: the statement was never
206
+ * planned, so nothing failed, and the first evidence is a write rejected
207
+ * hours later by whichever end validates first.
208
+ */
209
+ columnTypeDrift: ColumnTypeDrift[];
210
+ /**
211
+ * Columns that are NOT NULL, have no default, and that no property declares
212
+ * any more — so every insert through the API is rejected for a field the
213
+ * author cannot name, because they already deleted it.
214
+ *
215
+ * This is what a **rename** looks like to an additive provisioner. Rename
216
+ * `title` to `headline` and boot does exactly what it promises: it adds
217
+ * `headline` (nullable, correctly, and says why) and leaves `title` alone,
218
+ * because dropping a column is destructive and this runs unattended. The
219
+ * table is then unwritable:
220
+ *
221
+ * POST /api/data/posts -> 400 PG_23502
222
+ * Missing required field: "title" in "posts" cannot be empty.
223
+ *
224
+ * naming a field that is no longer in the collection at all. On the default
225
+ * first-run path there is no way out inside the product either: the managed
226
+ * database is PGlite, and `db push` — the documented repair — refuses there
227
+ * because Atlas needs a second database to diff against.
228
+ *
229
+ * Reported, never acted on. Dropping the column is the right fix roughly
230
+ * always and destructive exactly once, which is not a decision to take
231
+ * unattended.
232
+ */
233
+ orphanedRequiredColumns: OrphanedRequiredColumn[];
234
+ }
235
+ /**
236
+ * A NOT NULL column with no default that no property declares.
237
+ * @see EnsurePlan.orphanedRequiredColumns
238
+ */
239
+ export interface OrphanedRequiredColumn {
240
+ /** `schema.table`. */
241
+ table: string;
242
+ /** The column left behind. */
243
+ column: string;
244
+ /** The collection that no longer declares it. */
245
+ slug: string;
182
246
  }
183
247
  /** A constraint the configuration asks for that the planner is not applying. */
184
248
  export interface WithheldConstraint {
@@ -276,3 +340,29 @@ export declare function readSchemaFactsFor(client: Queryable, collections: Colle
276
340
  * The error is surfaced with the statement that caused it.
277
341
  */
278
342
  export declare function ensureCollectionTables(client: Queryable, collections: CollectionConfig[], log?: (message: string) => void, options?: EnsureOptions): Promise<EnsureOutcome>;
343
+ /**
344
+ * What actually went wrong, rather than what the ORM said about it.
345
+ *
346
+ * Drizzle wraps every driver error, and its `message` is literally
347
+ * ``Failed query: ${sql}\nparams: ${params}`` — the statement we already know,
348
+ * and not one word of Postgres's answer. Reporting `err.message` therefore
349
+ * produced a failure that named the statement twice and the *reason* zero
350
+ * times, so four unrelated boot failures — a missing extension, a wrong
351
+ * privilege, a bad index method, a constraint the rows violate — all read
352
+ * identically to whoever had to fix them.
353
+ *
354
+ * Two consequences, both observed in the field before this existed:
355
+ *
356
+ * 1. On a managed deployment the reason never reaches anybody. Boot dies, the
357
+ * pod never goes ready, and the deploy reports a readiness timeout — so the
358
+ * one line that says why is the one line that was thrown away here.
359
+ * 2. {@link vectorExtensionHint} matches on the error text. Against the
360
+ * wrapper it matched nothing, so the hint written specifically to explain a
361
+ * missing pgvector never fired on the path that raises it.
362
+ *
363
+ * `detail` and `hint` come along because they are the fields Postgres uses to
364
+ * say which object it meant and what to do about it — `CREATE EXTENSION` names
365
+ * the missing library there, and a rejected constraint names the row. This
366
+ * writes into the tenant's own boot log, about the tenant's own database.
367
+ */
368
+ export declare function describeDriverError(err: unknown): string;
@@ -1 +1 @@
1
- export {};
1
+ export declare const main: () => Promise<void>;
@@ -0,0 +1,24 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ __createRequire(import.meta.url);
3
+ import { t as main } from "../generate-drizzle-schema-CwEgVyjO.js";
4
+ //#region src/schema/bin/generate-drizzle-schema.ts
5
+ /**
6
+ * The built entry point for `generate-drizzle-schema`.
7
+ *
8
+ * `generate-drizzle-schema.ts` guards its own `main()` with
9
+ * `import.meta.url.endsWith(process.argv[1])`, because `src/index.ts` re-exports
10
+ * the module and importing the driver must not run the generator. That guard is
11
+ * correct when the file IS the process entry — which is how it runs from source
12
+ * under tsx — and silently false once the module is bundled: the executed file
13
+ * is a stub, the code lives in a shared chunk, and the chunk's `import.meta.url`
14
+ * is not the stub's path. The generator then did nothing, exited 0, and the
15
+ * caller reported success over an unwritten schema.
16
+ *
17
+ * So the built path gets an entry that says what it means.
18
+ */
19
+ main().catch(() => {
20
+ process.exitCode = 1;
21
+ });
22
+ //#endregion
23
+
24
+ //# sourceMappingURL=generate-drizzle-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-drizzle-schema.js","names":[],"sources":["../../src/schema/bin/generate-drizzle-schema.ts"],"sourcesContent":["/**\n * The built entry point for `generate-drizzle-schema`.\n *\n * `generate-drizzle-schema.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-drizzle-schema\";\n\nmain().catch(() => {\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgBA,KAAK,CAAC,CAAC,YAAY;CACf,QAAQ,WAAW;AACvB,CAAC"}
@@ -0,0 +1,30 @@
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
+ /**
15
+ * What to do with the file that was just generated.
16
+ *
17
+ * Not the same answer on the managed development database: `rebase db generate`
18
+ * plans with Atlas, Atlas needs a second empty database to diff against, and
19
+ * PGlite serves exactly one — so the CLI refuses that command outright there.
20
+ * This line recommended it unconditionally, which meant every stock scaffold
21
+ * was told, by the tool itself, to run something the same tool would refuse.
22
+ *
23
+ * The kind arrives as `REBASE_DEV_DATABASE_KIND`, set by the CLI from the
24
+ * ordered resolution in `dev-db/resolve.ts`. It is a pure decision — nothing is
25
+ * started to make it — and the driver cannot make it for itself: on the managed
26
+ * path `DATABASE_URL` is a perfectly ordinary connection string to a Postgres
27
+ * on loopback. Absent (the driver run directly, outside the CLI) keeps the
28
+ * original wording.
29
+ */
30
+ export declare const nextStepAfterGenerate: () => string;
@@ -9,6 +9,34 @@ export declare const isNumericId: (collection: CollectionConfig) => boolean;
9
9
  export declare const getPrimaryKeyName: (collection: CollectionConfig) => string;
10
10
  export declare const isIdProperty: (propName: string, prop: Property, collection: CollectionConfig) => boolean;
11
11
  type ResolveCollection = (slug: string) => CollectionConfig | undefined;
12
+ /**
13
+ * The `ON DELETE` a foreign key gets when the author did not say.
14
+ *
15
+ * One rule for every property that emits one — `belongsTo` relations and
16
+ * `reference` properties alike. `reference` kept its own `CASCADE` default for
17
+ * a release after this rule was written, which is exactly the split this
18
+ * function exists to prevent: two spellings of the same link, two data-retention
19
+ * behaviours, and only one of them documented.
20
+ *
21
+ * An optional link is `SET NULL`: the column can hold NULL, so dropping the
22
+ * parent leaves the child row with an empty pointer, which is what "optional"
23
+ * already means.
24
+ *
25
+ * A required link is **`RESTRICT`**, not `CASCADE`. `NOT NULL` says the child
26
+ * cannot exist without a parent; it does not say deleting the parent should
27
+ * take the child with it. That second claim is a data-retention decision, and
28
+ * defaulting to it meant `onDelete` — a field nobody has to write — silently
29
+ * turned every `DELETE FROM authors` into a cascade through posts, comments and
30
+ * anything else that hung off them. `RESTRICT` fails the delete instead and
31
+ * names the constraint, so the author chooses `cascade` on purpose or clears
32
+ * the children first.
33
+ *
34
+ * Both DDL paths — the `CREATE TABLE` generator and {@link planRelationalColumns},
35
+ * which boot-ensure compares the live database against — call this, so a
36
+ * default that appears in one and not the other cannot make `db push` plan a
37
+ * constraint rewrite on every run.
38
+ */
39
+ export declare const defaultBelongsToOnDelete: (required: boolean | undefined) => "RESTRICT" | "SET NULL";
12
40
  /**
13
41
  * The individual SQL statements a single security rule compiles to: a
14
42
  * `DROP POLICY IF EXISTS` / `CREATE POLICY` pair per operation, each a complete
@@ -1 +1 @@
1
- export {};
1
+ export declare const main: () => Promise<void>;
@@ -0,0 +1,138 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ __createRequire(import.meta.url);
3
+ import { a as generatePostgresVectorDdl, i as generatePostgresSearchDdl, n as generatePostgresDdl, r as generatePostgresPoliciesDdl } from "../generate-postgres-ddl-logic-CtnpIBVv.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";
9
+ import path from "path";
10
+ import { pathToFileURL } from "url";
11
+ //#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
+ /**
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.
38
+ */
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
+ var runGeneration = async (collectionsFilePath, outputPath) => {
45
+ 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 = await generatePostgresDdl(collections, {
53
+ includePolicies: false,
54
+ includeSearch: false,
55
+ includeVector: false
56
+ });
57
+ const policiesContent = generatePostgresPoliciesDdl(collections);
58
+ const searchContent = generatePostgresSearchDdl(collections);
59
+ const vectorContent = generatePostgresVectorDdl(collections, { extensions: declaredDatabaseExtensions() });
60
+ if (outputPath) {
61
+ const outputDir = path.dirname(outputPath);
62
+ await promises.mkdir(outputDir, { recursive: true });
63
+ await promises.writeFile(outputPath, ddlContent);
64
+ out(`✅ PostgreSQL DDL generated successfully at ${outputPath}`);
65
+ const policiesPath = path.join(outputDir, "policies.sql");
66
+ await promises.writeFile(policiesPath, policiesContent);
67
+ out(`✅ PostgreSQL Policies DDL generated successfully at ${policiesPath}`);
68
+ const searchPath = path.join(outputDir, "search.sql");
69
+ if (searchContent) {
70
+ await promises.writeFile(searchPath, searchContent);
71
+ out(`✅ PostgreSQL search DDL generated successfully at ${searchPath}`);
72
+ } else if (fs$1.existsSync(searchPath)) await promises.rm(searchPath);
73
+ const vectorPath = path.join(outputDir, "vector.sql");
74
+ if (vectorContent) {
75
+ await promises.writeFile(vectorPath, vectorContent);
76
+ out(`✅ PostgreSQL vector DDL generated successfully at ${vectorPath}`);
77
+ } else if (fs$1.existsSync(vectorPath)) await promises.rm(vectorPath);
78
+ } else {
79
+ out("✅ PostgreSQL DDL generated successfully.");
80
+ out(String(ddlContent));
81
+ out("\n✅ PostgreSQL Policies DDL generated successfully.");
82
+ out(String(policiesContent));
83
+ }
84
+ } catch (error) {
85
+ outError(`Error generating DDL schema: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
86
+ process.exitCode = 1;
87
+ throw error;
88
+ }
89
+ };
90
+ var main = async () => {
91
+ const collectionsFilePathArg = process.argv.find((arg) => arg.startsWith("--collections="));
92
+ const collectionsFilePath = collectionsFilePathArg ? collectionsFilePathArg.split("=")[1] : process.argv[2];
93
+ const outputPathArg = process.argv.find((arg) => arg.startsWith("--output="));
94
+ const outputPath = outputPathArg ? outputPathArg.split("=")[1] : void 0;
95
+ const watch = process.argv.includes("--watch");
96
+ if (!collectionsFilePath) {
97
+ out("Usage: ts-node generate-postgres-ddl.ts <path-to-collections-file> [--output <path-to-output-file>] [--watch]");
98
+ return;
99
+ }
100
+ const resolvedPath = path.resolve(process.cwd(), collectionsFilePath);
101
+ const resolvedOutputPath = outputPath ? path.resolve(process.cwd(), outputPath) : void 0;
102
+ if (watch) {
103
+ out(`Watching for changes in ${resolvedPath}...`);
104
+ const { default: chokidar } = await import("chokidar");
105
+ chokidar.watch(resolvedPath, {
106
+ persistent: true,
107
+ ignoreInitial: false
108
+ }).on("all", (event, filePath) => {
109
+ out(`[${event}] ${filePath}. Regenerating DDL schema...`);
110
+ runGeneration(resolvedPath, resolvedOutputPath).catch(() => void 0);
111
+ });
112
+ } else await runGeneration(resolvedPath, resolvedOutputPath);
113
+ };
114
+ if (import.meta.url.endsWith(process.argv[1])) main().catch(() => {
115
+ process.exitCode = 1;
116
+ });
117
+ //#endregion
118
+ //#region src/schema/bin/generate-postgres-ddl.ts
119
+ /**
120
+ * The built entry point for `generate-postgres-ddl`.
121
+ *
122
+ * `generate-postgres-ddl.ts` guards its own `main()` with
123
+ * `import.meta.url.endsWith(process.argv[1])`, because `src/index.ts` re-exports
124
+ * the module and importing the driver must not run the generator. That guard is
125
+ * correct when the file IS the process entry — which is how it runs from source
126
+ * under tsx — and silently false once the module is bundled: the executed file
127
+ * is a stub, the code lives in a shared chunk, and the chunk's `import.meta.url`
128
+ * is not the stub's path. The generator then did nothing, exited 0, and the
129
+ * caller reported success over an unwritten schema.
130
+ *
131
+ * So the built path gets an entry that says what it means.
132
+ */
133
+ main().catch(() => {
134
+ process.exitCode = 1;
135
+ });
136
+ //#endregion
137
+
138
+ //# sourceMappingURL=generate-postgres-ddl.js.map
@@ -0,0 +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 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 = await 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\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 } 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":";;;;;;;;;;;;AAeA,IAAM,mBAAmB;CAAC;CAAgB;CAAgB;CAAsB;AAAoB;;;;;;;;;;;;;;;;;;;;;AAsBpG,eAAe,sBAAsB,WAAkC;CACnE,MAAM,QAAQ,iBACT,KAAI,SAAQ,KAAK,KAAK,WAAW,IAAI,CAAC,CAAC,CACvC,MAAK,cAAa,KAAG,WAAW,SAAS,CAAC;CAC/C,IAAI,CAAC,OAAO;CACZ,MAAM,OAAO,cAAc,KAAK,CAAC,CAAC;AACtC;AAEA,IAAM,gBAAgB,OAAO,qBAA8B,eAAwB;CAC/E,IAAI;EACA,IAAI,CAAC,qBACD,MAAM,IAAI,MAAM,oCAAoC;EAGxD,MAAM,eAAe,KAAK,QAAQ,mBAAmB;EAKrD,MAAM,sBAAsB,KAAK,QAAQ,YAAY,CAAC;EAItD,IAAI,cAAkC,MAAM,6BAA6B,YAAY;EAGrF,IAAI,CAAC,eAAe,CAAC,MAAM,QAAQ,WAAW,GAC1C,cAAc,CAAC;EAInB,YAAY,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;EAMvD,MAAM,aAAa,MAAM,oBAAoB,aAAa;GACtD,iBAAiB;GACjB,eAAe;GACf,eAAe;EACnB,CAAC;EACD,MAAM,kBAAkB,4BAA4B,WAAW;EAC/D,MAAM,gBAAgB,0BAA0B,WAAW;EAC3D,MAAM,gBAAgB,0BAA0B,aAAa,EACzD,YAAY,2BAA2B,EAC3C,CAAC;EAED,IAAI,YAAY;GACZ,MAAM,YAAY,KAAK,QAAQ,UAAU;GACzC,MAAM,SAAW,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;GACrD,MAAM,SAAW,UAAU,YAAY,UAAU;GACjD,IAAI,8CAA8C,YAAY;GAE9D,MAAM,eAAe,KAAK,KAAK,WAAW,cAAc;GACxD,MAAM,SAAW,UAAU,cAAc,eAAe;GACxD,IAAI,uDAAuD,cAAc;GAKzE,MAAM,aAAa,KAAK,KAAK,WAAW,YAAY;GACpD,IAAI,eAAe;IACf,MAAM,SAAW,UAAU,YAAY,aAAa;IACpD,IAAI,qDAAqD,YAAY;GACzE,OAAO,IAAI,KAAG,WAAW,UAAU,GAC/B,MAAM,SAAW,GAAG,UAAU;GAQlC,MAAM,aAAa,KAAK,KAAK,WAAW,YAAY;GACpD,IAAI,eAAe;IACf,MAAM,SAAW,UAAU,YAAY,aAAa;IACpD,IAAI,qDAAqD,YAAY;GACzE,OAAO,IAAI,KAAG,WAAW,UAAU,GAC/B,MAAM,SAAW,GAAG,UAAU;EAEtC,OAAO;GACH,IAAI,0CAA0C;GAC9C,IAAI,OAAO,UAAU,CAAC;GACtB,IAAI,qDAAqD;GACzD,IAAI,OAAO,eAAe,CAAC;EAC/B;CAEJ,SAAS,OAAO;EACZ,SAAS,gCAAgC,iBAAiB,QAAS,MAAM,SAAS,MAAM,UAAW,OAAO,KAAK,GAAG;EASlH,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;;;;;;;;;;;;;;;;;ACnLL,KAAK,CAAC,CAAC,YAAY;CACf,QAAQ,WAAW;AACvB,CAAC"}
@@ -56,3 +56,40 @@ export interface MissingGeneratedName {
56
56
  export declare function findMissingGeneratedNames(generatedSource: string, collections: CollectionConfig[]): MissingGeneratedName[];
57
57
  /** One-line summary for a log or a CLI notice. */
58
58
  export declare function describeMissingGeneratedNames(missing: MissingGeneratedName[]): string;
59
+ /** What `rebase schema stale` found, and what that costs. */
60
+ export interface StaleVerdict {
61
+ /** The lines to print, in order. Empty only under `--fix` with nothing to fix. */
62
+ lines: string[];
63
+ /** Non-zero only when the reader must act before the server will boot. */
64
+ exitCode: 0 | 1;
65
+ /** True when `--fix` should regenerate. */
66
+ regenerate: boolean;
67
+ }
68
+ /**
69
+ * The whole of `schema stale`'s decision, separated from doing it.
70
+ *
71
+ * Separated because of what the command used to do on the clean path: **exit 0
72
+ * having written zero bytes to stdout and zero to stderr.** For a command the
73
+ * top-level help describes as "Report generated schema files the collections
74
+ * have moved past", that is indistinguishable from a no-op, a crash, and a
75
+ * subcommand the driver does not implement. Three of its four paths were
76
+ * silent, and none of them could be tested where they stood: `cli.ts` uses
77
+ * `import.meta`, which the driver's jest suite cannot load at all.
78
+ *
79
+ * `--fix` is the exception, and stays silent when there is nothing to fix.
80
+ * `rebase dev` runs `schema stale --fix` before every boot with inherited
81
+ * stdio, and "nothing was wrong" is not news a hundred lines into a start-up
82
+ * transcript. The flag is the automated caller's; its silence is the point.
83
+ */
84
+ export declare function staleVerdict(input: {
85
+ /** `--output`, as the reader typed it. */
86
+ outputPath: string;
87
+ /** Whether the generated schema exists at all. */
88
+ generatedExists: boolean;
89
+ /** The legacy foreign-key names found in it, if it was read. */
90
+ stale: LegacyForeignKeyName[];
91
+ /** Why the comparison could not be made, when it could not. */
92
+ unreadable?: string;
93
+ /** Whether `--fix` was given. */
94
+ fix: boolean;
95
+ }): StaleVerdict;
@@ -16,6 +16,40 @@ export interface QueryableClient {
16
16
  rows: R[];
17
17
  }>;
18
18
  }
19
+ /**
20
+ * A Postgres identifier, quoted so that whatever is inside it stays inside it.
21
+ *
22
+ * Introspection reads table and schema names out of `pg_class` and puts them
23
+ * straight back into SQL, and wrapping a name in double quotes is not the same
24
+ * as escaping it: a table named `x" ; DROP TABLE users; --` closes the quote
25
+ * and then the statement. Postgres's own escape for a quote inside a quoted
26
+ * identifier is to double it, which is what this does.
27
+ *
28
+ * "The catalogue is trusted" is true of your own database, and is exactly the
29
+ * assumption this tool cannot make. Introspection exists to be pointed at
30
+ * databases somebody else built, and it runs with the privileges of whoever
31
+ * ran it — which is usually a superuser, because that is who has a connection
32
+ * string to a database they are adopting.
33
+ */
34
+ export declare function quoteIdentifier(name: string): string;
35
+ /** `"schema"."table"`, both halves quoted. */
36
+ export declare function quoteRelation(schema: string, table: string): string;
37
+ /**
38
+ * Can this table's name be the name of the file describing it?
39
+ *
40
+ * Introspection writes `<collectionsDir>/<tableName>.ts`, and the table name
41
+ * came from a database this tool did not create. Postgres will hold a table
42
+ * called `../../../etc/cron.d/x`, and `path.join` resolves it — so without this
43
+ * the generator writes a file wherever the name points, as whoever ran the
44
+ * command.
45
+ *
46
+ * Deliberately narrow, and deliberately not a blocklist of `..` and `/`: the
47
+ * question is "is this a plain file name?", and every answer that is not
48
+ * obviously yes is a table better written by hand. NUL and the Windows
49
+ * separator are in scope because a collections directory is checked out on
50
+ * whatever machine the developer has.
51
+ */
52
+ export declare function isUsableFileName(name: string): boolean;
19
53
  /**
20
54
  * Base tables, excluding partitions.
21
55
  *