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

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 (162) hide show
  1. package/dist/{BranchService-BRt78gfa.js → BranchService-Bsn2wfaj.js} +3 -2
  2. package/dist/{BranchService-BRt78gfa.js.map → BranchService-Bsn2wfaj.js.map} +1 -1
  3. package/dist/PostgresBackendDriver.d.ts +83 -2
  4. package/dist/auth/ensure-tables.d.ts +12 -0
  5. package/dist/auth/services.d.ts +6 -0
  6. package/dist/backup/backup-cli.d.ts +14 -0
  7. package/dist/backup/backup-service.d.ts +2 -2
  8. package/dist/backup/pg-binary.d.ts +5 -0
  9. package/dist/{backup-cli-DW5p9_zv.js → backup-cli-aUfRv16h.js} +37 -29
  10. package/dist/backup-cli-aUfRv16h.js.map +1 -0
  11. package/dist/{backup-service-EwcDVG-8.js → backup-service-BTBB7RXn.js} +55 -21
  12. package/dist/backup-service-BTBB7RXn.js.map +1 -0
  13. package/dist/boot-ddl-DVpsFN6r.js +105 -0
  14. package/dist/boot-ddl-DVpsFN6r.js.map +1 -0
  15. package/dist/cli-errors-CaI4ntyD.js +1919 -0
  16. package/dist/cli-errors-CaI4ntyD.js.map +1 -0
  17. package/dist/cli-errors.d.ts +51 -5
  18. package/dist/{cli-flags-BglvpjHv.js → cli-flags-B-Dawocl.js} +5 -2
  19. package/dist/cli-flags-B-Dawocl.js.map +1 -0
  20. package/dist/cli-helpers.d.ts +40 -0
  21. package/dist/cli-output.d.ts +16 -28
  22. package/dist/cli-push.d.ts +87 -0
  23. package/dist/cli.js +563 -182
  24. package/dist/cli.js.map +1 -1
  25. package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-drZIsSNf.js} +10 -4
  26. package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-drZIsSNf.js.map} +1 -1
  27. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-BPoNILnD.js} +19 -110
  28. package/dist/connection-BPoNILnD.js.map +1 -0
  29. package/dist/connection.d.ts +9 -1
  30. package/dist/databasePoolManager-BkEy2w18.js +119 -0
  31. package/dist/databasePoolManager-BkEy2w18.js.map +1 -0
  32. package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-DP1_SNFo.js} +484 -346
  33. package/dist/deferred-index-build-DP1_SNFo.js.map +1 -0
  34. package/dist/{doctor-C-sYWbmt.js → doctor-C4WiftHm.js} +63 -52
  35. package/dist/doctor-C4WiftHm.js.map +1 -0
  36. package/dist/ensure-collection-policies-CXpbeNag.js +541 -0
  37. package/dist/ensure-collection-policies-CXpbeNag.js.map +1 -0
  38. package/dist/ensure-collection-tables-BhsU6qUE.js +532 -0
  39. package/dist/ensure-collection-tables-BhsU6qUE.js.map +1 -0
  40. package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-CHsgyLqg.js} +172 -76
  41. package/dist/ensure-tables-CHsgyLqg.js.map +1 -0
  42. package/dist/enum-labels-kept-D8fI1MoX.js +56 -0
  43. package/dist/enum-labels-kept-D8fI1MoX.js.map +1 -0
  44. package/dist/generate-drizzle-schema-Bz52jH2h.js +57 -0
  45. package/dist/generate-drizzle-schema-Bz52jH2h.js.map +1 -0
  46. package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-B74WN37S.js} +5 -5
  47. package/dist/generate-drizzle-schema-logic-B74WN37S.js.map +1 -0
  48. package/dist/generate-files-CdEckX6c.js +199 -0
  49. package/dist/generate-files-CdEckX6c.js.map +1 -0
  50. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B64O2oZF.js} +3 -3
  51. package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B64O2oZF.js.map} +1 -1
  52. package/dist/generated-sql.d.ts +8 -2
  53. package/dist/history/HistoryService.d.ts +38 -6
  54. package/dist/history/ensure-history-table.d.ts +42 -4
  55. package/dist/index.es.js +2922 -7475
  56. package/dist/index.es.js.map +1 -1
  57. package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-C-Di8dA9.js} +44 -6
  58. package/dist/introspect-db-logic-C-Di8dA9.js.map +1 -0
  59. package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-D6kAqReI.js} +23 -1
  60. package/dist/introspect-db-queries-D6kAqReI.js.map +1 -0
  61. package/dist/pg-error-utils-vJezlXdI.js +388 -0
  62. package/dist/pg-error-utils-vJezlXdI.js.map +1 -0
  63. package/dist/{plan-schema-DU9exq6C.js → plan-schema-B1YF_tYq.js} +151 -41
  64. package/dist/plan-schema-B1YF_tYq.js.map +1 -0
  65. package/dist/{policy-drift-B-J2hhm0.js → policy-drift-Dm8i-s1J.js} +23 -6
  66. package/dist/policy-drift-Dm8i-s1J.js.map +1 -0
  67. package/dist/push-index-prebuild-Db4AQF4i.js +45 -0
  68. package/dist/push-index-prebuild-Db4AQF4i.js.map +1 -0
  69. package/dist/push-legacy-renames-BK6ilQq2.js +34 -0
  70. package/dist/push-legacy-renames-BK6ilQq2.js.map +1 -0
  71. package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CtC3y9Ol.js} +8 -4
  72. package/dist/render-ddl-CtC3y9Ol.js.map +1 -0
  73. package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-RQUljnJ1.js} +125 -1270
  74. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +1 -0
  75. package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-DvzqlaL1.js} +129 -17
  76. package/dist/rls-enforcement-DvzqlaL1.js.map +1 -0
  77. package/dist/schema/auth-schema.d.ts +2 -2
  78. package/dist/schema/boot-ddl.d.ts +47 -0
  79. package/dist/schema/collection-index.d.ts +45 -0
  80. package/dist/schema/column-plan-helpers.d.ts +5 -0
  81. package/dist/schema/deferred-index-build.d.ts +131 -0
  82. package/dist/schema/destructive-sql.d.ts +7 -0
  83. package/dist/schema/doctor-cli.js +110 -6
  84. package/dist/schema/doctor-cli.js.map +1 -1
  85. package/dist/schema/doctor-search-scale.d.ts +45 -0
  86. package/dist/schema/doctor.d.ts +20 -1
  87. package/dist/schema/ensure-collection-policies.d.ts +73 -35
  88. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  89. package/dist/schema/enum-labels-kept.d.ts +26 -0
  90. package/dist/schema/generate-drizzle-schema.js +1 -1
  91. package/dist/schema/generate-files.d.ts +44 -0
  92. package/dist/schema/generate-next-step.d.ts +0 -13
  93. package/dist/schema/generate-postgres-ddl.js +10 -81
  94. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  95. package/dist/schema/introspect-db-logic.d.ts +12 -0
  96. package/dist/schema/introspect-db-queries.d.ts +11 -1
  97. package/dist/schema/introspect-db-structure.d.ts +10 -0
  98. package/dist/schema/introspect-db.js +4 -5
  99. package/dist/schema/introspect-db.js.map +1 -1
  100. package/dist/schema/migration-post-process.d.ts +34 -0
  101. package/dist/schema/plan/diff-plan.d.ts +126 -0
  102. package/dist/schema/plan/types.d.ts +8 -0
  103. package/dist/schema/push-index-prebuild.d.ts +25 -0
  104. package/dist/schema/push-legacy-renames.d.ts +20 -0
  105. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  106. package/dist/security/rls-enforcement.d.ts +32 -4
  107. package/dist/services/FetchService.d.ts +58 -8
  108. package/dist/services/PersistService.d.ts +15 -0
  109. package/dist/services/RelationService.d.ts +88 -3
  110. package/dist/services/cdc/CdcListener.d.ts +17 -6
  111. package/dist/services/cdc/trigger-cdc.d.ts +151 -27
  112. package/dist/services/channel-history.d.ts +15 -4
  113. package/dist/services/collection-helpers.d.ts +15 -0
  114. package/dist/services/field-viewer.d.ts +15 -1
  115. package/dist/services/pg-notify-listener.d.ts +7 -0
  116. package/dist/services/realtimeService.d.ts +314 -14
  117. package/dist/services/row-pipeline.d.ts +16 -9
  118. package/dist/services/socket-liveness.d.ts +42 -2
  119. package/dist/services/sql-script.d.ts +207 -3
  120. package/dist/services/vector-iterative-scan.d.ts +40 -0
  121. package/dist/services/write-depth.d.ts +6 -0
  122. package/dist/services/write-transaction-scope.d.ts +9 -0
  123. package/dist/socket-liveness-D3UD5_BW.js +7570 -0
  124. package/dist/socket-liveness-D3UD5_BW.js.map +1 -0
  125. package/dist/socket-opt-ins.d.ts +64 -0
  126. package/dist/source-DBDbBTL4.js +811 -0
  127. package/dist/source-DBDbBTL4.js.map +1 -0
  128. package/dist/tsx-loader.d.ts +14 -0
  129. package/dist/utils/drizzle-conditions.d.ts +21 -1
  130. package/dist/utils/error-cause.d.ts +7 -0
  131. package/dist/utils/pg-error-utils.d.ts +2 -4
  132. package/dist/utils/result-rows.d.ts +10 -0
  133. package/dist/websocket.d.ts +13 -0
  134. package/package.json +7 -7
  135. package/dist/backup-cli-DW5p9_zv.js.map +0 -1
  136. package/dist/backup-service-EwcDVG-8.js.map +0 -1
  137. package/dist/cli-errors-DsA-K9uP.js +0 -893
  138. package/dist/cli-errors-DsA-K9uP.js.map +0 -1
  139. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  140. package/dist/cli-output-CNdMql-L.js +0 -47
  141. package/dist/cli-output-CNdMql-L.js.map +0 -1
  142. package/dist/collection-index-DdnsxiJ_.js +0 -286
  143. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  144. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  145. package/dist/doctor-C-sYWbmt.js.map +0 -1
  146. package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
  147. package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
  148. package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
  149. package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
  150. package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
  151. package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
  152. package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
  153. package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
  154. package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
  155. package/dist/plan-schema-DU9exq6C.js.map +0 -1
  156. package/dist/policy-drift-B-J2hhm0.js.map +0 -1
  157. package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
  158. package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
  159. package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
  160. package/dist/source-Br7L7GOI.js +0 -410
  161. package/dist/source-Br7L7GOI.js.map +0 -1
  162. package/dist/utils/sql-redaction.d.ts +0 -21
package/dist/cli.js CHANGED
@@ -1,20 +1,41 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
3
  import { a as __toESM } from "./__vite-browser-external-BnuHet1e.js";
4
- import { a as formatForeignGeneratedColumnBanner, c as reportCommandFailure, i as diagnoseDbError, r as diagnoseAtlasFailure, t as checkDatabaseConnectivity } from "./cli-errors-DsA-K9uP.js";
5
- import { n as outError, r as outWarn, t as out } from "./cli-output-CNdMql-L.js";
6
- import { A as resolveLocalBin, B as forLibpq, C as loadCollectionsForCli, D as readSearchDdl, E as queryGeneratedColumnDependencies, F as decidePushSafety, I as detectDestructiveStatements, L as extractPlanStatements, M as GENERATED_SQL_DIR, N as generatedSqlDir, O as readTriggersDdl, P as prepareGeneratedSqlDir, R as fromAlterTable, S as getVectorExcludes, T as queryColumnTypes, _ as getDevDatabaseUrl, a as ExcludeIntrospectionError, b as getTableExcludes, c as applyVectorDdl, d as describeReinstallCommand, f as detectProjectPackageManager, g as ensureDevDatabaseExists, h as dropGeneratedColumns, j as DESIRED_STATE_URL, k as readVectorDdl, l as describeBuildScriptRemedy, m as dropDevDatabase, o as applySearchDdl, p as diagnoseMissingBin, r as dropLegacyAuthSchema, s as applyTriggersDdl, t as RLS_BOOTSTRAP_SQL, u as describeDevAddCommand, v as getForeignIndexExcludes, w as promptConfirm, x as getTriggerExcludes, y as getSearchExcludes, z as execa } from "./rls-bootstrap-sql-_KNnjanK.js";
7
- import { t as chalk } from "./source-Br7L7GOI.js";
4
+ import { A as queryColumnTypes, B as prepareGeneratedSqlDir, C as getForeignIndexExcludes, D as getVectorExcludes, E as getTriggerExcludes, F as resolveLocalBin, G as isSyncedPlan, H as detectDestructiveStatements, I as withAppClient, L as DESIRED_STATE_URL, M as readSearchDdl, N as readTriggersDdl, O as loadCollectionsForCli, P as readVectorDdl, R as GENERATED_SQL_DIR, S as ensureDevDatabaseExists, T as getTableExcludes, U as extractPlanStatements, V as decidePushSafety, W as fromAlterTable, _ as describeReinstallCommand, a as formatForeignGeneratedColumnBanner, b as dropDevDatabase, c as reportCommandFailure, d as applyTriggersDdl, f as applyVectorDdl, g as describeDevAddCommand, h as describeBuildScriptRemedy, i as diagnoseDbError, j as queryGeneratedColumnDependencies, k as promptConfirm, l as ExcludeIntrospectionError, m as closeAppClients, p as atlasDevDatabaseUrl, r as diagnoseAtlasFailure, t as checkDatabaseConnectivity, u as applySearchDdl, v as detectProjectPackageManager, w as getSearchExcludes, x as dropGeneratedColumns, y as diagnoseMissingBin, z as generatedSqlDir } from "./cli-errors-CaI4ntyD.js";
5
+ import { a as outWarn, i as outError, r as out, t as chalk } from "./source-DBDbBTL4.js";
6
+ import { o as execa, r as dropLegacyAuthSchema, s as forLibpq, t as RLS_BOOTSTRAP_SQL } from "./rls-bootstrap-sql-RQUljnJ1.js";
8
7
  import { t as require_arg } from "./arg-Dni7MzLB.js";
9
- import { n as collectionsPathIn, r as parseDriverLine, t as assertKnownFlags } from "./cli-flags-BglvpjHv.js";
10
- import { logger } from "@rebasepro/server";
8
+ import { n as collectionsPathIn, r as parseDriverLine, t as assertKnownFlags } from "./cli-flags-B-Dawocl.js";
11
9
  import { formatRelativeTime } from "@rebasepro/utils";
12
- import fs from "fs";
13
10
  import path from "path";
14
- import { fileURLToPath } from "url";
15
- //#region src/schema/generated-column-conflicts.ts
11
+ import fs from "fs";
12
+ import { fileURLToPath, pathToFileURL } from "url";
13
+ import { createRequire } from "module";
14
+ //#region src/tsx-loader.ts
16
15
  var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
17
16
  /**
17
+ * tsx's loader, as the URL `node --import` takes, or null when none resolves.
18
+ *
19
+ * For running one of this driver's scripts in one Node process. The `tsx`
20
+ * binary is a relay — pnpm's `.bin/tsx` shell shim, then tsx's CLI in one Node,
21
+ * which spawns a second Node with this same loader to run the script — so
22
+ * spawning through it costs a shell and a Node start (~50–100 ms) per child.
23
+ *
24
+ * Resolved the way Node resolves the package: from the project the driver
25
+ * runs in first (its cwd is the backend, where a scaffold declares tsx), then
26
+ * from this package. A URL rather than a path: a Windows path is not a valid
27
+ * `--import` specifier.
28
+ */
29
+ function resolveTsxLoader() {
30
+ const bases = [path.join(process.cwd(), "noop.js"), import.meta.url];
31
+ for (const base of bases) try {
32
+ return pathToFileURL(createRequire(base).resolve("tsx")).href;
33
+ } catch {}
34
+ return null;
35
+ }
36
+ //#endregion
37
+ //#region src/schema/generated-column-conflicts.ts
38
+ /**
18
39
  * Which columns an Atlas plan cannot touch, because a generated column reads
19
40
  * them.
20
41
  *
@@ -533,6 +554,60 @@ function tidy(sql) {
533
554
  return trimmed.length === 0 ? "" : `${trimmed}\n`;
534
555
  }
535
556
  //#endregion
557
+ //#region src/schema/migration-post-process.ts
558
+ /**
559
+ * Run the steps that finish a migration Atlas just wrote — the search, vector
560
+ * and trigger DDL, the RLS bootstrap and policies, the re-hash — and take the
561
+ * migration back out when any of them fails.
562
+ *
563
+ * Atlas's own file creates the tables and nothing else: no `ENABLE ROW LEVEL
564
+ * SECURITY`, no policy. Those arrive in the append. A failure here used to be a
565
+ * yellow warning followed by "You can now run rebase db migrate", and when it
566
+ * struck before the file was rewritten the checksum still matched — so the
567
+ * migration applied cleanly and created every new table with RLS off while
568
+ * the schema's default privileges handed `rebase_user` full DML on it. On a
569
+ * deployment that runs migrations and never provisions at boot
570
+ * (`REBASE_MIGRATE_ON_BOOT=none`), nothing closed it again.
571
+ *
572
+ * Deleting the file is safe because it is the one this run created: running
573
+ * `db generate` again, once the cause is fixed, writes the same diff.
574
+ */
575
+ async function finishNewMigration(file, steps, rehash) {
576
+ try {
577
+ await steps();
578
+ return { ok: true };
579
+ } catch (err) {
580
+ const error = err instanceof Error ? err.message : String(err);
581
+ let removed = false;
582
+ try {
583
+ if (fs.existsSync(file)) fs.unlinkSync(file);
584
+ removed = !fs.existsSync(file);
585
+ } catch {
586
+ removed = false;
587
+ }
588
+ if (!removed) return {
589
+ ok: false,
590
+ error,
591
+ removed
592
+ };
593
+ try {
594
+ await rehash();
595
+ return {
596
+ ok: false,
597
+ error,
598
+ removed
599
+ };
600
+ } catch (hashErr) {
601
+ return {
602
+ ok: false,
603
+ error,
604
+ removed,
605
+ rehashError: hashErr instanceof Error ? hashErr.message : String(hashErr)
606
+ };
607
+ }
608
+ }
609
+ }
610
+ //#endregion
536
611
  //#region src/schema/atlas-argv.ts
537
612
  /**
538
613
  * Does this Atlas invocation accept `--exclude`?
@@ -627,6 +702,90 @@ var ScratchDatabase = class {
627
702
  }
628
703
  };
629
704
  //#endregion
705
+ //#region src/cli-push.ts
706
+ /**
707
+ * Apply a planned push.
708
+ *
709
+ * A dry-run that answered "Schema is synced" skips the second Atlas run: that
710
+ * run would plan the same diff again (≈0.9 s at 111 tables, 5.5 s at 411 —
711
+ * Atlas's cost grows faster than the table count) and then apply nothing. The
712
+ * steps after it still run, because Atlas cannot see what they own. A plan with
713
+ * anything in it keeps the old flow, including Atlas re-planning at apply time.
714
+ *
715
+ * @returns `"synced"` when Atlas was not run again, `"applied"` otherwise.
716
+ */
717
+ async function applyPushPlan(plan, steps) {
718
+ const synced = isSyncedPlan(plan.dryRun) && plan.rebuildable === 0;
719
+ if (synced) steps.reportSynced();
720
+ else {
721
+ if (plan.rebuildable > 0) await steps.dropRebuildable();
722
+ try {
723
+ await steps.atlasApply();
724
+ } catch (err) {
725
+ if (plan.rebuildable > 0) await steps.rebuildAfterFailure();
726
+ throw err;
727
+ }
728
+ }
729
+ await steps.afterApply();
730
+ return synced ? "synced" : "applied";
731
+ }
732
+ /**
733
+ * The tables whose stamp `db push` may write: those every one of whose planned
734
+ * statements `policies.sql` carries, verbatim.
735
+ *
736
+ * A stamp says "this stored definition is what the plan's statement made". It
737
+ * is only true of a policy the statement *did* make — and since both are
738
+ * rendered by the same function from the same collections, a table whose
739
+ * statements are all in the file is one whose policies the file just created.
740
+ * Anything else (a hand-edited file, a file generated elsewhere) is not
741
+ * stamped, and the next boot re-applies that table, as before.
742
+ */
743
+ function stampableTables(plans, policiesSql) {
744
+ return plans.filter((plan) => plan.policyStatements.length > 0 && plan.policyStatements.every((statement) => policiesSql.includes(statement)));
745
+ }
746
+ /**
747
+ * Run `policies.sql`, then stamp the policies it created the way a boot does,
748
+ * in the same transaction.
749
+ *
750
+ * Before stamps the push's policies carried none, so the first boot after
751
+ * every push re-applied them all — a DROP and a CREATE per policy, an ACCESS
752
+ * EXCLUSIVE lock per table. Stamped here, that boot finds them current.
753
+ *
754
+ * The file runs in one transaction, as it did as one implicit one: a policy
755
+ * that fails leaves the previous ones in place. The stamps run after it, under
756
+ * a savepoint: a stamp that cannot be written costs the next boot a re-apply,
757
+ * which is what it did before, so it is reported and the policies are kept.
758
+ * Nothing is left set on the connection: `SET LOCAL` ends with the transaction.
759
+ */
760
+ async function applyPoliciesFile(client, policiesSql, stamping) {
761
+ await client.query("BEGIN");
762
+ let stamped = 0;
763
+ let stampError;
764
+ try {
765
+ await client.query(policiesSql);
766
+ if (stamping.statements.length > 0) {
767
+ await client.query("SAVEPOINT rebase_policy_stamps");
768
+ try {
769
+ if (stamping.searchPath) await client.query(`SET LOCAL search_path TO ${stamping.searchPath}`);
770
+ await client.query(stamping.statements.join("\n"));
771
+ await client.query("RELEASE SAVEPOINT rebase_policy_stamps");
772
+ stamped = stamping.statements.length;
773
+ } catch (err) {
774
+ await client.query("ROLLBACK TO SAVEPOINT rebase_policy_stamps");
775
+ stampError = err instanceof Error ? err.message : String(err);
776
+ }
777
+ }
778
+ await client.query("COMMIT");
779
+ } catch (err) {
780
+ await client.query("ROLLBACK").catch(() => void 0);
781
+ throw err;
782
+ }
783
+ return {
784
+ stamped,
785
+ stampError
786
+ };
787
+ }
788
+ //#endregion
630
789
  //#region src/branch-argv.ts
631
790
  /**
632
791
  * Argument shapes for `rebase db branch`, with no dependencies.
@@ -841,6 +1000,11 @@ var __cliDirname = path.dirname(fileURLToPath(import.meta.url));
841
1000
  * tsx maps it back. Running the built `.js` under plain `node` therefore fails
842
1001
  * on the user's code rather than on ours, one directory further from anything
843
1002
  * the error mentions.
1003
+ *
1004
+ * tsx's loader in one Node (`node --import`) when it resolves, rather than the
1005
+ * `tsx` binary, which is a shell shim and a relay Node in front of that same
1006
+ * loader (see `tsx-loader.ts`). The binary stays the fallback — a global tsx.
1007
+ * `args` is everything after `bin`, the script included.
844
1008
  */
845
1009
  function resolveChildScript(name) {
846
1010
  const built = path.join(__cliDirname, "schema", `${name}.js`);
@@ -850,6 +1014,16 @@ function resolveChildScript(name) {
850
1014
  ok: false,
851
1015
  reason: "script"
852
1016
  };
1017
+ const tsxLoader = resolveTsxLoader();
1018
+ if (tsxLoader) return {
1019
+ ok: true,
1020
+ bin: process.execPath,
1021
+ args: [
1022
+ "--import",
1023
+ tsxLoader,
1024
+ script
1025
+ ]
1026
+ };
853
1027
  const tsxBin = resolveLocalBin("tsx");
854
1028
  if (!tsxBin) return {
855
1029
  ok: false,
@@ -858,7 +1032,7 @@ function resolveChildScript(name) {
858
1032
  return {
859
1033
  ok: true,
860
1034
  bin: tsxBin,
861
- script
1035
+ args: [script]
862
1036
  };
863
1037
  }
864
1038
  /** What to print when {@link resolveChildScript} cannot produce a command. */
@@ -866,6 +1040,40 @@ function childScriptError(what, reason) {
866
1040
  if (reason === "tsx") return `✗ Could not find the tsx binary, which ${what} needs to read your collection files. Install your project's dependencies and try again.`;
867
1041
  return `✗ The driver's ${what} is missing. This copy of @rebasepro/server-postgres is incomplete — reinstall it, or run its build if you are working in the monorepo.`;
868
1042
  }
1043
+ /**
1044
+ * Whether this process can run the generators itself — that is, whether it
1045
+ * runs under tsx, the interpreter the project's collection files need (see
1046
+ * {@link resolveChildScript}). `rebase` always starts the driver that way when
1047
+ * tsx is installed, which a scaffold guarantees; a driver started with plain
1048
+ * `node` keeps the child process, which brings its own tsx.
1049
+ */
1050
+ function generatesInProcess() {
1051
+ return [...process.execArgv, process.env.NODE_OPTIONS ?? ""].some((flag) => /(?:^|[\s\\/=])tsx(?:$|[\s\\/@])/.test(flag));
1052
+ }
1053
+ /**
1054
+ * Run one of the generators in this process — see `schema/generate-files.ts`
1055
+ * for why a one-shot generation no longer gets a process of its own.
1056
+ *
1057
+ * The messages and the exit code are the child's: a generator that failed has
1058
+ * printed its own error, and this adds the one-line verdict; one that could not
1059
+ * even be loaded is reported the way a child that never started was.
1060
+ */
1061
+ async function generateInProcess(what, label, run) {
1062
+ let generators;
1063
+ try {
1064
+ generators = await import("./generate-files-CdEckX6c.js").then((n) => n.t);
1065
+ } catch (err) {
1066
+ outError(chalk.red(`✗ Failed to run ${what}: ${err instanceof Error ? err.message : String(err)}`));
1067
+ process.exit(1);
1068
+ }
1069
+ try {
1070
+ await run(generators);
1071
+ } catch (err) {
1072
+ if (err instanceof generators.GenerationFailed) outError(chalk.red(`✗ ${label} generation failed — nothing was applied.`));
1073
+ else outError(chalk.red(`✗ Failed to run ${what}: ${err instanceof Error ? err.message : String(err)}`));
1074
+ process.exit(1);
1075
+ }
1076
+ }
869
1077
  async function loadEnv() {
870
1078
  try {
871
1079
  const dotenv = await import("dotenv");
@@ -894,12 +1102,16 @@ async function runPluginCommand(args) {
894
1102
  const subcommand = args[1];
895
1103
  assertKnownFlags(domain, subcommand, args);
896
1104
  assertCollectionsPathExists(collectionsPathIn(args));
897
- if (domain === "db") await scratchDatabase.around(() => dbCommand(subcommand, args));
898
- else if (domain === "schema") await schemaCommand(subcommand, args);
899
- else if (domain === "doctor") await doctorPluginCommand(args);
900
- else {
901
- outError(chalk.red(`Unknown domain command: ${domain}`));
902
- process.exit(1);
1105
+ try {
1106
+ if (domain === "db") await scratchDatabase.around(() => dbCommand(subcommand, args));
1107
+ else if (domain === "schema") await schemaCommand(subcommand, args);
1108
+ else if (domain === "doctor") await doctorPluginCommand(args);
1109
+ else {
1110
+ outError(chalk.red(`Unknown domain command: ${domain}`));
1111
+ process.exit(1);
1112
+ }
1113
+ } finally {
1114
+ await closeAppClients();
903
1115
  }
904
1116
  }
905
1117
  /** The migration files on disk, sorted, or none if the directory is absent. */
@@ -972,6 +1184,48 @@ async function stripCarvedOutFromNewestMigration(collectionsPath) {
972
1184
  return !result.empty;
973
1185
  }
974
1186
  /**
1187
+ * One line per destructive statement, as `db push` prints them — shared by
1188
+ * push, `db generate` and `db migrate --refuse-destructive`, so the three
1189
+ * describe the same statement the same way.
1190
+ */
1191
+ function destructiveLines(destructive) {
1192
+ return destructive.map((d) => chalk.red(` ${d.kind}${d.detail ? ` (${d.detail})` : ""}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1193
+ }
1194
+ /**
1195
+ * The column types `detectDestructiveStatements` reads a type change against,
1196
+ * or none when the database cannot be asked — every type change then counts
1197
+ * as destructive, which is the safe side of a report.
1198
+ */
1199
+ async function columnTypesOrNone(databaseUrl) {
1200
+ if (!databaseUrl) return [];
1201
+ try {
1202
+ return await queryColumnTypes(databaseUrl);
1203
+ } catch {
1204
+ return [];
1205
+ }
1206
+ }
1207
+ /**
1208
+ * Say what in the migration `db generate` just wrote destroys data.
1209
+ *
1210
+ * `db push` stops before a destructive change; `db generate` wrote the same
1211
+ * `DROP COLUMN` into a file without a word, and `db migrate` applied it. Only
1212
+ * the part Atlas planned is read: what is appended after it (the search
1213
+ * rebuild, policies, triggers) is Rebase's own and puts back what it drops.
1214
+ * A report, not a refusal — the exit code is unchanged.
1215
+ */
1216
+ async function reportDestructiveMigration(migrationSql, migrationFile, databaseUrl) {
1217
+ const destructive = detectDestructiveStatements(migrationSql, await columnTypesOrNone(databaseUrl));
1218
+ if (destructive.length === 0) return;
1219
+ outWarn("");
1220
+ outWarn(chalk.yellow(` ⚠️ ${migrationFile} includes ${destructive.length} destructive change(s) that will DESTROY data when applied:`));
1221
+ outWarn("");
1222
+ for (const line of destructiveLines(destructive)) outWarn(line);
1223
+ outWarn("");
1224
+ outWarn(chalk.yellow(" Review it before `rebase db migrate`. `rebase db migrate --refuse-destructive` will not apply it."));
1225
+ outWarn(chalk.yellow(" Back up first: rebase db backup"));
1226
+ outWarn("");
1227
+ }
1228
+ /**
975
1229
  * The flags `rebase db` reads for the subcommands it parses itself. Hoisted
976
1230
  * and named `…FLAGS` so the documentation verifier, which reads specs out of
977
1231
  * this source, sees them.
@@ -979,6 +1233,7 @@ async function stripCarvedOutFromNewestMigration(collectionsPath) {
979
1233
  var DB_FLAGS = {
980
1234
  "--collections": String,
981
1235
  "--allow-destructive": Boolean,
1236
+ "--refuse-destructive": Boolean,
982
1237
  "--dry-run": Boolean,
983
1238
  "--yes": Boolean,
984
1239
  "--baseline": String,
@@ -1011,7 +1266,7 @@ async function dbCommand(subcommand, rawArgs) {
1011
1266
  return;
1012
1267
  }
1013
1268
  if (subcommand === "backup" || subcommand === "restore" || subcommand === "backups") {
1014
- const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-DW5p9_zv.js");
1269
+ const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-aUfRv16h.js");
1015
1270
  if (subcommand === "backup" && backupActionOf(rawArgs) === "list") await backupsCommand(rawArgs);
1016
1271
  else if (subcommand === "backup") await backupCommand(rawArgs);
1017
1272
  else if (subcommand === "restore") await restoreCommand(rawArgs);
@@ -1025,7 +1280,7 @@ async function dbCommand(subcommand, rawArgs) {
1025
1280
  out(chalk.bold(" 📦 Rebase DB Generate"));
1026
1281
  out(chalk.gray(" Step 1/2: Generating Drizzle schema & Postgres DDL from collections..."));
1027
1282
  out("");
1028
- await schemaCommand("generate", rawArgs);
1283
+ await schemaCommand("generate", rawArgs, { nextStep: false });
1029
1284
  await generatePostgresDdlCommand(rawArgs);
1030
1285
  out("");
1031
1286
  out(chalk.gray(" Step 2/2: Generating SQL migration files with Atlas..."));
@@ -1042,54 +1297,65 @@ async function dbCommand(subcommand, rawArgs) {
1042
1297
  ], collectionsPath);
1043
1298
  let wroteNewMigration = listMigrationFiles().length > migrationsBefore.length;
1044
1299
  if (wroteNewMigration) wroteNewMigration = await stripCarvedOutFromNewestMigration(collectionsPath);
1045
- try {
1046
- const migrationsDir = path.resolve(process.cwd(), "drizzle", "migrations");
1047
- if (fs.existsSync(migrationsDir) && wroteNewMigration) {
1048
- const sqlFiles = fs.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort();
1049
- if (sqlFiles.length > 0) {
1050
- const newestMigrationFile = path.join(migrationsDir, sqlFiles[sqlFiles.length - 1]);
1051
- let migrationContent = fs.readFileSync(newestMigrationFile, "utf-8");
1052
- migrationContent = migrationContent.replace(/CREATE SCHEMA (?!IF NOT EXISTS)("[^"]+");/g, "CREATE SCHEMA IF NOT EXISTS $1;");
1053
- const vectorContent = readVectorDdl();
1054
- if (vectorContent) {
1055
- migrationContent = `${migrationContent}\n\n${vectorContent}`;
1056
- out(chalk.gray(" ✓ Appended vector DDL to the migration"));
1057
- }
1058
- const searchContent = readSearchDdl();
1059
- if (searchContent) {
1060
- const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-BJsLaVNX.js").then((n) => n.o);
1061
- const migrationConflicts = findGeneratedColumnConflicts(parseColumnMutations(migrationContent), searchColumnDependencies(await loadCollectionsForCli(collectionsPath)));
1062
- const preamble = migrationDropPreamble(migrationConflicts);
1063
- if (preamble) {
1064
- migrationContent = `${preamble}\n${migrationContent}`;
1065
- for (const conflict of migrationConflicts) out(chalk.gray(` ✓ ${describeConflict(conflict)} is dropped and rebuilt inside the migration`));
1066
- }
1067
- migrationContent = `${migrationContent}\n\n${searchContent}`;
1068
- out(chalk.gray(" ✓ Appended search DDL to the migration"));
1069
- }
1070
- const triggersContent = readTriggersDdl();
1071
- if (triggersContent) {
1072
- migrationContent = `${migrationContent}\n\n${triggersContent}`;
1073
- out(chalk.gray(" ✓ Appended `updated_at` triggers to the migration"));
1074
- }
1075
- fs.writeFileSync(newestMigrationFile, migrationContent, "utf-8");
1076
- const policiesFile = path.join(generatedSqlDir(), "policies.sql");
1077
- if (fs.existsSync(policiesFile)) {
1078
- const policiesContent = fs.readFileSync(policiesFile, "utf-8");
1079
- fs.appendFileSync(newestMigrationFile, "\n\n" + RLS_BOOTSTRAP_SQL + "\n" + policiesContent);
1080
- out(chalk.gray(` ✓ Appended RLS policies to migration file: ${path.basename(newestMigrationFile)}`));
1081
- out(chalk.gray(" Re-hashing migration files..."));
1082
- await runAtlas("migrate", [
1083
- "hash",
1084
- "--dir",
1085
- "file://drizzle/migrations"
1086
- ], collectionsPath);
1087
- out(chalk.gray(" ✓ Migration directory checksum updated successfully."));
1300
+ const newestWritten = wroteNewMigration ? listMigrationFiles().at(-1) : void 0;
1301
+ if (newestWritten) await reportDestructiveMigration(fs.readFileSync(path.resolve(process.cwd(), "drizzle", "migrations", newestWritten), "utf-8"), newestWritten, process.env.DATABASE_URL);
1302
+ const migrationsDir = path.resolve(process.cwd(), "drizzle", "migrations");
1303
+ const newestSql = fs.existsSync(migrationsDir) && wroteNewMigration ? fs.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort().at(-1) : void 0;
1304
+ if (newestSql) {
1305
+ const newestMigrationFile = path.join(migrationsDir, newestSql);
1306
+ const finished = await finishNewMigration(newestMigrationFile, async () => {
1307
+ let migrationContent = fs.readFileSync(newestMigrationFile, "utf-8");
1308
+ migrationContent = migrationContent.replace(/CREATE SCHEMA (?!IF NOT EXISTS)("[^"]+");/g, "CREATE SCHEMA IF NOT EXISTS $1;");
1309
+ const vectorContent = readVectorDdl();
1310
+ if (vectorContent) {
1311
+ migrationContent = `${migrationContent}\n\n${vectorContent}`;
1312
+ out(chalk.gray(" ✓ Appended vector DDL to the migration"));
1313
+ }
1314
+ const searchContent = readSearchDdl();
1315
+ if (searchContent) {
1316
+ const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-B64O2oZF.js").then((n) => n.o);
1317
+ const migrationConflicts = findGeneratedColumnConflicts(parseColumnMutations(migrationContent), searchColumnDependencies(await loadCollectionsForCli(collectionsPath)));
1318
+ const preamble = migrationDropPreamble(migrationConflicts);
1319
+ if (preamble) {
1320
+ migrationContent = `${preamble}\n${migrationContent}`;
1321
+ for (const conflict of migrationConflicts) out(chalk.gray(` ✓ ${describeConflict(conflict)} is dropped and rebuilt inside the migration`));
1088
1322
  }
1323
+ migrationContent = `${migrationContent}\n\n${searchContent}`;
1324
+ out(chalk.gray(" ✓ Appended search DDL to the migration"));
1325
+ }
1326
+ const triggersContent = readTriggersDdl();
1327
+ if (triggersContent) {
1328
+ migrationContent = `${migrationContent}\n\n${triggersContent}`;
1329
+ out(chalk.gray(" ✓ Appended `updated_at` triggers to the migration"));
1089
1330
  }
1331
+ fs.writeFileSync(newestMigrationFile, migrationContent, "utf-8");
1332
+ const policiesFile = path.join(generatedSqlDir(), "policies.sql");
1333
+ if (fs.existsSync(policiesFile)) {
1334
+ const policiesContent = fs.readFileSync(policiesFile, "utf-8");
1335
+ fs.appendFileSync(newestMigrationFile, "\n\n" + RLS_BOOTSTRAP_SQL + "\n" + policiesContent);
1336
+ out(chalk.gray(` ✓ Appended RLS policies to migration file: ${path.basename(newestMigrationFile)}`));
1337
+ out(chalk.gray(" Re-hashing migration files..."));
1338
+ await runAtlas("migrate", [
1339
+ "hash",
1340
+ "--dir",
1341
+ "file://drizzle/migrations"
1342
+ ], collectionsPath, { throwOnFailure: true });
1343
+ out(chalk.gray(" ✓ Migration directory checksum updated successfully."));
1344
+ }
1345
+ }, async () => {
1346
+ await runAtlas("migrate", [
1347
+ "hash",
1348
+ "--dir",
1349
+ "file://drizzle/migrations"
1350
+ ], collectionsPath, { throwOnFailure: true });
1351
+ });
1352
+ if (!finished.ok) {
1353
+ outError(chalk.red(`\n ✗ Could not finish ${newestSql}: ${finished.error}`));
1354
+ outError(chalk.gray(" Atlas's half of a migration creates the tables; the part that failed adds their\n row-level security, policies, search, vector and trigger DDL. Applied alone, it\n would serve the new tables with no row filtering."));
1355
+ outError(chalk.gray(finished.removed ? ` ${newestSql} was removed. Fix the cause and run \`rebase db generate\` again.` : ` ${newestSql} could not be removed — delete it by hand before \`rebase db migrate\`.`));
1356
+ if (finished.rehashError) outError(chalk.gray(` Re-hashing failed too (${finished.rehashError}); run \`atlas migrate hash\` or generate again.`));
1357
+ process.exit(1);
1090
1358
  }
1091
- } catch (err) {
1092
- outWarn(chalk.yellow(` ⚠️ Failed to append policies or re-hash migration: ${err instanceof Error ? err.message : String(err)}`));
1093
1359
  }
1094
1360
  if (!wroteNewMigration) out(chalk.gray(` ℹ No migration written — nothing in this change is visible to Atlas.
1095
1361
  Search, vector, trigger and RLS DDL are applied by \`rebase db push\`, and at
@@ -1103,16 +1369,19 @@ async function dbCommand(subcommand, rawArgs) {
1103
1369
  out(chalk.bold(` 🗄️ Rebase DB ${subcommand.charAt(0).toUpperCase() + subcommand.slice(1)}`));
1104
1370
  out("");
1105
1371
  if (subcommand === "push") {
1106
- out(chalk.gray(" Step 1/3: Generating Drizzle schema & Postgres DDL from collections..."));
1372
+ const dryRun = argsList["--dry-run"] === true;
1373
+ out(chalk.gray(dryRun ? " Step 1/3: Generating the Postgres DDL from collections (the plan's input)..." : " Step 1/3: Generating Drizzle schema & Postgres DDL from collections..."));
1107
1374
  out("");
1108
- await schemaCommand("generate", rawArgs);
1109
- await generatePostgresDdlCommand(rawArgs);
1375
+ if (dryRun) out(chalk.gray(" --dry-run: src/schema.generated.ts is left as it is."));
1376
+ else await schemaCommand("generate", rawArgs, { nextStep: false });
1377
+ await generatePostgresDdlCommand(rawArgs, { retireLegacyCopies: !dryRun });
1110
1378
  out("");
1111
- const dryRun = argsList["--dry-run"] === true;
1112
1379
  out(chalk.gray(dryRun ? " Step 2/3: Planning the change against the database (nothing is applied)..." : " Step 2/3: Pushing schema to database with Atlas..."));
1113
1380
  out("");
1114
1381
  const databaseUrl = process.env.DATABASE_URL;
1115
1382
  if (databaseUrl && !dryRun) await ensureAuthSchemaAndFunctions(databaseUrl);
1383
+ if (databaseUrl) await renameLegacyForeignKeys(databaseUrl, collectionsPath, dryRun);
1384
+ const prebuilt = databaseUrl ? await prebuildForeignKeyIndexes(databaseUrl, collectionsPath, dryRun) : [];
1116
1385
  const plan = await runAtlas("schema", [
1117
1386
  "apply",
1118
1387
  "--to",
@@ -1124,6 +1393,7 @@ async function dbCommand(subcommand, rawArgs) {
1124
1393
  const planStatements = extractPlanStatements(plan);
1125
1394
  const planListing = unreadable ? plan.trim() : planStatements.map((statement) => `${statement};`).join("\n");
1126
1395
  const { managed: rebuildable, foreign: unowned } = partitionByOwnership(databaseUrl ? findGeneratedColumnConflicts(parseColumnMutations(plan), await queryGeneratedColumnDependencies(databaseUrl)) : [], collectionsPath ? await getSearchExcludes(collectionsPath) : []);
1396
+ if (databaseUrl) await reportKeptEnumLabels(databaseUrl, collectionsPath);
1127
1397
  if (dryRun) {
1128
1398
  out(chalk.gray(" Step 3/3: --dry-run — nothing was applied."));
1129
1399
  out("");
@@ -1132,6 +1402,13 @@ async function dbCommand(subcommand, rawArgs) {
1132
1402
  out(chalk.gray(planListing.split("\n").map((l) => ` ${l}`).join("\n")));
1133
1403
  } else out(chalk.green(" ✓ No changes: the database already matches these collections."));
1134
1404
  out("");
1405
+ if (prebuilt.length > 0) {
1406
+ out(chalk.gray(` ${prebuilt.length} foreign-key index(es) on existing tables would be built first,`));
1407
+ out(chalk.gray(" CONCURRENTLY, so the apply would not build them under a write lock (the plan"));
1408
+ out(chalk.gray(" above lists them because they do not exist yet):"));
1409
+ for (const name of prebuilt) out(chalk.gray(` ${name}`));
1410
+ out("");
1411
+ }
1135
1412
  if (rebuildable.length > 0) {
1136
1413
  out(chalk.gray(` ${rebuildable.length} generated column(s) would be dropped before the apply and`));
1137
1414
  out(chalk.gray(" rebuilt from search.sql after it — Postgres cannot retype a column"));
@@ -1142,7 +1419,7 @@ async function dbCommand(subcommand, rawArgs) {
1142
1419
  if (unowned.length > 0) outWarn(formatForeignGeneratedColumnBanner(unowned));
1143
1420
  if (destructive.length > 0) {
1144
1421
  outWarn(chalk.yellow(` ⚠️ ${destructive.length} of those DESTROY data:`));
1145
- for (const d of destructive) outWarn(chalk.red(` ${d.kind}${d.detail ? ` (${d.detail})` : ""}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1422
+ for (const line of destructiveLines(destructive)) outWarn(line);
1146
1423
  outWarn("");
1147
1424
  outWarn(chalk.yellow(" Applying them needs `rebase db push --allow-destructive`. Back up first: rebase db backup"));
1148
1425
  outWarn("");
@@ -1164,7 +1441,7 @@ async function dbCommand(subcommand, rawArgs) {
1164
1441
  if (destructive.length > 0) {
1165
1442
  outWarn(chalk.yellow(` ⚠️ This push includes ${destructive.length} destructive change(s) that will DESTROY data:`));
1166
1443
  outWarn("");
1167
- for (const d of destructive) outWarn(chalk.red(` ${d.kind}${d.detail ? ` (${d.detail})` : ""}: `) + chalk.gray(d.statement.replace(/\s+/g, " ")));
1444
+ for (const line of destructiveLines(destructive)) outWarn(line);
1168
1445
  outWarn("");
1169
1446
  outWarn(chalk.yellow(" Full planned changes:"));
1170
1447
  outWarn(chalk.gray(planListing.split("\n").map((l) => ` ${l}`).join("\n")));
@@ -1181,40 +1458,65 @@ async function dbCommand(subcommand, rawArgs) {
1181
1458
  }
1182
1459
  } else outWarn(chalk.yellow(" Proceeding because --allow-destructive was passed."));
1183
1460
  }
1184
- if (rebuildable.length > 0 && databaseUrl) {
1185
- for (const conflict of rebuildable) out(chalk.gray(` Dropping ${describeConflict(conflict)} — it reads a column this push retypes.`));
1186
- await dropGeneratedColumns(databaseUrl, dropGeneratedColumnStatements(rebuildable));
1187
- }
1188
- try {
1189
- await runAtlas("schema", [
1190
- "apply",
1191
- "--to",
1192
- DESIRED_STATE_URL,
1193
- "--auto-approve"
1194
- ], collectionsPath, { throwOnFailure: true });
1195
- } catch (err) {
1196
- if (rebuildable.length > 0 && databaseUrl) {
1461
+ await applyPushPlan({
1462
+ dryRun: plan,
1463
+ rebuildable: databaseUrl ? rebuildable.length : 0
1464
+ }, {
1465
+ dropRebuildable: async () => {
1466
+ if (!databaseUrl) return;
1467
+ for (const conflict of rebuildable) out(chalk.gray(` Dropping ${describeConflict(conflict)} — it reads a column this push retypes.`));
1468
+ await dropGeneratedColumns(databaseUrl, dropGeneratedColumnStatements(rebuildable));
1469
+ },
1470
+ atlasApply: async () => {
1471
+ await runAtlas("schema", [
1472
+ "apply",
1473
+ "--to",
1474
+ DESIRED_STATE_URL,
1475
+ "--auto-approve"
1476
+ ], collectionsPath, { throwOnFailure: true });
1477
+ },
1478
+ reportSynced: () => out("Schema is synced, no changes to be made"),
1479
+ rebuildAfterFailure: async () => {
1480
+ if (!databaseUrl) return;
1197
1481
  out(chalk.gray(" The apply failed — rebuilding the generated columns it needed out of the way."));
1198
1482
  await applySearchDdl(databaseUrl);
1483
+ },
1484
+ afterApply: async () => {
1485
+ out("");
1486
+ if (!databaseUrl) {
1487
+ outWarn(chalk.yellow(" ⚠️ DATABASE_URL not found in environment, skipping RLS policies application."));
1488
+ return;
1489
+ }
1490
+ await lockManagedTables(databaseUrl, collectionsPath);
1491
+ await ensureAuthTables(databaseUrl, collectionsPath);
1492
+ await applyVectorDdl(databaseUrl);
1493
+ await applySearchDdl(databaseUrl);
1494
+ await applyTriggersDdl(databaseUrl);
1495
+ await applyPolicies(databaseUrl, collectionsPath);
1496
+ await reconcilePolicies(databaseUrl, collectionsPath);
1497
+ await ensureRlsUserRole(databaseUrl);
1498
+ await retireLegacyAuthSchema(databaseUrl);
1199
1499
  }
1200
- throw err;
1201
- }
1202
- out("");
1203
- if (databaseUrl) {
1204
- await lockManagedTables(databaseUrl, collectionsPath);
1205
- await ensureAuthTables(databaseUrl, collectionsPath);
1206
- await applyVectorDdl(databaseUrl);
1207
- await applySearchDdl(databaseUrl);
1208
- await applyTriggersDdl(databaseUrl);
1209
- await applyPolicies(databaseUrl);
1210
- await reconcilePolicies(databaseUrl, collectionsPath);
1211
- await ensureRlsUserRole(databaseUrl);
1212
- await retireLegacyAuthSchema(databaseUrl);
1213
- } else outWarn(chalk.yellow(" ⚠️ DATABASE_URL not found in environment, skipping RLS policies application."));
1500
+ });
1214
1501
  } else if (subcommand === "migrate") {
1215
1502
  const databaseUrl = process.env.DATABASE_URL;
1216
- if (databaseUrl) await ensureAuthSchemaAndFunctions(databaseUrl);
1217
1503
  const baseline = argsList["--baseline"];
1504
+ if (argsList["--refuse-destructive"] === true) {
1505
+ const plan = await runAtlas("migrate", [...migrateApplyArgs(argsList._, baseline), "--dry-run"], collectionsPath, { captureStdout: true });
1506
+ const destructive = detectDestructiveStatements(plan, await columnTypesOrNone(databaseUrl));
1507
+ if (destructive.length > 0) {
1508
+ outError(chalk.red(` ✗ Refusing to migrate: the pending migrations include ${destructive.length} destructive change(s):`));
1509
+ outError("");
1510
+ for (const line of destructiveLines(destructive)) outError(line);
1511
+ outError("");
1512
+ outError(chalk.gray(" Nothing was applied. Run `rebase db migrate` without --refuse-destructive to apply them."));
1513
+ outError(chalk.gray(" Back up first: rebase db backup"));
1514
+ process.exit(1);
1515
+ }
1516
+ out(chalk.gray(` --refuse-destructive: ${extractPlanStatements(plan).length} pending statement(s), none destructive.`));
1517
+ out("");
1518
+ }
1519
+ if (databaseUrl) await ensureAuthSchemaAndFunctions(databaseUrl);
1218
1520
  if (baseline) {
1219
1521
  out(chalk.gray(` Recording ${baseline} as already applied, then migrating from the next one.`));
1220
1522
  out("");
@@ -1230,16 +1532,79 @@ async function dbCommand(subcommand, rawArgs) {
1230
1532
  out("");
1231
1533
  }
1232
1534
  }
1535
+ /**
1536
+ * Build, CONCURRENTLY and before Atlas plans, the derived foreign-key indexes
1537
+ * an existing table is missing — see `schema/push-index-prebuild.ts`. With
1538
+ * `dryRun`, only find them.
1539
+ *
1540
+ * Never fatal: an index not built here is one Atlas builds inside the push, as
1541
+ * it always did.
1542
+ *
1543
+ * @returns `schema.index` of each one missing when this started.
1544
+ */
1545
+ /** Renames relation columns that carry their pre-0.13 derived name, as boot does; a dry-run only names them. */
1546
+ async function renameLegacyForeignKeys(databaseUrl, collectionsPath, dryRun) {
1547
+ let renames;
1548
+ try {
1549
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1550
+ const { legacyForeignKeyRenames } = await import("./push-legacy-renames-BK6ilQq2.js");
1551
+ const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1552
+ renames = await withAppClient(databaseUrl, (client) => legacyForeignKeyRenames({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1553
+ } catch {
1554
+ return;
1555
+ }
1556
+ if (renames.length === 0) return;
1557
+ out(chalk.gray(dryRun ? ` ${renames.length} relation column(s) carry the name Rebase derived before 0.13; a push renames them first,` : ` Renaming ${renames.length} relation column(s) that carry the name Rebase derived before 0.13 (data, indexes and`));
1558
+ out(chalk.gray(dryRun ? " keeping their data (the plan below still shows them as a drop and an add, because nothing ran):" : " constraints are kept — the same rename `rebase dev` runs):"));
1559
+ for (const rename of renames) out(chalk.gray(` ${rename.table}: "${rename.legacy}" → "${rename.expected}"`));
1560
+ if (!dryRun) await withAppClient(databaseUrl, async (client) => {
1561
+ for (const rename of renames) await client.query(rename.sql);
1562
+ });
1563
+ out(chalk.gray(" To keep an old name instead, name the column on the relation (`localKey`, or `through` on a many-to-many)."));
1564
+ out("");
1565
+ }
1566
+ /** Names the enum labels the database keeps after the collections dropped them. See `schema/enum-labels-kept.ts`. */
1567
+ async function reportKeptEnumLabels(databaseUrl, collectionsPath) {
1568
+ try {
1569
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1570
+ const { keptEnumLabels, formatKeptEnumLabelsNotice } = await import("./enum-labels-kept-D8fI1MoX.js");
1571
+ const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1572
+ const kept = await withAppClient(databaseUrl, (client) => keptEnumLabels({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1573
+ if (kept.length > 0) outWarn(chalk.yellow(formatKeptEnumLabelsNotice(kept)));
1574
+ } catch {}
1575
+ }
1576
+ async function prebuildForeignKeyIndexes(databaseUrl, collectionsPath, dryRun) {
1577
+ let missing;
1578
+ try {
1579
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1580
+ const { missingForeignKeyIndexes } = await import("./push-index-prebuild-Db4AQF4i.js");
1581
+ const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1582
+ missing = await withAppClient(databaseUrl, (client) => missingForeignKeyIndexes({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1583
+ } catch {
1584
+ return [];
1585
+ }
1586
+ const names = missing.map((index) => `${index.schema}.${index.indexName}`);
1587
+ if (missing.length === 0 || dryRun) return names;
1588
+ out(chalk.gray(` Building ${missing.length} foreign-key index(es) on existing tables first — CONCURRENTLY, so writes go on:`));
1589
+ try {
1590
+ const { buildDeferredIndexes } = await import("./deferred-index-build-DP1_SNFo.js").then((n) => n.r);
1591
+ const outcome = await withAppClient(databaseUrl, (client) => buildDeferredIndexes(async () => ({
1592
+ dedicated: true,
1593
+ query: async (text) => ({ rows: (await client.query(text)).rows }),
1594
+ release: () => void 0
1595
+ }), missing, { log: (message) => out(chalk.gray(` ${message}`)) }));
1596
+ for (const failure of outcome.failed) outWarn(chalk.yellow(` ⚠️ ${failure.index} was not built ahead (${failure.error}); the push builds it inside its transaction instead.`));
1597
+ } catch (err) {
1598
+ outWarn(chalk.yellow(` ⚠️ The foreign-key indexes were not built ahead (${err instanceof Error ? err.message : String(err)}); the push builds them inside its transaction instead.`));
1599
+ }
1600
+ out("");
1601
+ return names;
1602
+ }
1233
1603
  async function ensureAuthSchemaAndFunctions(databaseUrl) {
1234
1604
  try {
1235
- const { Client } = await import("pg");
1236
- const client = new Client({ connectionString: databaseUrl });
1237
- await client.connect();
1238
- try {
1605
+ await withAppClient(databaseUrl, async (client) => {
1239
1606
  await client.query(RLS_BOOTSTRAP_SQL);
1240
- } finally {
1241
- await client.end();
1242
- }
1607
+ });
1243
1608
  } catch (err) {
1244
1609
  outWarn(chalk.yellow(` ⚠️ Failed to bootstrap the RLS helper functions: ${err instanceof Error ? err.message : String(err)}`));
1245
1610
  }
@@ -1266,18 +1631,11 @@ async function lockManagedTables(databaseUrl, collectionsPath) {
1266
1631
  let unsecured;
1267
1632
  let userRole;
1268
1633
  try {
1269
- const { lockCollectionTables } = await import("./ensure-collection-policies-B1ureSIV.js");
1270
- const { loadCollections } = await import("./doctor-C-sYWbmt.js").then((n) => n.t);
1271
- userRole = (await import("./rls-enforcement-CfXOJJaW.js").then((n) => n.o)).REBASE_USER_ROLE;
1634
+ const { lockCollectionTables } = await import("./ensure-collection-policies-CXpbeNag.js");
1635
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1636
+ userRole = (await import("./rls-enforcement-DvzqlaL1.js").then((n) => n.c)).REBASE_USER_ROLE;
1272
1637
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1273
- const { Client } = await import("pg");
1274
- const client = new Client({ connectionString: databaseUrl });
1275
- await client.connect();
1276
- try {
1277
- ({unsecured} = await lockCollectionTables({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1278
- } finally {
1279
- await client.end();
1280
- }
1638
+ ({unsecured} = await withAppClient(databaseUrl, (client) => lockCollectionTables({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections)));
1281
1639
  } catch (err) {
1282
1640
  outError(chalk.red(`\n ✗ The schema was applied, but row-level security could not be switched on for its tables: ${err instanceof Error ? err.message : String(err)}`));
1283
1641
  outError(chalk.gray(" A table this push created may be readable and writable by every authenticated request\n until it is. Re-run `rebase db push` — it locks every collection table before anything else."));
@@ -1310,20 +1668,13 @@ async function lockManagedTables(databaseUrl, collectionsPath) {
1310
1668
  async function ensureAuthTables(databaseUrl, collectionsPath) {
1311
1669
  try {
1312
1670
  const { drizzle } = await import("drizzle-orm/node-postgres");
1313
- const { ensureAuthTablesExist } = await import("./ensure-tables-BmI_tRxc.js").then((n) => n.n);
1314
- const { loadCollections } = await import("./doctor-C-sYWbmt.js").then((n) => n.t);
1315
- const { Client } = await import("pg");
1671
+ const { ensureAuthTablesExist } = await import("./ensure-tables-CHsgyLqg.js").then((n) => n.n);
1672
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1316
1673
  const authCollection = (await loadCollections(path.resolve(process.cwd(), collectionsPath))).find((c) => {
1317
1674
  const a = c.auth;
1318
1675
  return a === true || typeof a === "object" && a !== null && a.enabled === true;
1319
1676
  });
1320
- const client = new Client({ connectionString: databaseUrl });
1321
- await client.connect();
1322
- try {
1323
- await ensureAuthTablesExist(drizzle(client), authCollection);
1324
- } finally {
1325
- await client.end();
1326
- }
1677
+ await withAppClient(databaseUrl, (client) => ensureAuthTablesExist(drizzle(client), authCollection));
1327
1678
  } catch (err) {
1328
1679
  outWarn(chalk.yellow(` ⚠️ Failed to ensure framework auth tables: ${err instanceof Error ? err.message : String(err)}`));
1329
1680
  }
@@ -1338,24 +1689,19 @@ async function ensureRlsUserRole(databaseUrl) {
1338
1689
  const schemas = ["public", "rebase"];
1339
1690
  let rls;
1340
1691
  try {
1341
- rls = await import("./rls-enforcement-CfXOJJaW.js").then((n) => n.o);
1692
+ rls = await import("./rls-enforcement-DvzqlaL1.js").then((n) => n.c);
1342
1693
  } catch (err) {
1343
1694
  outError(chalk.red(`\n ✗ Could not load the RLS provisioning module: ${err instanceof Error ? err.message : String(err)}`));
1344
1695
  return;
1345
1696
  }
1346
1697
  try {
1347
- const { Client } = await import("pg");
1348
- const client = new Client({ connectionString: databaseUrl });
1349
- await client.connect();
1350
- try {
1698
+ await withAppClient(databaseUrl, async (client) => {
1351
1699
  const runSql = async (text) => (await client.query(text)).rows;
1352
1700
  if ((await rls.detectConnectionPosture(runSql)).privileged) {
1353
1701
  await rls.ensureAppRole(runSql, schemas);
1354
1702
  out(chalk.gray(` ✓ RLS role "${rls.REBASE_USER_ROLE}" provisioned/refreshed.`));
1355
1703
  }
1356
- } finally {
1357
- await client.end();
1358
- }
1704
+ });
1359
1705
  } catch (err) {
1360
1706
  outError(chalk.red(`\n ✗ The schema change was applied, but the "${rls.REBASE_USER_ROLE}" role could not be provisioned: ${err instanceof Error ? err.message : String(err)}`));
1361
1707
  outError(chalk.gray(rls.appRoleSetupInstructions("your database user", schemas)));
@@ -1372,35 +1718,23 @@ async function ensureRlsUserRole(databaseUrl) {
1372
1718
  */
1373
1719
  async function retireLegacyAuthSchema(databaseUrl) {
1374
1720
  try {
1375
- const { Client } = await import("pg");
1376
- const client = new Client({ connectionString: databaseUrl });
1377
- await client.connect();
1378
- try {
1379
- await dropLegacyAuthSchema(async (text) => (await client.query(text)).rows, {
1380
- info: (m) => out(chalk.gray(` ${m}`)),
1381
- warn: (m) => outWarn(chalk.yellow(` ⚠️ ${m}`))
1382
- });
1383
- } finally {
1384
- await client.end();
1385
- }
1721
+ await withAppClient(databaseUrl, (client) => dropLegacyAuthSchema(async (text) => (await client.query(text)).rows, {
1722
+ info: (m) => out(chalk.gray(` ${m}`)),
1723
+ warn: (m) => outWarn(chalk.yellow(` ⚠️ ${m}`))
1724
+ }));
1386
1725
  } catch {}
1387
1726
  }
1388
- async function applyPolicies(databaseUrl) {
1727
+ async function applyPolicies(databaseUrl, collectionsPath) {
1389
1728
  try {
1390
1729
  const policiesPath = path.join(generatedSqlDir(), "policies.sql");
1391
1730
  if (!fs.existsSync(policiesPath)) return;
1392
1731
  out(chalk.gray(" Step 3/3: Applying RLS policies to database..."));
1393
1732
  out("");
1394
1733
  const policiesContent = fs.readFileSync(policiesPath, "utf-8");
1395
- const { Client } = await import("pg");
1396
- const client = new Client({ connectionString: databaseUrl });
1397
- await client.connect();
1398
- try {
1399
- await client.query(policiesContent);
1400
- out(chalk.green(" ✓ RLS policies applied successfully."));
1401
- } finally {
1402
- await client.end();
1403
- }
1734
+ const stamping = await policyStamping(databaseUrl, collectionsPath, policiesContent);
1735
+ const { stampError } = await withAppClient(databaseUrl, (client) => applyPoliciesFile(client, policiesContent, stamping));
1736
+ out(chalk.green(" ✓ RLS policies applied successfully."));
1737
+ if (stampError) outWarn(chalk.yellow(` ⚠️ The policies could not be stamped (${stampError}); the next boot applies them again.`));
1404
1738
  } catch (err) {
1405
1739
  const hint = diagnoseDbError(err, databaseUrl);
1406
1740
  if (hint) outError(hint);
@@ -1409,6 +1743,25 @@ async function applyPolicies(databaseUrl) {
1409
1743
  }
1410
1744
  }
1411
1745
  /**
1746
+ * The stamps a boot looks for, for the policies `policies.sql` is about to
1747
+ * create — see `applyPoliciesFile`. None when the collections cannot be read
1748
+ * here, which costs only the shortcut: the next boot re-applies, as before.
1749
+ */
1750
+ async function policyStamping(databaseUrl, collectionsPath, policiesSql) {
1751
+ try {
1752
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1753
+ const { planCollectionPolicies } = await import("./generate-postgres-ddl-logic-B64O2oZF.js").then((n) => n.o);
1754
+ const { policyStampSql } = await import("./ensure-collection-policies-CXpbeNag.js");
1755
+ const { pinSearchPath } = await import("./connection-BPoNILnD.js").then((n) => n.n);
1756
+ return {
1757
+ statements: stampableTables(planCollectionPolicies(await loadCollections(path.resolve(process.cwd(), collectionsPath))), policiesSql).map((plan) => policyStampSql(plan)).filter((statement) => statement !== void 0),
1758
+ searchPath: pinSearchPath(databaseUrl) === databaseUrl ? void 0 : "public"
1759
+ };
1760
+ } catch {
1761
+ return { statements: [] };
1762
+ }
1763
+ }
1764
+ /**
1412
1765
  * Remove the policies an earlier push superseded but never dropped.
1413
1766
  *
1414
1767
  * `policies.sql` only DROPs the names it is about to CREATE, and a rule's
@@ -1423,13 +1776,10 @@ async function applyPolicies(databaseUrl) {
1423
1776
  */
1424
1777
  async function reconcilePolicies(databaseUrl, collectionsPath) {
1425
1778
  try {
1426
- const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-B-J2hhm0.js").then((n) => n.a);
1427
- const { loadCollections } = await import("./doctor-C-sYWbmt.js").then((n) => n.t);
1779
+ const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-Dm8i-s1J.js").then((n) => n.a);
1780
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1428
1781
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1429
- const { Client } = await import("pg");
1430
- const client = new Client({ connectionString: databaseUrl });
1431
- await client.connect();
1432
- try {
1782
+ await withAppClient(databaseUrl, async (client) => {
1433
1783
  const drift = await checkPolicyDrift(client, collections);
1434
1784
  const { dropped, kept } = await dropOrphanedPolicies(client, drift, collections);
1435
1785
  for (const p of dropped) out(chalk.gray(` ✓ Dropped superseded policy "${p.name}" on ${p.schema}.${p.table}`));
@@ -1450,9 +1800,7 @@ async function reconcilePolicies(databaseUrl, collectionsPath) {
1450
1800
  orphaned: []
1451
1801
  }));
1452
1802
  }
1453
- } finally {
1454
- await client.end();
1455
- }
1803
+ });
1456
1804
  } catch (err) {
1457
1805
  outWarn(chalk.yellow(` ⚠️ Could not reconcile RLS policies: ${err instanceof Error ? err.message : String(err)}`));
1458
1806
  }
@@ -1505,8 +1853,8 @@ async function branchCommand(rawArgs) {
1505
1853
  outError(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
1506
1854
  process.exit(1);
1507
1855
  }
1508
- const { DatabasePoolManager } = await import("./databasePoolManager-Bj5FbeAs.js").then((n) => n.n);
1509
- const { BranchService } = await import("./BranchService-BRt78gfa.js").then((n) => n.n);
1856
+ const { DatabasePoolManager } = await import("./databasePoolManager-BkEy2w18.js").then((n) => n.n);
1857
+ const { BranchService } = await import("./BranchService-Bsn2wfaj.js").then((n) => n.n);
1510
1858
  const { drizzle } = await import("drizzle-orm/node-postgres");
1511
1859
  const { Pool } = await import("pg");
1512
1860
  const pool = new Pool({
@@ -1795,8 +2143,15 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
1795
2143
  process.exit(1);
1796
2144
  }
1797
2145
  await checkDatabaseConnectivity(databaseUrl);
1798
- const devDatabaseUrl = getDevDatabaseUrl(databaseUrl);
1799
- if (takesDevUrl(domain, args)) await scratchDatabase.ensure(databaseUrl, devDatabaseUrl);
2146
+ let devDatabase;
2147
+ try {
2148
+ devDatabase = atlasDevDatabaseUrl(databaseUrl, env);
2149
+ } catch (err) {
2150
+ outError(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
2151
+ process.exit(1);
2152
+ }
2153
+ const devDatabaseUrl = devDatabase.url;
2154
+ if (takesDevUrl(domain, args) && !devDatabase.named) await scratchDatabase.ensure(databaseUrl, devDatabaseUrl);
1800
2155
  const atlasUrl = forLibpq(databaseUrl);
1801
2156
  const atlasDevUrl = forLibpq(devDatabaseUrl);
1802
2157
  const excludes = [];
@@ -1871,7 +2226,7 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
1871
2226
  if (opts.captureStdout) return stdoutText.trim().length > 0 ? stdoutText : stderrText;
1872
2227
  return "";
1873
2228
  }
1874
- async function generatePostgresDdlCommand(rawArgs) {
2229
+ async function generatePostgresDdlCommand(rawArgs, options = {}) {
1875
2230
  const argsList = (0, import_arg.default)({
1876
2231
  "--collections": String,
1877
2232
  "--output": String,
@@ -1881,20 +2236,31 @@ async function generatePostgresDdlCommand(rawArgs) {
1881
2236
  argv: rawArgs.slice(2),
1882
2237
  permissive: true
1883
2238
  });
1884
- const ddl = resolveChildScript("generate-postgres-ddl");
1885
- if (!ddl.ok) {
2239
+ const ddl = generatesInProcess() ? void 0 : resolveChildScript("generate-postgres-ddl");
2240
+ if (ddl && !ddl.ok) {
1886
2241
  outError(chalk.red(childScriptError("DDL generator", ddl.reason)));
1887
2242
  process.exit(1);
1888
2243
  }
1889
2244
  const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
1890
2245
  const outputPath = argsList["--output"] || path.join(".rebase/sql", "schema.sql");
1891
2246
  if (!argsList["--output"]) {
1892
- const retired = prepareGeneratedSqlDir();
1893
- if (retired.length > 0) out(chalk.gray(` Generated SQL is written to ${GENERATED_SQL_DIR}/ now, which is not committed. Removed the old copies — commit the deletion:\n ${retired.join(", ")}`));
2247
+ const retire = options.retireLegacyCopies !== false;
2248
+ const retired = prepareGeneratedSqlDir(process.cwd(), { retire });
2249
+ if (retired.length > 0 && !retire) out(chalk.gray(` The next \`rebase db push\` or \`rebase db generate\` removes the old copies an earlier release committed:\n ${retired.join(", ")}`));
2250
+ else if (retired.length > 0) out(chalk.gray(` Generated SQL is written to ${GENERATED_SQL_DIR}/ now, which is not committed. Removed the old copies — commit the deletion:\n ${retired.join(", ")}`));
2251
+ }
2252
+ if (!ddl) {
2253
+ await generateInProcess("Postgres DDL generator", "Postgres DDL", async ({ writePostgresDdl }) => {
2254
+ await writePostgresDdl({
2255
+ collectionsPath: path.resolve(process.cwd(), collectionsPath),
2256
+ outputPath: path.resolve(process.cwd(), outputPath)
2257
+ });
2258
+ });
2259
+ return;
1894
2260
  }
1895
2261
  const cmdParts = [
1896
2262
  ddl.bin,
1897
- ddl.script,
2263
+ ...ddl.args,
1898
2264
  `--collections=${collectionsPath}`,
1899
2265
  `--output=${outputPath}`
1900
2266
  ];
@@ -1943,9 +2309,9 @@ async function schemaStaleCommand(rawArgs) {
1943
2309
  const schemaFile = path.resolve(process.cwd(), outputPath);
1944
2310
  const fix = Boolean(argsList["--fix"]);
1945
2311
  const generatedExists = fs.existsSync(schemaFile);
1946
- const { loadCollections } = await import("./doctor-C-sYWbmt.js").then((n) => n.t);
2312
+ const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1947
2313
  const { findLegacyForeignKeyNames, compareGeneratedDeclarations, staleVerdict } = await import("./generated-schema-staleness-Di9c4ZxN.js").then((n) => n.r);
1948
- const { generateSchema } = await import("./generate-drizzle-schema-logic-sSFDp6LR.js").then((n) => n.n);
2314
+ const { generateSchema } = await import("./generate-drizzle-schema-logic-B74WN37S.js").then((n) => n.n);
1949
2315
  const { relationalCollections } = await import("@rebasepro/common");
1950
2316
  let stale = [];
1951
2317
  let differences = [];
@@ -1958,6 +2324,7 @@ async function schemaStaleCommand(rawArgs) {
1958
2324
  if (postgresCollections.length > 0) differences = compareGeneratedDeclarations(generateSchema(postgresCollections), onDisk);
1959
2325
  } catch (err) {
1960
2326
  unreadable = err instanceof Error ? err.message : String(err);
2327
+ const { logger } = await import("@rebasepro/server");
1961
2328
  logger.debug(`schema stale: skipped (${unreadable})`);
1962
2329
  }
1963
2330
  const verdict = staleVerdict({
@@ -1982,7 +2349,7 @@ async function schemaStaleCommand(rawArgs) {
1982
2349
  `--output=${outputPath}`
1983
2350
  ]);
1984
2351
  }
1985
- async function schemaCommand(subcommand, rawArgs) {
2352
+ async function schemaCommand(subcommand, rawArgs, options = {}) {
1986
2353
  if (subcommand && (rawArgs.includes("--help") || rawArgs.includes("-h"))) {
1987
2354
  out("");
1988
2355
  out(chalk.bold(` rebase schema ${subcommand}`));
@@ -2007,24 +2374,38 @@ async function schemaCommand(subcommand, rawArgs) {
2007
2374
  argv: rawArgs.slice(2),
2008
2375
  permissive: true
2009
2376
  });
2377
+ const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
2378
+ const outputPath = argsList["--output"] || path.join("src", "schema.generated.ts");
2379
+ const watch = argsList["--watch"] || false;
2380
+ if (!watch && generatesInProcess()) {
2381
+ out("");
2382
+ out(chalk.bold(" 🔧 Rebase Schema Generator"));
2383
+ out("");
2384
+ await generateInProcess("schema generator", "Drizzle schema", async ({ writeDrizzleSchema }) => {
2385
+ await writeDrizzleSchema({
2386
+ collectionsPath: path.resolve(process.cwd(), collectionsPath),
2387
+ outputPath: path.resolve(process.cwd(), outputPath),
2388
+ nextStep: options.nextStep
2389
+ });
2390
+ });
2391
+ return;
2392
+ }
2010
2393
  const generator = resolveChildScript("generate-drizzle-schema");
2011
2394
  if (!generator.ok) {
2012
2395
  outError(chalk.red(childScriptError("schema generator", generator.reason)));
2013
2396
  process.exit(1);
2014
2397
  }
2015
- const collectionsPath = argsList["--collections"] || path.join("..", "config", "collections");
2016
- const outputPath = argsList["--output"] || path.join("src", "schema.generated.ts");
2017
- const watch = argsList["--watch"] || false;
2018
2398
  out("");
2019
2399
  out(chalk.bold(" 🔧 Rebase Schema Generator"));
2020
2400
  out("");
2021
2401
  const cmdParts = [
2022
2402
  generator.bin,
2023
- generator.script,
2403
+ ...generator.args,
2024
2404
  `--collections=${collectionsPath}`,
2025
2405
  `--output=${outputPath}`
2026
2406
  ];
2027
2407
  if (watch) cmdParts.push("--watch");
2408
+ if (options.nextStep === false) cmdParts.push("--no-next-step");
2028
2409
  try {
2029
2410
  await execa(cmdParts[0], cmdParts.slice(1), {
2030
2411
  cwd: process.cwd(),
@@ -2061,7 +2442,7 @@ async function schemaCommand(subcommand, rawArgs) {
2061
2442
  out("");
2062
2443
  const cmdParts = [
2063
2444
  introspect.bin,
2064
- introspect.script,
2445
+ ...introspect.args,
2065
2446
  `--output=${outputPath}`,
2066
2447
  ...argsList["--force"] ? ["--force"] : [],
2067
2448
  ...argsList["--schema"] ? [`--schema=${argsList["--schema"]}`] : []
@@ -2104,7 +2485,7 @@ async function doctorPluginCommand(rawArgs) {
2104
2485
  const sdkPath = parsedArgs["--sdk"] || path.join("..", "generated", "sdk", "database.types.ts");
2105
2486
  const cmdParts = [
2106
2487
  doctor.bin,
2107
- doctor.script,
2488
+ ...doctor.args,
2108
2489
  `--collections=${collectionsPath}`,
2109
2490
  `--schema=${schemaPath}`,
2110
2491
  `--sdk=${sdkPath}`,