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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/README.md +16 -0
  2. package/dist/{BranchService-DGPL6G_C.js → BranchService-UgKa416x.js} +47 -19
  3. package/dist/BranchService-UgKa416x.js.map +1 -0
  4. package/dist/PostgresBackendDriver.d.ts +187 -13
  5. package/dist/PostgresBootstrapper.d.ts +101 -2
  6. package/dist/auth/ensure-tables.d.ts +12 -0
  7. package/dist/auth/services.d.ts +78 -14
  8. package/dist/{auth-users-columns-D2LBFrMH.js → auth-users-columns-C72EMoDJ.js} +17 -1
  9. package/dist/{auth-users-columns-D2LBFrMH.js.map → auth-users-columns-C72EMoDJ.js.map} +1 -1
  10. package/dist/backup/backup-cli.d.ts +75 -2
  11. package/dist/backup/backup-service.d.ts +2 -2
  12. package/dist/backup/pg-binary.d.ts +5 -0
  13. package/dist/{backup-cli-24v4OlSp.js → backup-cli-yiKd1GsC.js} +168 -75
  14. package/dist/backup-cli-yiKd1GsC.js.map +1 -0
  15. package/dist/{backup-service-HQ9GC4tN.js → backup-service-AZWc2wvI.js} +104 -35
  16. package/dist/backup-service-AZWc2wvI.js.map +1 -0
  17. package/dist/boot-ddl-B9ZmBJ75.js +105 -0
  18. package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
  19. package/dist/cli-console-DGee3Zl7.js +225 -0
  20. package/dist/cli-console-DGee3Zl7.js.map +1 -0
  21. package/dist/cli-console.d.ts +39 -0
  22. package/dist/cli-errors-oVUuD8k0.js +2180 -0
  23. package/dist/cli-errors-oVUuD8k0.js.map +1 -0
  24. package/dist/cli-errors.d.ts +133 -9
  25. package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
  26. package/dist/cli-flags-DnPHXjRH.js.map +1 -0
  27. package/dist/cli-flags.d.ts +15 -0
  28. package/dist/cli-helpers.d.ts +88 -34
  29. package/dist/cli-output.d.ts +16 -28
  30. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  31. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  32. package/dist/cli-prompt.d.ts +19 -0
  33. package/dist/cli-push.d.ts +87 -0
  34. package/dist/cli-scratch-database.d.ts +33 -8
  35. package/dist/cli.js +826 -230
  36. package/dist/cli.js.map +1 -1
  37. package/dist/{column-plan-helpers-DF-8dTVa.js → column-plan-helpers-D_V4oaZh.js} +43 -27
  38. package/dist/column-plan-helpers-D_V4oaZh.js.map +1 -0
  39. package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
  40. package/dist/connection-C938nR3E.js.map +1 -0
  41. package/dist/connection.d.ts +13 -2
  42. package/dist/data-transformer.d.ts +0 -8
  43. package/dist/databasePoolManager-DZgnBOgj.js +119 -0
  44. package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
  45. package/dist/deferred-index-build-BqmEHGaD.js +1051 -0
  46. package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
  47. package/dist/{doctor-Cb2thZ8s.js → doctor-CUAAOFZm.js} +281 -89
  48. package/dist/doctor-CUAAOFZm.js.map +1 -0
  49. package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
  50. package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
  51. package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
  52. package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
  53. package/dist/{ensure-tables-Dhn9KM3B.js → ensure-tables-DMPWhJ6R.js} +180 -77
  54. package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
  55. package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
  56. package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
  57. package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
  58. package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
  59. package/dist/{generate-drizzle-schema-logic-BfzK7UQd.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +29 -11
  60. package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
  61. package/dist/generate-files-OoRJLEdk.js +199 -0
  62. package/dist/generate-files-OoRJLEdk.js.map +1 -0
  63. package/dist/generate-postgres-ddl-logic-B4k9djAV.js +152 -0
  64. package/dist/generate-postgres-ddl-logic-B4k9djAV.js.map +1 -0
  65. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
  66. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
  67. package/dist/generated-sql.d.ts +34 -0
  68. package/dist/history/HistoryService.d.ts +64 -5
  69. package/dist/history/ensure-history-table.d.ts +63 -4
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.es.js +139 -16738
  72. package/dist/index.es.js.map +1 -1
  73. package/dist/{introspect-db-logic-WMuAfxvw.js → introspect-db-logic-BCc1Jym2.js} +654 -353
  74. package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
  75. package/dist/introspect-db-queries-BhhJg0b8.js +364 -0
  76. package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
  77. package/dist/open-rebase-CAjWpPjs.js +20895 -0
  78. package/dist/open-rebase-CAjWpPjs.js.map +1 -0
  79. package/dist/open-rebase.d.ts +94 -0
  80. package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
  81. package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
  82. package/dist/{plan-schema-C0fxM8dY.js → plan-schema-CsNgK0qi.js} +279 -534
  83. package/dist/plan-schema-CsNgK0qi.js.map +1 -0
  84. package/dist/{policy-drift-DljYdrpW.js → policy-drift-BKC50y2p.js} +33 -9
  85. package/dist/policy-drift-BKC50y2p.js.map +1 -0
  86. package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
  87. package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
  88. package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
  89. package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
  90. package/dist/{generate-postgres-ddl-logic-Bt2d2mRH.js → render-ddl-CrfEe__B.js} +24 -157
  91. package/dist/render-ddl-CrfEe__B.js.map +1 -0
  92. package/dist/{rls-bootstrap-sql-B8EclDyM.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1101
  93. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  94. package/dist/{rls-enforcement-C6Xk0lA6.js → rls-enforcement-D7P4-ZYX.js} +138 -18
  95. package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
  96. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  97. package/dist/schema/atlas-exclude.d.ts +42 -0
  98. package/dist/schema/auth-schema.d.ts +172 -2
  99. package/dist/schema/boot-ddl.d.ts +47 -0
  100. package/dist/schema/carved-out-migration.d.ts +5 -4
  101. package/dist/schema/classify-change.d.ts +29 -1
  102. package/dist/schema/collection-index.d.ts +45 -0
  103. package/dist/schema/column-plan-helpers.d.ts +38 -21
  104. package/dist/schema/deferred-index-build.d.ts +131 -0
  105. package/dist/schema/destructive-sql.d.ts +50 -2
  106. package/dist/schema/doctor-cli.js +110 -6
  107. package/dist/schema/doctor-cli.js.map +1 -1
  108. package/dist/schema/doctor-search-scale.d.ts +45 -0
  109. package/dist/schema/doctor.d.ts +53 -1
  110. package/dist/schema/ensure-collection-policies.d.ts +95 -35
  111. package/dist/schema/ensure-collection-tables.d.ts +56 -3
  112. package/dist/schema/enum-labels-kept.d.ts +26 -0
  113. package/dist/schema/generate-drizzle-schema.js +1 -1
  114. package/dist/schema/generate-files.d.ts +44 -0
  115. package/dist/schema/generate-next-step.d.ts +0 -13
  116. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -5
  117. package/dist/schema/generate-postgres-ddl.js +10 -81
  118. package/dist/schema/generate-postgres-ddl.js.map +1 -1
  119. package/dist/schema/generate-schema-commit.d.ts +12 -0
  120. package/dist/schema/generated-schema-diff.d.ts +7 -0
  121. package/dist/schema/introspect-db-logic.d.ts +63 -0
  122. package/dist/schema/introspect-db-queries.d.ts +21 -2
  123. package/dist/schema/introspect-db-search.d.ts +22 -0
  124. package/dist/schema/introspect-db-storage.d.ts +83 -0
  125. package/dist/schema/introspect-db-structure.d.ts +10 -0
  126. package/dist/schema/introspect-db.js +29 -21
  127. package/dist/schema/introspect-db.js.map +1 -1
  128. package/dist/schema/migrate-baseline.d.ts +33 -0
  129. package/dist/schema/migration-post-process.d.ts +34 -0
  130. package/dist/schema/plan/diff-plan.d.ts +127 -1
  131. package/dist/schema/plan/plan-schema.d.ts +16 -8
  132. package/dist/schema/plan/render-ddl.d.ts +8 -1
  133. package/dist/schema/plan/types.d.ts +45 -11
  134. package/dist/schema/push-index-prebuild.d.ts +25 -0
  135. package/dist/schema/push-legacy-renames.d.ts +20 -0
  136. package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
  137. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  138. package/dist/search-column-BM-GV6vH.js +442 -0
  139. package/dist/search-column-BM-GV6vH.js.map +1 -0
  140. package/dist/security/policy-drift.d.ts +1 -1
  141. package/dist/security/rls-enforcement.d.ts +39 -4
  142. package/dist/services/BranchService.d.ts +29 -1
  143. package/dist/services/FetchService.d.ts +63 -12
  144. package/dist/services/PersistService.d.ts +15 -0
  145. package/dist/services/RelationService.d.ts +106 -3
  146. package/dist/services/cdc/CdcListener.d.ts +26 -10
  147. package/dist/services/cdc/identity-columns.d.ts +19 -0
  148. package/dist/services/cdc/trigger-cdc.d.ts +180 -18
  149. package/dist/services/channel-bus/PostgresChannelBus.d.ts +12 -7
  150. package/dist/services/channel-bus/index.d.ts +1 -1
  151. package/dist/services/channel-history.d.ts +32 -5
  152. package/dist/services/collection-helpers.d.ts +26 -1
  153. package/dist/services/field-viewer.d.ts +15 -1
  154. package/dist/services/pg-notify-listener.d.ts +95 -6
  155. package/dist/services/realtimeService.d.ts +467 -53
  156. package/dist/services/row-pipeline.d.ts +16 -9
  157. package/dist/services/socket-liveness.d.ts +85 -0
  158. package/dist/services/soft-delete.d.ts +12 -0
  159. package/dist/services/sql-script.d.ts +275 -0
  160. package/dist/services/vector-iterative-scan.d.ts +40 -0
  161. package/dist/services/write-depth.d.ts +22 -0
  162. package/dist/services/write-transaction-scope.d.ts +15 -3
  163. package/dist/socket-opt-ins.d.ts +64 -0
  164. package/dist/source-DB6xJv3p.js +873 -0
  165. package/dist/source-DB6xJv3p.js.map +1 -0
  166. package/dist/tsx-loader.d.ts +14 -0
  167. package/dist/utils/connection-string.d.ts +31 -0
  168. package/dist/utils/drizzle-conditions.d.ts +63 -3
  169. package/dist/utils/error-cause.d.ts +7 -0
  170. package/dist/utils/pg-error-utils.d.ts +2 -4
  171. package/dist/utils/result-rows.d.ts +10 -0
  172. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  173. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  174. package/dist/websocket-DUiiwQd2.js +1632 -0
  175. package/dist/websocket-DUiiwQd2.js.map +1 -0
  176. package/dist/websocket.d.ts +63 -18
  177. package/package.json +7 -7
  178. package/dist/BranchService-DGPL6G_C.js.map +0 -1
  179. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  180. package/dist/arg-Dni7MzLB.js.map +0 -1
  181. package/dist/backup-cli-24v4OlSp.js.map +0 -1
  182. package/dist/backup-service-HQ9GC4tN.js.map +0 -1
  183. package/dist/cli-errors-B8qHg02P.js +0 -798
  184. package/dist/cli-errors-B8qHg02P.js.map +0 -1
  185. package/dist/cli-flags-BglvpjHv.js.map +0 -1
  186. package/dist/cli-output-CNdMql-L.js +0 -47
  187. package/dist/cli-output-CNdMql-L.js.map +0 -1
  188. package/dist/collection-index-DdnsxiJ_.js +0 -286
  189. package/dist/collection-index-DdnsxiJ_.js.map +0 -1
  190. package/dist/column-plan-helpers-DF-8dTVa.js.map +0 -1
  191. package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
  192. package/dist/doctor-Cb2thZ8s.js.map +0 -1
  193. package/dist/ensure-collection-policies-RHUcEp4v.js +0 -105
  194. package/dist/ensure-collection-policies-RHUcEp4v.js.map +0 -1
  195. package/dist/ensure-collection-tables-BEEjn5cn.js +0 -857
  196. package/dist/ensure-collection-tables-BEEjn5cn.js.map +0 -1
  197. package/dist/ensure-tables-Dhn9KM3B.js.map +0 -1
  198. package/dist/generate-drizzle-schema-logic-BfzK7UQd.js.map +0 -1
  199. package/dist/generate-drizzle-schema-yzY_BLhr.js +0 -123
  200. package/dist/generate-drizzle-schema-yzY_BLhr.js.map +0 -1
  201. package/dist/generate-postgres-ddl-logic-Bt2d2mRH.js.map +0 -1
  202. package/dist/introspect-db-logic-WMuAfxvw.js.map +0 -1
  203. package/dist/plan-schema-C0fxM8dY.js.map +0 -1
  204. package/dist/policy-drift-DljYdrpW.js.map +0 -1
  205. package/dist/rls-bootstrap-sql-B8EclDyM.js.map +0 -1
  206. package/dist/rls-enforcement-C6Xk0lA6.js.map +0 -1
  207. package/dist/source-Br7L7GOI.js +0 -410
  208. package/dist/source-Br7L7GOI.js.map +0 -1
@@ -1,86 +1,23 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { a as __toESM, i as __require, n as __commonJSMin, r as __exportAll, t as require___vite_browser_external } from "./__vite-browser-external-BnuHet1e.js";
4
- import { o as isRebaseIndexName } from "./collection-index-DdnsxiJ_.js";
5
- import { r as outWarn, t as out } from "./cli-output-CNdMql-L.js";
6
- import { t as chalk } from "./source-Br7L7GOI.js";
3
+ import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-V7rFbMRe.js";
7
4
  import { LEGACY_RLS_SCHEMA, REBASE_SCHEMA } from "@rebasepro/types";
8
5
  import { sqlRows } from "@rebasepro/common";
9
- import fs from "fs";
10
- import path from "path";
6
+ import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
7
+ import { constants } from "node:os";
8
+ import tty from "node:tty";
11
9
  import { fileURLToPath } from "node:url";
12
10
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
13
11
  import { StringDecoder } from "node:string_decoder";
14
12
  import { aborted, callbackify, debuglog, inspect, promisify, stripVTControlCharacters } from "node:util";
15
- import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
16
- import tty from "node:tty";
17
- import path$1 from "node:path";
18
- import { execSync } from "child_process";
13
+ import path from "node:path";
19
14
  import { scheduler, setImmediate, setTimeout } from "node:timers/promises";
20
- import { constants } from "node:os";
21
15
  import { EventEmitter, addAbortListener, on, once, setMaxListeners } from "node:events";
22
16
  import { serialize } from "node:v8";
23
17
  import { appendFileSync, createReadStream, createWriteStream, readFileSync, statSync, writeFileSync } from "node:fs";
24
18
  import { finished } from "node:stream/promises";
25
19
  import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
26
20
  import { Buffer as Buffer$1 } from "node:buffer";
27
- import { createRequire } from "module";
28
- import { fileURLToPath as fileURLToPath$1, pathToFileURL } from "url";
29
- //#region src/utils/connection-string.ts
30
- /**
31
- * Connection-string rewrites for the tools Rebase shells out to.
32
- *
33
- * Pure and dependency-free: `pg-tools` builds argument vectors without a live
34
- * server, and these have to be usable from there.
35
- */
36
- /**
37
- * The `sslmode` values libpq accepts. `no-verify` is conspicuously not one.
38
- */
39
- var LIBPQ_SSLMODES = /* @__PURE__ */ new Set([
40
- "disable",
41
- "allow",
42
- "prefer",
43
- "require",
44
- "verify-ca",
45
- "verify-full"
46
- ]);
47
- /**
48
- * Make a connection string safe to hand to a libpq program.
49
- *
50
- * `sslmode=no-verify` is a node-postgres convention — encrypt, but do not check
51
- * the certificate — and libpq does not have it. It does not degrade or warn:
52
- *
53
- * psql: error: invalid sslmode value: "no-verify"
54
- *
55
- * which means a `DATABASE_URL` that works perfectly for the app makes `psql`,
56
- * `pg_dump`, `pg_restore` and Atlas all refuse to start, with an error that
57
- * points at the value rather than at the convention it comes from. It has cost
58
- * this project time more than once.
59
- *
60
- * `require` is the honest translation: libpq's `require` encrypts and does not
61
- * verify the certificate either, which is exactly what `no-verify` asks for.
62
- * Nothing is relaxed by the rewrite — `verify-ca` and `verify-full` are left
63
- * alone, so a connection string that asked for verification still gets it.
64
- *
65
- * Only `sslmode` is touched, and only when it is a value libpq would reject.
66
- * Anything unparseable is returned as given: a connection that works
67
- * unverified beats one this corrupted.
68
- */
69
- function forLibpq(connectionString) {
70
- let url;
71
- try {
72
- url = new URL(connectionString);
73
- } catch {
74
- return connectionString;
75
- }
76
- if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") return connectionString;
77
- const sslmode = url.searchParams.get("sslmode");
78
- if (sslmode === null || LIBPQ_SSLMODES.has(sslmode)) return connectionString;
79
- url.searchParams.set("sslmode", "require");
80
- url.search = Array.from(url.searchParams.entries()).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
81
- return url.toString();
82
- }
83
- //#endregion
84
21
  //#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
85
22
  function isPlainObject(value) {
86
23
  if (typeof value !== "object" || value === null) return false;
@@ -851,7 +788,7 @@ var handleCommand = (filePath, rawArguments, rawOptions) => {
851
788
  var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
852
789
  module.exports = isexe;
853
790
  isexe.sync = sync;
854
- var fs$3 = __require("fs");
791
+ var fs$2 = __require("fs");
855
792
  function checkPathExt(path, options) {
856
793
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
857
794
  if (!pathext) return true;
@@ -868,12 +805,12 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
868
805
  return checkPathExt(path, options);
869
806
  }
870
807
  function isexe(path, options, cb) {
871
- fs$3.stat(path, function(er, stat) {
808
+ fs$2.stat(path, function(er, stat) {
872
809
  cb(er, er ? false : checkStat(stat, path, options));
873
810
  });
874
811
  }
875
812
  function sync(path, options) {
876
- return checkStat(fs$3.statSync(path), path, options);
813
+ return checkStat(fs$2.statSync(path), path, options);
877
814
  }
878
815
  }));
879
816
  //#endregion
@@ -881,14 +818,14 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
881
818
  var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
882
819
  module.exports = isexe;
883
820
  isexe.sync = sync;
884
- var fs$2 = __require("fs");
821
+ var fs$1 = __require("fs");
885
822
  function isexe(path, options, cb) {
886
- fs$2.stat(path, function(er, stat) {
823
+ fs$1.stat(path, function(er, stat) {
887
824
  cb(er, er ? false : checkStat(stat, options));
888
825
  });
889
826
  }
890
827
  function sync(path, options) {
891
- return checkStat(fs$2.statSync(path), options);
828
+ return checkStat(fs$1.statSync(path), options);
892
829
  }
893
830
  function checkStat(stat, options) {
894
831
  return stat.isFile() && checkMode(stat, options);
@@ -952,7 +889,7 @@ var require_isexe = /* @__PURE__ */ __commonJSMin(((exports, module) => {
952
889
  //#region ../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
953
890
  var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
954
891
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
955
- var path$4 = __require("path");
892
+ var path$3 = __require("path");
956
893
  var COLON = isWindows ? ";" : ":";
957
894
  var isexe = require_isexe();
958
895
  var getNotFoundError = (cmd) => Object.assign(/* @__PURE__ */ new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -982,7 +919,7 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
982
919
  if (i === pathEnv.length) return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
983
920
  const ppRaw = pathEnv[i];
984
921
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
985
- const pCmd = path$4.join(pathPart, cmd);
922
+ const pCmd = path$3.join(pathPart, cmd);
986
923
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
987
924
  resolve(subStep(p, i, 0));
988
925
  });
@@ -1004,7 +941,7 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1004
941
  for (let i = 0; i < pathEnv.length; i++) {
1005
942
  const ppRaw = pathEnv[i];
1006
943
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1007
- const pCmd = path$4.join(pathPart, cmd);
944
+ const pCmd = path$3.join(pathPart, cmd);
1008
945
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
1009
946
  for (let j = 0; j < pathExt.length; j++) {
1010
947
  const cur = p + pathExt[j];
@@ -1035,7 +972,7 @@ var require_path_key = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1035
972
  //#endregion
1036
973
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
1037
974
  var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1038
- var path$3 = __require("path");
975
+ var path$2 = __require("path");
1039
976
  var which = require_which();
1040
977
  var getPathKey = require_path_key();
1041
978
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -1050,12 +987,12 @@ var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) =>
1050
987
  try {
1051
988
  resolved = which.sync(parsed.command, {
1052
989
  path: env[getPathKey({ env })],
1053
- pathExt: withoutPathExt ? path$3.delimiter : void 0
990
+ pathExt: withoutPathExt ? path$2.delimiter : void 0
1054
991
  });
1055
992
  } catch (e) {} finally {
1056
993
  if (shouldSwitchCwd) process.chdir(cwd);
1057
994
  }
1058
- if (resolved) resolved = path$3.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
995
+ if (resolved) resolved = path$2.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
1059
996
  return resolved;
1060
997
  }
1061
998
  function resolveCommand(parsed) {
@@ -1104,16 +1041,16 @@ var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) =
1104
1041
  //#endregion
1105
1042
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
1106
1043
  var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1107
- var fs$1 = __require("fs");
1044
+ var fs = __require("fs");
1108
1045
  var shebangCommand = require_shebang_command();
1109
1046
  function readShebang(command) {
1110
1047
  const size = 150;
1111
1048
  const buffer = Buffer.alloc(size);
1112
1049
  let fd;
1113
1050
  try {
1114
- fd = fs$1.openSync(command, "r");
1115
- fs$1.readSync(fd, buffer, 0, size, 0);
1116
- fs$1.closeSync(fd);
1051
+ fd = fs.openSync(command, "r");
1052
+ fs.readSync(fd, buffer, 0, size, 0);
1053
+ fs.closeSync(fd);
1117
1054
  } catch (e) {}
1118
1055
  return shebangCommand(buffer.toString());
1119
1056
  }
@@ -1122,7 +1059,7 @@ var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1122
1059
  //#endregion
1123
1060
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
1124
1061
  var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1125
- var path$2 = __require("path");
1062
+ var path$1 = __require("path");
1126
1063
  var resolveCommand = require_resolveCommand();
1127
1064
  var escape = require_escape();
1128
1065
  var readShebang = require_readShebang();
@@ -1145,7 +1082,7 @@ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1145
1082
  const needsShell = !isExecutableRegExp.test(commandFile);
1146
1083
  if (parsed.options.forceShell || needsShell) {
1147
1084
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
1148
- parsed.command = path$2.normalize(parsed.command);
1085
+ parsed.command = path$1.normalize(parsed.command);
1149
1086
  parsed.command = escape.command(parsed.command);
1150
1087
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
1151
1088
  parsed.args = [
@@ -1254,33 +1191,33 @@ function toPath(urlOrPath) {
1254
1191
  }
1255
1192
  function traversePathUp(startPath) {
1256
1193
  return { *[Symbol.iterator]() {
1257
- let currentPath = path$1.resolve(toPath(startPath));
1194
+ let currentPath = path.resolve(toPath(startPath));
1258
1195
  let previousPath;
1259
1196
  while (previousPath !== currentPath) {
1260
1197
  yield currentPath;
1261
1198
  previousPath = currentPath;
1262
- currentPath = path$1.resolve(currentPath, "..");
1199
+ currentPath = path.resolve(currentPath, "..");
1263
1200
  }
1264
1201
  } };
1265
1202
  }
1266
1203
  //#endregion
1267
1204
  //#region ../../node_modules/.pnpm/npm-run-path@6.0.0/node_modules/npm-run-path/index.js
1268
1205
  var npmRunPath = ({ cwd = process$1.cwd(), path: pathOption = process$1.env[pathKey()], preferLocal = true, execPath = process$1.execPath, addExecPath = true } = {}) => {
1269
- const cwdPath = path$1.resolve(toPath(cwd));
1206
+ const cwdPath = path.resolve(toPath(cwd));
1270
1207
  const result = [];
1271
- const pathParts = pathOption.split(path$1.delimiter);
1208
+ const pathParts = pathOption.split(path.delimiter);
1272
1209
  if (preferLocal) applyPreferLocal(result, pathParts, cwdPath);
1273
1210
  if (addExecPath) applyExecPath(result, pathParts, execPath, cwdPath);
1274
- return pathOption === "" || pathOption === path$1.delimiter ? `${result.join(path$1.delimiter)}${pathOption}` : [...result, pathOption].join(path$1.delimiter);
1211
+ return pathOption === "" || pathOption === path.delimiter ? `${result.join(path.delimiter)}${pathOption}` : [...result, pathOption].join(path.delimiter);
1275
1212
  };
1276
1213
  var applyPreferLocal = (result, pathParts, cwdPath) => {
1277
1214
  for (const directory of traversePathUp(cwdPath)) {
1278
- const pathPart = path$1.join(directory, "node_modules/.bin");
1215
+ const pathPart = path.join(directory, "node_modules/.bin");
1279
1216
  if (!pathParts.includes(pathPart)) result.push(pathPart);
1280
1217
  }
1281
1218
  };
1282
1219
  var applyExecPath = (result, pathParts, execPath, cwdPath) => {
1283
- const pathPart = path$1.resolve(cwdPath, toPath(execPath), "..");
1220
+ const pathPart = path.resolve(cwdPath, toPath(execPath), "..");
1284
1221
  if (!pathParts.includes(pathPart)) result.push(pathPart);
1285
1222
  };
1286
1223
  var npmRunPathEnv = ({ env = process$1.env, ...options } = {}) => {
@@ -2290,7 +2227,7 @@ var mapNode = ({ options }) => {
2290
2227
  var handleNodeOption = (file, commandArguments, { node: shouldHandleNode = false, nodePath = execPath, nodeOptions = execArgv.filter((nodeOption) => !nodeOption.startsWith("--inspect")), cwd, execPath: formerNodePath, ...options }) => {
2291
2228
  if (formerNodePath !== void 0) throw new TypeError("The \"execPath\" option has been removed. Please use the \"nodePath\" option instead.");
2292
2229
  const normalizedNodePath = safeNormalizeFileUrl(nodePath, "The \"nodePath\" option");
2293
- const resolvedNodePath = path$1.resolve(cwd, normalizedNodePath);
2230
+ const resolvedNodePath = path.resolve(cwd, normalizedNodePath);
2294
2231
  const newOptions = {
2295
2232
  ...options,
2296
2233
  nodePath: resolvedNodePath,
@@ -2302,7 +2239,7 @@ var handleNodeOption = (file, commandArguments, { node: shouldHandleNode = false
2302
2239
  commandArguments,
2303
2240
  newOptions
2304
2241
  ];
2305
- if (path$1.basename(file, ".exe") === "node") throw new TypeError("When the \"node\" option is true, the first argument does not need to be \"node\".");
2242
+ if (path.basename(file, ".exe") === "node") throw new TypeError("When the \"node\" option is true, the first argument does not need to be \"node\".");
2306
2243
  return [
2307
2244
  resolvedNodePath,
2308
2245
  [
@@ -2386,7 +2323,7 @@ var serializeEncoding = (encoding) => typeof encoding === "string" ? `"${encodin
2386
2323
  //#region ../../node_modules/.pnpm/execa@9.6.1/node_modules/execa/lib/arguments/cwd.js
2387
2324
  var normalizeCwd = (cwd = getDefaultCwd()) => {
2388
2325
  const cwdString = safeNormalizeFileUrl(cwd, "The \"cwd\" option");
2389
- return path$1.resolve(cwdString);
2326
+ return path.resolve(cwdString);
2390
2327
  };
2391
2328
  var getDefaultCwd = () => {
2392
2329
  try {
@@ -2424,7 +2361,7 @@ var normalizeOptions = (filePath, rawArguments, rawOptions) => {
2424
2361
  options.killSignal = normalizeKillSignal(options.killSignal);
2425
2362
  options.forceKillAfterDelay = normalizeForceKillAfterDelay(options.forceKillAfterDelay);
2426
2363
  options.lines = options.lines.map((lines, fdNumber) => lines && !BINARY_ENCODINGS.has(options.encoding) && options.buffer[fdNumber]);
2427
- if (process$1.platform === "win32" && path$1.basename(file, ".exe") === "cmd") commandArguments.unshift("/q");
2364
+ if (process$1.platform === "win32" && path.basename(file, ".exe") === "cmd") commandArguments.unshift("/q");
2428
2365
  return {
2429
2366
  file,
2430
2367
  commandArguments,
@@ -6252,1007 +6189,6 @@ createExeca(mapNode);
6252
6189
  createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
6253
6190
  var { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
6254
6191
  //#endregion
6255
- //#region src/schema/destructive-sql.ts
6256
- var import___vite_browser_external = /* @__PURE__ */ __toESM(require___vite_browser_external(), 1);
6257
- /**
6258
- * Pure helpers that classify an Atlas declarative-apply plan as destructive
6259
- * or not, and decide what `rebase db push` should do about it.
6260
- *
6261
- * `db push` runs `atlas schema apply` to make the live database match the
6262
- * generated `schema.sql`. Removing a collection field compiles to
6263
- * `DROP COLUMN`; renaming compiles to drop-then-add — either destroys data.
6264
- * We first run the apply with `--dry-run` to obtain the planned SQL, scan it
6265
- * here, and refuse to auto-approve anything destructive without an explicit
6266
- * opt-in.
6267
- *
6268
- * A type change is the one destructive plan that names no DROP. `ALTER COLUMN
6269
- * "price" TYPE integer` rounds every 19.99 to 20, and `TYPE date` throws away
6270
- * every timestamp's time of day, in a statement that reads like a routine
6271
- * alter. So a type change is gated too, unless it is a widening every value
6272
- * survives unchanged — which needs the column's current type, read from the
6273
- * catalogue by the caller.
6274
- *
6275
- * Everything in this file is side-effect free so it can be unit-tested
6276
- * without Atlas or a database.
6277
- */
6278
- /**
6279
- * SQL fragments that destroy data or data-bearing objects. Matched
6280
- * case-insensitively against each statement of the plan. `IF EXISTS` /
6281
- * whitespace variations are tolerated by the regexes below.
6282
- */
6283
- var DESTRUCTIVE_PATTERNS = [
6284
- {
6285
- label: "DROP TABLE",
6286
- re: /\bDROP\s+TABLE\b/i
6287
- },
6288
- {
6289
- label: "DROP COLUMN",
6290
- re: /\bDROP\s+COLUMN\b/i
6291
- },
6292
- {
6293
- label: "DROP SCHEMA",
6294
- re: /\bDROP\s+SCHEMA\b/i
6295
- },
6296
- {
6297
- label: "DROP VIEW",
6298
- re: /\bDROP\s+(MATERIALIZED\s+)?VIEW\b/i
6299
- },
6300
- {
6301
- label: "DROP TYPE",
6302
- re: /\bDROP\s+TYPE\b/i
6303
- },
6304
- {
6305
- label: "TRUNCATE",
6306
- re: /\bTRUNCATE\b/i
6307
- }
6308
- ];
6309
- /**
6310
- * Split a SQL script into individual statements, dropping blank lines and
6311
- * `--` comment lines. Deliberately simple: Atlas emits one plain statement
6312
- * per `;`, without string literals that contain semicolons in a schema DDL
6313
- * plan, so a naive split is safe and keeps this dependency-free.
6314
- */
6315
- function splitSqlStatements(sql) {
6316
- return sql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n").split(";").map((s) => s.trim()).filter((s) => s.length > 0);
6317
- }
6318
- /**
6319
- * Every column of every ordinary table outside the system schemas, with its
6320
- * type. What {@link detectDestructiveStatements} compares a planned type
6321
- * change against; `format_type` because it carries the modifier (`(255)`,
6322
- * `(10,2)`) that decides whether a change widens or narrows.
6323
- */
6324
- var COLUMN_TYPES_SQL = `
6325
- SELECT n.nspname AS "schema",
6326
- c.relname AS "table",
6327
- a.attname AS "column",
6328
- format_type(a.atttypid, a.atttypmod) AS "type"
6329
- FROM pg_attribute a
6330
- JOIN pg_class c ON c.oid = a.attrelid
6331
- JOIN pg_namespace n ON n.oid = c.relnamespace
6332
- WHERE c.relkind IN ('r', 'p')
6333
- AND a.attnum > 0
6334
- AND NOT a.attisdropped
6335
- AND n.nspname NOT IN ('pg_catalog', 'information_schema')
6336
- AND n.nspname NOT LIKE 'pg\\_%'
6337
- `;
6338
- /**
6339
- * Scan an Atlas plan (the SQL printed by `schema apply --dry-run`) and return
6340
- * the statements that would destroy data. An empty array means the plan is
6341
- * safe to auto-approve.
6342
- *
6343
- * `columnTypes` is what the database holds now. A type change to a column it
6344
- * does not list is flagged: not knowing what a column is means not knowing
6345
- * whether the change keeps its values.
6346
- */
6347
- function detectDestructiveStatements(planSql, columnTypes = []) {
6348
- const found = [];
6349
- for (const statement of splitSqlStatements(planSql)) {
6350
- const dropped = DESTRUCTIVE_PATTERNS.find(({ re }) => re.test(statement));
6351
- if (dropped) {
6352
- found.push({
6353
- statement,
6354
- kind: dropped.label
6355
- });
6356
- continue;
6357
- }
6358
- const lossy = parseColumnTypeChanges(statement).map((change) => ({
6359
- change,
6360
- from: currentTypeOf(change, columnTypes)
6361
- })).filter(({ change, from }) => from === void 0 || !isLosslessTypeChange(from, change.to));
6362
- if (lossy.length > 0) found.push({
6363
- statement,
6364
- kind: "ALTER COLUMN TYPE",
6365
- detail: lossy.map(({ change, from }) => `"${change.column}" ${from ?? "(current type unknown)"} → ${change.to}`).join("; ")
6366
- });
6367
- }
6368
- return found;
6369
- }
6370
- var IDENT = String.raw`(?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)`;
6371
- var ALTER_TABLE_RE = new RegExp(String.raw`^ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:ONLY\s+)?(${IDENT})(?:\.(${IDENT}))?\s+([\s\S]*)$`, "i");
6372
- var TYPE_CLAUSE_RE = new RegExp(String.raw`^ALTER\s+(?:COLUMN\s+)?(${IDENT})\s+(?:SET\s+DATA\s+)?TYPE\s+([\s\S]+)$`, "i");
6373
- var unquote = (ident) => ident.startsWith("\"") ? ident.slice(1, -1).replace(/""/g, "\"") : ident;
6374
- /**
6375
- * Split an `ALTER TABLE`'s clause list on its top-level commas — the ones
6376
- * outside parentheses and quotes, so `TYPE numeric(10, 2)` stays one clause.
6377
- */
6378
- function splitClauses(body) {
6379
- const clauses = [];
6380
- let depth = 0;
6381
- let quote;
6382
- let start = 0;
6383
- for (let i = 0; i < body.length; i++) {
6384
- const ch = body[i];
6385
- if (quote) {
6386
- if (ch === quote) quote = void 0;
6387
- } else if (ch === "\"" || ch === "'") quote = ch;
6388
- else if (ch === "(") depth++;
6389
- else if (ch === ")") depth--;
6390
- else if (ch === "," && depth === 0) {
6391
- clauses.push(body.slice(start, i).trim());
6392
- start = i + 1;
6393
- }
6394
- }
6395
- clauses.push(body.slice(start).trim());
6396
- return clauses.filter((clause) => clause.length > 0);
6397
- }
6398
- /**
6399
- * Every `ALTER COLUMN … [SET DATA] TYPE` in one statement. Atlas prints its
6400
- * dry-run statements under a `-> ` marker and puts several clauses in one
6401
- * `ALTER TABLE`, so both are handled.
6402
- */
6403
- function parseColumnTypeChanges(statement) {
6404
- const table = ALTER_TABLE_RE.exec(statement.replace(/^\s*->\s*/, "").trim());
6405
- if (!table) return [];
6406
- const [schema, name] = table[2] ? [unquote(table[1]), unquote(table[2])] : [void 0, unquote(table[1])];
6407
- const changes = [];
6408
- for (const clause of splitClauses(table[3])) {
6409
- const match = TYPE_CLAUSE_RE.exec(clause);
6410
- if (!match) continue;
6411
- const to = match[2].split(/\s+(?:USING|COLLATE)\s/i)[0].trim();
6412
- changes.push({
6413
- schema,
6414
- table: name,
6415
- column: unquote(match[1]),
6416
- to
6417
- });
6418
- }
6419
- return changes;
6420
- }
6421
- /**
6422
- * The column's current type. A table named without its schema matches in any
6423
- * schema, but only when every match agrees — two same-named tables with
6424
- * different types leave the answer unknown.
6425
- */
6426
- function currentTypeOf(change, columnTypes) {
6427
- const types = new Set(columnTypes.filter((col) => col.table === change.table && col.column === change.column && (change.schema === void 0 || col.schema === change.schema)).map((col) => col.type));
6428
- return types.size === 1 ? [...types][0] : void 0;
6429
- }
6430
- /** Spellings of one type, mapped to one name. Anything absent compares verbatim. */
6431
- var TYPE_ALIASES = {
6432
- "int": "integer",
6433
- "int4": "integer",
6434
- "int2": "smallint",
6435
- "int8": "bigint",
6436
- "character varying": "varchar",
6437
- "decimal": "numeric",
6438
- "timestamp without time zone": "timestamp",
6439
- "timestamp with time zone": "timestamptz",
6440
- "time without time zone": "time",
6441
- "time with time zone": "timetz",
6442
- "bool": "boolean",
6443
- "float4": "real",
6444
- "float8": "double precision",
6445
- "character": "bpchar",
6446
- "char": "bpchar"
6447
- };
6448
- /** Decimal digits each integer type holds, which is what a `numeric` must hold too. */
6449
- var INTEGER_DIGITS = {
6450
- smallint: 5,
6451
- integer: 10,
6452
- bigint: 19
6453
- };
6454
- /** Types whose modifier is a fractional-seconds precision, 6 when absent. */
6455
- var SECONDS_PRECISION = /* @__PURE__ */ new Set([
6456
- "timestamp",
6457
- "timestamptz",
6458
- "time",
6459
- "timetz"
6460
- ]);
6461
- function parseType(raw) {
6462
- let text = raw.trim().replace(/\s+/g, " ");
6463
- let dims = 0;
6464
- for (let suffix = /\s*\[\d*\]$/.exec(text); suffix; suffix = /\s*\[\d*\]$/.exec(text)) {
6465
- dims++;
6466
- text = text.slice(0, suffix.index);
6467
- }
6468
- if (text.includes("\"")) return {
6469
- base: text,
6470
- dims
6471
- };
6472
- text = text.toLowerCase().replace(/^pg_catalog\./, "");
6473
- let args;
6474
- const modifier = /\(([^)]*)\)/.exec(text);
6475
- if (modifier) {
6476
- args = modifier[1].split(",").map((part) => Number(part.trim()));
6477
- text = `${text.slice(0, modifier.index)} ${text.slice(modifier.index + modifier[0].length)}`.replace(/\s+/g, " ").trim();
6478
- }
6479
- return {
6480
- base: TYPE_ALIASES[text] ?? text,
6481
- args,
6482
- dims
6483
- };
6484
- }
6485
- /** Does moving from one modifier to another, on the same type, keep every value? */
6486
- function modifierWidens(base, from, to) {
6487
- if (JSON.stringify(from) === JSON.stringify(to)) return true;
6488
- if (base === "varchar") return to === void 0 || from !== void 0 && to[0] >= from[0];
6489
- if (base === "numeric") {
6490
- if (to === void 0) return true;
6491
- if (from === void 0) return false;
6492
- const [p1, s1 = 0] = from;
6493
- const [p2, s2 = 0] = to;
6494
- return s2 >= s1 && p2 - s2 >= p1 - s1;
6495
- }
6496
- if (SECONDS_PRECISION.has(base)) return (to?.[0] ?? 6) >= (from?.[0] ?? 6);
6497
- return false;
6498
- }
6499
- /**
6500
- * Is `ALTER COLUMN … TYPE to` a widening, one that keeps every value exactly
6501
- * as it was?
6502
- *
6503
- * Deliberately short. Everything not listed needs a confirmation, including
6504
- * changes that are usually harmless: `timestamp` → `timestamptz` reads each
6505
- * value in the session's time zone, `json` → `jsonb` drops duplicate keys,
6506
- * `real` → `double precision` prints 0.1 as 0.100000001490116, and `integer` →
6507
- * `text` keeps the digits but changes what the column is.
6508
- *
6509
- * - the same type, with a modifier at least as wide (`varchar(100)` →
6510
- * `varchar(255)`, `numeric(10,2)` → `numeric(12,2)` or unbounded,
6511
- * `timestamptz(3)` → `timestamptz`);
6512
- * - `smallint` → `integer` → `bigint`, and any of them to a `numeric` with
6513
- * room for all their digits;
6514
- * - `varchar(n)` → `text`, and `text` → an unbounded `varchar`.
6515
- *
6516
- * Arrays follow their element type, and never gain or lose a dimension.
6517
- */
6518
- function isLosslessTypeChange(from, to) {
6519
- const a = parseType(from);
6520
- const b = parseType(to);
6521
- if (a.dims !== b.dims) return false;
6522
- if (a.args?.some(Number.isNaN) || b.args?.some(Number.isNaN)) return false;
6523
- if (a.base === b.base) return modifierWidens(a.base, a.args, b.args);
6524
- const fromDigits = INTEGER_DIGITS[a.base];
6525
- if (fromDigits !== void 0) {
6526
- const toDigits = INTEGER_DIGITS[b.base];
6527
- if (toDigits !== void 0) return toDigits >= fromDigits;
6528
- if (b.base === "numeric") return b.args === void 0 || b.args[0] - (b.args[1] ?? 0) >= fromDigits;
6529
- return false;
6530
- }
6531
- if (a.base === "varchar" && b.base === "text") return true;
6532
- if (a.base === "text" && b.base === "varchar") return b.args === void 0;
6533
- return false;
6534
- }
6535
- /**
6536
- * Decide how `db push` should proceed given the plan's destructiveness and
6537
- * the invocation context.
6538
- *
6539
- * - No destructive statements → `apply` (safe to auto-approve).
6540
- * - Destructive + `--allow-destructive` → `apply` (operator opted in).
6541
- * - Destructive + interactive TTY → `confirm` (prompt before applying).
6542
- * - Destructive + non-interactive → `refuse` (never silently drop data in
6543
- * CI / scripts / agents).
6544
- */
6545
- function decidePushSafety(opts) {
6546
- if (opts.destructiveCount === 0) return "apply";
6547
- if (opts.allowDestructive) return "apply";
6548
- return opts.interactive ? "confirm" : "refuse";
6549
- }
6550
- //#endregion
6551
- //#region src/module-dir.ts
6552
- var moduleDir = path.dirname(fileURLToPath$1(import.meta.url));
6553
- //#endregion
6554
- //#region src/cli-helpers.ts
6555
- /**
6556
- * Which package manager installed this project, read off its lockfile.
6557
- *
6558
- * Every remedy for a blocked build script is package-manager specific, and
6559
- * naming the wrong one is the same failure as naming none: `pnpm approve-builds`
6560
- * is not a thing an npm user can run, and `pnpm.onlyBuiltDependencies` is a key
6561
- * npm does not read. This existed as a pnpm-only message for long enough that
6562
- * npm 12 — which blocks dependency lifecycle scripts by default, the way pnpm 10
6563
- * does — could ship and leave every npm reader three unfollowable commands deep.
6564
- *
6565
- * The lockfile, not `npm_config_user_agent`: the question is how the project's
6566
- * `node_modules` was built, not which binary happens to be invoking us. Walks up
6567
- * because commands run from `backend/` in a scaffolded project, and the lockfile
6568
- * is at the workspace root.
6569
- */
6570
- function detectProjectPackageManager(startDir = process.cwd()) {
6571
- const lockfiles = [
6572
- ["pnpm-lock.yaml", "pnpm"],
6573
- ["bun.lock", "bun"],
6574
- ["bun.lockb", "bun"],
6575
- ["yarn.lock", "yarn"],
6576
- ["package-lock.json", "npm"]
6577
- ];
6578
- let dir = path.resolve(startDir);
6579
- while (true) {
6580
- for (const [file, manager] of lockfiles) if (fs.existsSync(path.join(dir, file))) return manager;
6581
- const parent = path.dirname(dir);
6582
- if (parent === dir) return "unknown";
6583
- dir = parent;
6584
- }
6585
- }
6586
- /**
6587
- * How to let `@ariga/atlas` run its `preinstall`, in the reader's own package
6588
- * manager.
6589
- *
6590
- * Returns the lines to print after "the binary is missing but the package is
6591
- * installed". Kept next to {@link detectProjectPackageManager} so the two move
6592
- * together, and returned rather than printed so it can be asserted on.
6593
- */
6594
- function describeBuildScriptRemedy(packageName, manager = detectProjectPackageManager()) {
6595
- switch (manager) {
6596
- case "npm": return [
6597
- ` npm install-scripts approve ${packageName}`,
6598
- "",
6599
- " or, to record it in the project (what `rebase init` scaffolds):",
6600
- "",
6601
- " // package.json",
6602
- ` "allowScripts": { "${packageName}": true }`,
6603
- "",
6604
- " Then re-run `npm install`."
6605
- ];
6606
- case "yarn": return [
6607
- " // .yarnrc.yml",
6608
- " enableScripts: true",
6609
- "",
6610
- " Then re-run `yarn install`."
6611
- ];
6612
- case "bun": return [
6613
- " // package.json",
6614
- ` "trustedDependencies": ["${packageName}"]`,
6615
- "",
6616
- " Then re-run `bun install`."
6617
- ];
6618
- default: return [
6619
- " pnpm approve-builds",
6620
- "",
6621
- " or, to record it in the project (what `rebase init` scaffolds):",
6622
- "",
6623
- " // package.json",
6624
- ` "pnpm": { "onlyBuiltDependencies": ["${packageName}"] }`,
6625
- "",
6626
- " Then re-run `pnpm install`."
6627
- ];
6628
- }
6629
- }
6630
- /**
6631
- * "Install again, from scratch", in the reader's own package manager.
6632
- *
6633
- * For the `bin-link-missing` state: the tree is half-written, and re-resolving
6634
- * it is what recreates the `.bin` shim. `--force` on pnpm is what makes it
6635
- * relink rather than decide the tree is already up to date.
6636
- */
6637
- function describeReinstallCommand(manager = detectProjectPackageManager()) {
6638
- switch (manager) {
6639
- case "npm": return "rm -rf node_modules && npm install";
6640
- case "yarn": return "yarn install --force";
6641
- case "bun": return "rm -rf node_modules && bun install";
6642
- default: return "pnpm install --force";
6643
- }
6644
- }
6645
- /** `add <pkg>` as a dev dependency, in the reader's own package manager. */
6646
- function describeDevAddCommand(packageName, manager = detectProjectPackageManager()) {
6647
- switch (manager) {
6648
- case "npm": return `npm install -D ${packageName}`;
6649
- case "yarn": return `yarn add -D ${packageName}`;
6650
- case "bun": return `bun add -d ${packageName}`;
6651
- default: return `pnpm add -D ${packageName}`;
6652
- }
6653
- }
6654
- /**
6655
- * Where a package's own manifest lives, or null if it is not installed here.
6656
- *
6657
- * `package.json` rather than the package root: a package with an `exports` map
6658
- * that omits `.` is unresolvable by name even when it is perfectly installed,
6659
- * which would misreport it as absent.
6660
- */
6661
- function resolvePackageManifest(packageName) {
6662
- const bases = [pathToFileURL(path.join(process.cwd(), "package.json")), pathToFileURL(path.join(moduleDir, "package.json"))];
6663
- for (const base of bases) try {
6664
- return createRequire(base).resolve(`${packageName}/package.json`);
6665
- } catch {}
6666
- return null;
6667
- }
6668
- /**
6669
- * The binaries a package's own manifest declares, as absolute paths.
6670
- *
6671
- * `bin` is a string for a single binary named after the package, or a map.
6672
- * Both forms appear in the wild and `@ariga/atlas` uses the map.
6673
- */
6674
- function declaredBinaries(manifestPath) {
6675
- let manifest;
6676
- try {
6677
- manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
6678
- } catch {
6679
- return [];
6680
- }
6681
- const dir = path.dirname(manifestPath);
6682
- if (typeof manifest.bin === "string") return [path.resolve(dir, manifest.bin)];
6683
- if (manifest.bin && typeof manifest.bin === "object") return Object.values(manifest.bin).filter((target) => typeof target === "string").map((target) => path.resolve(dir, target));
6684
- return [];
6685
- }
6686
- function diagnoseMissingBin(packageName) {
6687
- const manifestPath = resolvePackageManifest(packageName);
6688
- if (!manifestPath) return "not-installed";
6689
- const binaries = declaredBinaries(manifestPath);
6690
- if (binaries.length === 0) return "build-script-blocked";
6691
- return binaries.some((file) => fs.existsSync(file)) ? "bin-link-missing" : "build-script-blocked";
6692
- }
6693
- function resolveLocalBin(binName) {
6694
- let dir = moduleDir;
6695
- while (true) {
6696
- const candidate = path.join(dir, "node_modules", ".bin", binName);
6697
- if (fs.existsSync(candidate)) return candidate;
6698
- const parent = path.dirname(dir);
6699
- if (parent === dir) break;
6700
- dir = parent;
6701
- }
6702
- let cwd = process.cwd();
6703
- while (true) {
6704
- const candidate = path.join(cwd, "node_modules", ".bin", binName);
6705
- if (fs.existsSync(candidate)) return candidate;
6706
- const parent = path.dirname(cwd);
6707
- if (parent === cwd) break;
6708
- cwd = parent;
6709
- }
6710
- try {
6711
- const globalPath = execSync(process.platform === "win32" ? `where ${binName}` : `which ${binName}`, { encoding: "utf-8" }).trim().split("\n")[0].trim();
6712
- if (globalPath && fs.existsSync(globalPath)) return globalPath;
6713
- } catch {}
6714
- return null;
6715
- }
6716
- async function getTableIncludesFromCollections(allCollections) {
6717
- const { getTableName, relationalCollections, resolveJunctionSpecs } = await import("@rebasepro/common");
6718
- const { isPostgresCollectionConfig } = await import("@rebasepro/types");
6719
- const collections = relationalCollections(allCollections);
6720
- const includes = [];
6721
- for (const col of collections) {
6722
- const tableName = getTableName(col);
6723
- const schema = isPostgresCollectionConfig(col) && col.schema ? col.schema : "public";
6724
- if (tableName) includes.push(`${schema}.${tableName}`);
6725
- }
6726
- for (const junction of resolveJunctionSpecs(collections).values()) includes.push(`${junction.schema}.${junction.table}`);
6727
- return Array.from(new Set(includes));
6728
- }
6729
- async function getTableIncludes(collectionsPath) {
6730
- return getTableIncludesFromCollections(await loadCollectionsForCli(collectionsPath));
6731
- }
6732
- /**
6733
- * Load a project's collections the way the Atlas-facing commands need them.
6734
- *
6735
- * Deliberately forgiving — a file that fails to import is skipped rather than
6736
- * fatal — because the callers use this to *narrow* what Atlas may touch, and a
6737
- * hard failure here would block a push over an unrelated broken file. Callers
6738
- * that cannot tolerate a partial answer (the table excludes, which fail closed)
6739
- * check the result themselves.
6740
- */
6741
- /** Paths this process has already complained about. See {@link loadCollectionsForCli}. */
6742
- var warnedMissingCollectionsPaths = /* @__PURE__ */ new Set();
6743
- async function loadCollectionsForCli(collectionsPath) {
6744
- const resolvedPath = path.resolve(collectionsPath);
6745
- const collections = [];
6746
- if (!fs.existsSync(resolvedPath) && !warnedMissingCollectionsPaths.has(resolvedPath)) {
6747
- warnedMissingCollectionsPaths.add(resolvedPath);
6748
- outWarn(chalk.yellow(` ⚠ Collections path not found: "${collectionsPath}"\n Resolved to: ${resolvedPath}\n (relative to cwd: ${process.cwd()})\n Pass an absolute path, or a path relative to where you run the command.`));
6749
- }
6750
- if (fs.existsSync(resolvedPath)) {
6751
- if (fs.statSync(resolvedPath).isDirectory()) {
6752
- const files = fs.readdirSync(resolvedPath);
6753
- for (const file of files) if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") try {
6754
- const module = await import(pathToFileURL(path.join(resolvedPath, file)).href);
6755
- if (module && module.default) collections.push(module.default);
6756
- } catch {}
6757
- }
6758
- }
6759
- return collections;
6760
- }
6761
- function getDevDatabaseUrl(databaseUrl) {
6762
- try {
6763
- const parsed = new URL(databaseUrl);
6764
- parsed.pathname = `/${parsed.pathname.slice(1)}_dev_diff`;
6765
- return parsed.toString();
6766
- } catch {
6767
- return databaseUrl + "_dev_diff";
6768
- }
6769
- }
6770
- /**
6771
- * Create the scratch database Atlas plans against, and say so when we cannot.
6772
- *
6773
- * Atlas needs a `--dev-url` — an empty database it can build the desired state
6774
- * in — and this makes `<db>_dev_diff` next to the real one. The whole body used
6775
- * to be wrapped in an empty catch commented "Ignore, let Atlas handle
6776
- * connection failures", and Atlas's version of handling it is `postgres:
6777
- * querying system variables: pq: database "app_dev_diff" does not exist
6778
- * (3D000)` — the symptom, four frames downstream of the cause, with the cause
6779
- * thrown away.
6780
- *
6781
- * The cause is almost always `42501`: `CREATE DATABASE` is a privilege managed
6782
- * providers do not grant, so this is the first thing a hosted user hits and the
6783
- * one they can do least with.
6784
- *
6785
- * @returns whether the scratch database was created by *this* call — the only
6786
- * case in which dropping it afterwards is ours to do.
6787
- */
6788
- async function ensureDevDatabaseExists(databaseUrl, devDatabaseUrl) {
6789
- let devDbName = "";
6790
- try {
6791
- const { Client } = await import("pg");
6792
- const parsed = new URL(databaseUrl);
6793
- devDbName = new URL(devDatabaseUrl).pathname.slice(1);
6794
- parsed.pathname = "/postgres";
6795
- const client = new Client({ connectionString: parsed.toString() });
6796
- await client.connect();
6797
- try {
6798
- if ((await client.query("SELECT 1 FROM pg_database WHERE datname = $1", [devDbName])).rowCount === 0) {
6799
- await client.query(`CREATE DATABASE "${devDbName}"`);
6800
- out(chalk.gray(` ✓ Created validation database "${devDbName}"`));
6801
- return true;
6802
- }
6803
- } finally {
6804
- await client.end();
6805
- }
6806
- } catch (err) {
6807
- outWarn(describeDevDatabaseFailure(err, devDbName || "<db>_dev_diff"));
6808
- }
6809
- return false;
6810
- }
6811
- /**
6812
- * What went wrong creating the scratch database, and what to do about it.
6813
- *
6814
- * Exported for its test: the `42501` branch is the one that matters and it
6815
- * cannot be provoked without a non-superuser role.
6816
- */
6817
- function describeDevDatabaseFailure(err, devDbName) {
6818
- const code = err?.code;
6819
- const message = err instanceof Error ? err.message : String(err);
6820
- const lines = [chalk.yellow(` ⚠ Could not create the Atlas scratch database "${devDbName}": ${message}`)];
6821
- if (code === "42501") lines.push(chalk.gray(` Atlas plans every change in a throwaway copy of the schema, and CREATE DATABASE
6822
- is a privilege most managed providers withhold. Two ways forward:
6823
- • GRANT the role CREATEDB: ALTER ROLE "<role>" CREATEDB;
6824
- • or create it once by hand: CREATE DATABASE "${devDbName}";`));
6825
- else lines.push(chalk.gray(" Atlas will fail next with `database \"" + devDbName + "\" does not exist (3D000)`, which is this."));
6826
- return lines.join("\n");
6827
- }
6828
- /**
6829
- * Drop the scratch database again, after the command that needed it succeeded.
6830
- *
6831
- * It is a full copy of the schema — no rows, but every table, type and index —
6832
- * and it was left behind per target forever. The only notice anyone got was
6833
- * `rebase db branch prune` reporting "3 Atlas scratch database(s) left over
6834
- * from db push", and on a provider that bills per database it is a silent cost.
6835
- *
6836
- * Deliberately NOT called on the failure path: after a failed push the scratch
6837
- * database holds the state Atlas was planning against, which is the one thing
6838
- * worth inspecting.
6839
- */
6840
- async function dropDevDatabase(databaseUrl, devDatabaseUrl) {
6841
- try {
6842
- const { Client } = await import("pg");
6843
- const parsed = new URL(databaseUrl);
6844
- const devDbName = new URL(devDatabaseUrl).pathname.slice(1);
6845
- parsed.pathname = "/postgres";
6846
- const client = new Client({ connectionString: parsed.toString() });
6847
- await client.connect();
6848
- try {
6849
- await client.query(`DROP DATABASE IF EXISTS "${devDbName}"`);
6850
- out(chalk.gray(` ✓ Removed validation database "${devDbName}"`));
6851
- } finally {
6852
- await client.end();
6853
- }
6854
- } catch (err) {
6855
- outWarn(chalk.gray(` ⚠ Could not remove the Atlas scratch database: ${err instanceof Error ? err.message : String(err)}\n \`rebase db branch prune --include-dev-diff\` clears them.`));
6856
- }
6857
- }
6858
- /**
6859
- * The generated SQL for the project's `search` blocks, if it has any.
6860
- *
6861
- * @param drizzleDir directory holding the generated SQL. Defaults to `drizzle`
6862
- * under the working directory.
6863
- */
6864
- function readSearchDdl(drizzleDir = path.resolve(process.cwd(), "drizzle")) {
6865
- const searchFile = path.join(drizzleDir, "search.sql");
6866
- if (!fs.existsSync(searchFile)) return "";
6867
- return fs.readFileSync(searchFile, "utf-8").trim();
6868
- }
6869
- /**
6870
- * Bring the search column, its index and their helpers up to date.
6871
- *
6872
- * Runs *after* Atlas, not before: the statements are `ALTER TABLE ... ADD
6873
- * COLUMN`, so the table has to exist. Atlas is told to ignore these objects
6874
- * entirely (`getSearchExcludes`) — it cannot manage them, and left to itself it
6875
- * would plan a `DROP COLUMN` for every one, since they are absent from the
6876
- * desired state it was given.
6877
- *
6878
- * A no-op when no collection declared `search`. A failure is *not* swallowed:
6879
- * silently pushing a schema whose search column never appeared is how a
6880
- * collection ends up with search configured, no error anywhere, and no results.
6881
- */
6882
- async function applySearchDdl(databaseUrl, drizzleDir = path.resolve(process.cwd(), "drizzle")) {
6883
- const sql = readSearchDdl(drizzleDir);
6884
- if (!sql) return;
6885
- const { Client } = await import("pg");
6886
- const client = new Client({ connectionString: databaseUrl });
6887
- await client.connect();
6888
- try {
6889
- await client.query(sql);
6890
- } finally {
6891
- await client.end();
6892
- }
6893
- out(chalk.gray(" ✓ Applied full-text search columns and indexes"));
6894
- }
6895
- /**
6896
- * Glob patterns keeping Atlas away from the search objects.
6897
- *
6898
- * Returns an empty list — and so changes nothing — for a project with no
6899
- * `search` block, which is every project that has not opted in.
6900
- */
6901
- async function getSearchExcludes(collectionsPath) {
6902
- const { searchExcludePatterns } = await import("./generate-postgres-ddl-logic-Bt2d2mRH.js").then((n) => n.o);
6903
- return searchExcludePatterns(await loadCollectionsForCli(collectionsPath));
6904
- }
6905
- /**
6906
- * Every `generated column → column it reads` edge in the database.
6907
- *
6908
- * Read from `pg_depend` rather than by matching names in the expression text:
6909
- * a generated column's `pg_attrdef` row carries one `deptype = 'n'` dependency
6910
- * per column its expression references, which is the catalogue's own answer and
6911
- * cannot be fooled by a column name that also appears inside a string literal
6912
- * or as part of a longer identifier.
6913
- *
6914
- * Used by `db push` to find what has to come out of the way before Atlas runs —
6915
- * see `generated-column-conflicts.ts` for why that is necessary at all.
6916
- */
6917
- async function queryGeneratedColumnDependencies(databaseUrl) {
6918
- const { Client } = await import("pg");
6919
- const client = new Client({ connectionString: databaseUrl });
6920
- await client.connect();
6921
- try {
6922
- return (await client.query(`
6923
- SELECT n.nspname AS "schema",
6924
- c.relname AS "table",
6925
- gen.attname AS "column",
6926
- dep.attname AS "dependsOn"
6927
- FROM pg_attrdef ad
6928
- JOIN pg_class c ON c.oid = ad.adrelid
6929
- JOIN pg_namespace n ON n.oid = c.relnamespace
6930
- JOIN pg_attribute gen ON gen.attrelid = ad.adrelid AND gen.attnum = ad.adnum
6931
- JOIN pg_depend d ON d.classid = 'pg_attrdef'::regclass
6932
- AND d.objid = ad.oid
6933
- AND d.refclassid = 'pg_class'::regclass
6934
- AND d.refobjid = ad.adrelid
6935
- AND d.deptype = 'n'
6936
- JOIN pg_attribute dep ON dep.attrelid = ad.adrelid AND dep.attnum = d.refobjsubid
6937
- WHERE gen.attgenerated <> ''
6938
- AND NOT gen.attisdropped
6939
- AND NOT dep.attisdropped;
6940
- `)).rows;
6941
- } finally {
6942
- await client.end();
6943
- }
6944
- }
6945
- /**
6946
- * Every column in the database with its current type, for the destructive
6947
- * gate to tell a widening `ALTER COLUMN … TYPE` from one that loses data.
6948
- */
6949
- async function queryColumnTypes(databaseUrl) {
6950
- const { Client } = await import("pg");
6951
- const client = new Client({ connectionString: databaseUrl });
6952
- await client.connect();
6953
- try {
6954
- return (await client.query(COLUMN_TYPES_SQL)).rows;
6955
- } finally {
6956
- await client.end();
6957
- }
6958
- }
6959
- /**
6960
- * Drop the generated columns standing in the way of an apply.
6961
- *
6962
- * Their indexes and their `COMMENT ON COLUMN` stamp go with them, which is
6963
- * exactly right: `applySearchDdl` rebuilds all three from `search.sql` and a
6964
- * surviving stamp would make the rebuilt column look like drift.
6965
- */
6966
- async function dropGeneratedColumns(databaseUrl, statements) {
6967
- if (statements.length === 0) return;
6968
- const { Client } = await import("pg");
6969
- const client = new Client({ connectionString: databaseUrl });
6970
- await client.connect();
6971
- try {
6972
- for (const statement of statements) await client.query(statement);
6973
- } finally {
6974
- await client.end();
6975
- }
6976
- }
6977
- /**
6978
- * The generated SQL for the project's `vector` properties, if it has any.
6979
- *
6980
- * @param drizzleDir directory holding the generated SQL. Defaults to `drizzle`
6981
- * under the working directory.
6982
- */
6983
- function readVectorDdl(drizzleDir = path.resolve(process.cwd(), "drizzle")) {
6984
- const vectorFile = path.join(drizzleDir, "vector.sql");
6985
- if (!fs.existsSync(vectorFile)) return "";
6986
- return fs.readFileSync(vectorFile, "utf-8").trim();
6987
- }
6988
- /**
6989
- * Install pgvector and bring the vector columns and their ANN indexes up to
6990
- * date.
6991
- *
6992
- * Runs *after* Atlas, like {@link applySearchDdl} and for the same reason: the
6993
- * statements are `ALTER TABLE … ADD COLUMN`, so the table has to exist. Atlas
6994
- * is told to ignore these objects entirely ({@link getVectorExcludes}) — it
6995
- * cannot manage a type its dev database is structurally incapable of having,
6996
- * and left to itself it would plan a `DROP COLUMN` for every one.
6997
- *
6998
- * A no-op when no collection declares a `vector` property. A failure is *not*
6999
- * swallowed: a push that reported success while the embedding column never
7000
- * appeared is how a project ends up with vector search configured, no error
7001
- * anywhere, and no results.
7002
- */
7003
- async function applyVectorDdl(databaseUrl, drizzleDir = path.resolve(process.cwd(), "drizzle")) {
7004
- const sql = readVectorDdl(drizzleDir);
7005
- if (!sql) return;
7006
- const { vectorExtensionHint } = await import("./vector-index-CMJKb3C9.js").then((n) => n.f);
7007
- const { Client } = await import("pg");
7008
- const client = new Client({ connectionString: databaseUrl });
7009
- await client.connect();
7010
- try {
7011
- await client.query(sql);
7012
- } catch (err) {
7013
- const message = err instanceof Error ? err.message : String(err);
7014
- const hint = vectorExtensionHint(message);
7015
- if (!hint) throw err;
7016
- throw new Error(`${message}${hint}`, { cause: err });
7017
- } finally {
7018
- await client.end();
7019
- }
7020
- out(chalk.gray(" ✓ Applied vector columns and ANN indexes"));
7021
- }
7022
- /**
7023
- * Glob patterns keeping Atlas away from the vector objects.
7024
- *
7025
- * Returns an empty list — and so changes nothing — for a project with no
7026
- * `vector` property, which is every project that has not opted in.
7027
- */
7028
- async function getVectorExcludes(collectionsPath) {
7029
- const { vectorExcludePatterns } = await import("./generate-postgres-ddl-logic-Bt2d2mRH.js").then((n) => n.o);
7030
- return vectorExcludePatterns(await loadCollectionsForCli(collectionsPath));
7031
- }
7032
- /**
7033
- * The generated SQL for the project's `autoValue: "on_update"` properties, if
7034
- * it has any.
7035
- *
7036
- * @param drizzleDir directory holding the generated SQL. Defaults to `drizzle`
7037
- * under the working directory.
7038
- */
7039
- function readTriggersDdl(drizzleDir = path.resolve(process.cwd(), "drizzle")) {
7040
- const triggersFile = path.join(drizzleDir, "triggers.sql");
7041
- if (!fs.existsSync(triggersFile)) return "";
7042
- return fs.readFileSync(triggersFile, "utf-8").trim();
7043
- }
7044
- /**
7045
- * Install the `updated_at` trigger function and bind it to every column that
7046
- * asked for one.
7047
- *
7048
- * Runs *after* Atlas, like {@link applySearchDdl} and {@link applyVectorDdl}:
7049
- * a trigger needs its table and its column to exist. Atlas is told to ignore
7050
- * the triggers ({@link getTriggerExcludes}) because it cannot manage a function
7051
- * at all — its free tier refuses to parse a desired state containing one.
7052
- *
7053
- * A no-op when no property declares `autoValue: "on_update"`. A failure is
7054
- * *not* swallowed: a push that reported success while the trigger never
7055
- * appeared is how `updated_at` ends up silently stale for every writer that is
7056
- * not the driver, which is the whole reason the trigger exists.
7057
- */
7058
- async function applyTriggersDdl(databaseUrl, drizzleDir = path.resolve(process.cwd(), "drizzle")) {
7059
- const sql = readTriggersDdl(drizzleDir);
7060
- if (!sql) return;
7061
- const { Client } = await import("pg");
7062
- const client = new Client({ connectionString: databaseUrl });
7063
- await client.connect();
7064
- try {
7065
- await client.query(sql);
7066
- } finally {
7067
- await client.end();
7068
- }
7069
- out(chalk.gray(" ✓ Applied `updated_at` triggers"));
7070
- }
7071
- /** @see getSearchExcludes */
7072
- async function getTriggerExcludes(collectionsPath) {
7073
- const { triggerExcludePatterns } = await import("./generate-postgres-ddl-logic-Bt2d2mRH.js").then((n) => n.o);
7074
- return triggerExcludePatterns(await loadCollectionsForCli(collectionsPath));
7075
- }
7076
- /**
7077
- * Query the live database for every user table/view outside the system
7078
- * catalogs. Separated from {@link getTableExcludes} so its failure mode can
7079
- * be handled explicitly (fail closed) and so tests can inject a stub.
7080
- */
7081
- /**
7082
- * Every index Postgres holds on a table Rebase manages, as
7083
- * `schema.table.index`.
7084
- *
7085
- * Constraint-backed indexes are left out: a `PRIMARY KEY` or a `UNIQUE` is a
7086
- * *constraint* to Atlas, diffed from the constraint declaration, and naming its
7087
- * index in an exclude would shield the constraint itself.
7088
- */
7089
- async function queryExistingIndexes(databaseUrl) {
7090
- const { Client } = await import("pg");
7091
- const client = new Client({ connectionString: databaseUrl });
7092
- await client.connect();
7093
- try {
7094
- return (await client.query(`
7095
- SELECT n.nspname || '.' || t.relname || '.' || i.relname AS full_name
7096
- FROM pg_index x
7097
- JOIN pg_class i ON i.oid = x.indexrelid
7098
- JOIN pg_class t ON t.oid = x.indrelid
7099
- JOIN pg_namespace n ON n.oid = t.relnamespace
7100
- WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
7101
- AND t.relkind IN ('r', 'p')
7102
- AND NOT EXISTS (
7103
- SELECT 1 FROM pg_constraint c WHERE c.conindid = i.oid
7104
- );
7105
- `)).rows.map((row) => row.full_name);
7106
- } finally {
7107
- await client.end();
7108
- }
7109
- }
7110
- /**
7111
- * Glob patterns keeping Atlas away from indexes that are not Rebase's.
7112
- *
7113
- * The problem this solves is live and predates the `indexes:` block. `db push`
7114
- * is declarative, so an index on a managed table that is absent from
7115
- * `schema.sql` is drift — and Atlas plans `DROP INDEX` for it. Verified
7116
- * against atlas v1.2.3: create one by hand, re-run an unchanged push, and the
7117
- * plan is a bare drop. `DROP INDEX` is not in `DESTRUCTIVE_PATTERNS`, so the
7118
- * auto-approved apply took it without asking. Until now the only way to have
7119
- * an index at all was to write it by hand, which made that the *only* outcome.
7120
- *
7121
- * Ownership rather than a prompt, because once indexes are declarable a drop
7122
- * is usually correct: removing one from the config should remove it from the
7123
- * database, quietly. What must never be dropped is an index Rebase never
7124
- * created — a hand-written one, or one an introspected database arrived with.
7125
- * {@link isRebaseIndexName} is the test, the same arrangement
7126
- * `isGeneratedPolicyName` uses to let policy reconciliation drop only what it
7127
- * generated.
7128
- *
7129
- * The named-in-the-desired-state check comes first and is what makes removal
7130
- * work: a declared index that the author has just deleted still matches the
7131
- * name pattern, is no longer in the plan, and so is *not* excluded — Atlas
7132
- * drops it, as intended.
7133
- *
7134
- * Three-part `schema.table.index`, never two: a two-part pattern reads as a
7135
- * *table* named `<index>` in a *schema* named `<table>`, matches nothing, and
7136
- * reports no error — the trap already recorded for the search excludes.
7137
- */
7138
- async function getForeignIndexExcludes(databaseUrl, collectionsPath, deps = {}) {
7139
- const queryIndexes = deps.queryExistingIndexes ?? queryExistingIndexes;
7140
- const managed = await (deps.getManagedIndexNames ?? managedIndexNames)(collectionsPath);
7141
- let existing;
7142
- try {
7143
- existing = await queryIndexes(databaseUrl);
7144
- } catch (err) {
7145
- throw new ExcludeIntrospectionError(`Failed to introspect the database for unmanaged indexes: ${err instanceof Error ? err.message : String(err)}`, err);
7146
- }
7147
- return existing.filter((full) => {
7148
- const indexName = full.slice(full.lastIndexOf(".") + 1);
7149
- if (managed.has(indexName)) return false;
7150
- return !isRebaseIndexName(indexName);
7151
- });
7152
- }
7153
- /**
7154
- * The index names the current collections would create — search, vector and
7155
- * declared alike. Anything Atlas would emit is by definition not foreign.
7156
- */
7157
- async function managedIndexNames(collectionsPath) {
7158
- const collections = await loadCollectionsForCli(collectionsPath);
7159
- const { searchExcludePatterns, vectorExcludePatterns } = await import("./generate-postgres-ddl-logic-Bt2d2mRH.js").then((n) => n.o);
7160
- const { resolveColumnName } = await import("./column-plan-helpers-DF-8dTVa.js").then((n) => n.n);
7161
- const { buildCollectionIndexPlan } = await import("./collection-index-DdnsxiJ_.js").then((n) => n.i);
7162
- const names = new Set(buildCollectionIndexPlan(collections, resolveColumnName).map((spec) => spec.indexName));
7163
- for (const pattern of [...searchExcludePatterns(collections), ...vectorExcludePatterns(collections)]) names.add(pattern.slice(pattern.lastIndexOf(".") + 1));
7164
- return names;
7165
- }
7166
- async function queryExistingTables(databaseUrl) {
7167
- const { Client } = await import("pg");
7168
- const client = new Client({ connectionString: databaseUrl });
7169
- await client.connect();
7170
- try {
7171
- return (await client.query(`
7172
- SELECT table_schema || '.' || table_name AS full_name
7173
- FROM information_schema.tables
7174
- WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
7175
- AND table_type IN ('BASE TABLE', 'VIEW');
7176
- `)).rows.map((row) => row.full_name);
7177
- } finally {
7178
- await client.end();
7179
- }
7180
- }
7181
- /**
7182
- * Raised when the exclude list could not be built. `db push` MUST abort on
7183
- * this rather than continue: the exclude list is the only thing shielding
7184
- * non-collection (user/system) tables from the auto-approved declarative
7185
- * apply. A partial list — the old fail-open behaviour — meant a transient
7186
- * introspection hiccup dropped every table not present in `schema.sql`.
7187
- */
7188
- var ExcludeIntrospectionError = class extends Error {
7189
- cause;
7190
- constructor(message, cause) {
7191
- super(message);
7192
- this.cause = cause;
7193
- this.name = "ExcludeIntrospectionError";
7194
- }
7195
- };
7196
- /**
7197
- * Build the `--exclude` list that protects tables Rebase doesn't manage from
7198
- * the declarative apply. Anything not backing a collection (or its M2M
7199
- * junctions) is excluded so Atlas never drops it.
7200
- *
7201
- * Fails **closed**: if the database can't be introspected we cannot know
7202
- * which tables to protect, so we throw {@link ExcludeIntrospectionError}
7203
- * instead of returning a near-empty list and letting the caller drop
7204
- * everything.
7205
- *
7206
- * `deps` is injectable for tests; production uses the real pg-backed queries.
7207
- */
7208
- async function getTableExcludes(databaseUrl, collectionsPath, deps = {}) {
7209
- const getIncludes = deps.getIncludes ?? getTableIncludes;
7210
- const queryTables = deps.queryExistingTables ?? queryExistingTables;
7211
- const includes = await getIncludes(collectionsPath);
7212
- const excludes = [
7213
- "atlas_schema_revisions.*",
7214
- "auth",
7215
- "auth.*",
7216
- "rebase",
7217
- "rebase.*"
7218
- ];
7219
- let existingTables;
7220
- try {
7221
- existingTables = await queryTables(databaseUrl);
7222
- } catch (err) {
7223
- throw new ExcludeIntrospectionError(`Failed to introspect the database for unmapped tables: ${err instanceof Error ? err.message : String(err)}`, err);
7224
- }
7225
- for (const table of existingTables) if (!includes.includes(table)) excludes.push(table);
7226
- return excludes;
7227
- }
7228
- /**
7229
- * Ask a yes/no question on an interactive terminal.
7230
- *
7231
- * The `isTTY` guard is the contract, not an optimisation: non-interactive
7232
- * shells (CI, pipes, agents) can never answer, and `readline` on a
7233
- * non-TTY stdin resolves with whatever the pipe happens to contain — or never
7234
- * resolves at all. Returning false there is what makes an unattended
7235
- * `db push` abort instead of auto-confirming a destructive change. Callers
7236
- * should already have gone through {@link decidePushSafety}, which decides
7237
- * whether interactive confirmation is even possible; this is the backstop.
7238
- *
7239
- * Lives here rather than in cli.ts so it is reachable from a test — cli.ts uses
7240
- * `import.meta` and cannot be imported by the jest runner.
7241
- */
7242
- async function promptConfirm(question) {
7243
- if (!process.stdin.isTTY) return false;
7244
- const rl = import___vite_browser_external.default.createInterface({
7245
- input: process.stdin,
7246
- output: process.stdout
7247
- });
7248
- try {
7249
- const answer = await new Promise((resolve) => rl.question(question, resolve));
7250
- return /^y(es)?$/i.test(answer.trim());
7251
- } finally {
7252
- rl.close();
7253
- }
7254
- }
7255
- //#endregion
7256
6192
  //#region src/schema/rls-bootstrap-sql.ts
7257
6193
  var rls_bootstrap_sql_exports = /* @__PURE__ */ __exportAll({
7258
6194
  DROP_LEGACY_AUTH_SCHEMA_SQL: () => DROP_LEGACY_AUTH_SCHEMA_SQL,
@@ -7260,7 +6196,8 @@ var rls_bootstrap_sql_exports = /* @__PURE__ */ __exportAll({
7260
6196
  LEGACY_RLS_FUNCTION_DEPENDENTS_SQL: () => LEGACY_RLS_FUNCTION_DEPENDENTS_SQL,
7261
6197
  RLS_BOOTSTRAP_SQL: () => RLS_BOOTSTRAP_SQL,
7262
6198
  RLS_BOOTSTRAP_STATEMENTS: () => RLS_BOOTSTRAP_STATEMENTS,
7263
- dropLegacyAuthSchema: () => dropLegacyAuthSchema
6199
+ dropLegacyAuthSchema: () => dropLegacyAuthSchema,
6200
+ rlsRuntimeIsCurrent: () => rlsRuntimeIsCurrent
7264
6201
  });
7265
6202
  /**
7266
6203
  * Canonical SQL bootstrap for the RLS helper functions.
@@ -7329,6 +6266,93 @@ $$ LANGUAGE sql STABLE`,
7329
6266
  SELECT COALESCE(NULLIF(current_setting('app.is_anonymous', true), ''), 'false')::boolean;
7330
6267
  $$ LANGUAGE sql STABLE`
7331
6268
  ];
6269
+ /** Everything `CREATE OR REPLACE FUNCTION` sets on a function, as one comparable string. */
6270
+ var describeFunction = (facts) => JSON.stringify([
6271
+ facts.returns.toLowerCase(),
6272
+ facts.language.toLowerCase(),
6273
+ facts.volatility,
6274
+ facts.strict,
6275
+ facts.parallel,
6276
+ facts.securityDefiner,
6277
+ facts.leakproof,
6278
+ facts.cost,
6279
+ facts.hasConfig,
6280
+ facts.body
6281
+ ]);
6282
+ /**
6283
+ * What each `CREATE OR REPLACE FUNCTION` in {@link RLS_BOOTSTRAP_STATEMENTS}
6284
+ * would leave in the catalogue, by `schema.name`. Parsed
6285
+ * from the statements themselves, so the two cannot drift; a statement this
6286
+ * does not recognise makes {@link rlsRuntimeIsCurrent} answer "no", and the
6287
+ * bootstrap runs as it always did.
6288
+ */
6289
+ var expectedRlsFunctions = () => {
6290
+ const expected = /* @__PURE__ */ new Map();
6291
+ for (const statement of RLS_BOOTSTRAP_STATEMENTS) {
6292
+ if (/^CREATE SCHEMA IF NOT EXISTS /.test(statement)) continue;
6293
+ const match = /^CREATE OR REPLACE FUNCTION (\w+)\.(\w+)\(\) RETURNS (\w+) AS \$\$([\s\S]*)\$\$ LANGUAGE (\w+) (IMMUTABLE|STABLE|VOLATILE)$/.exec(statement);
6294
+ if (!match) return void 0;
6295
+ const [, schema, name, returns, body, language, volatility] = match;
6296
+ expected.set(`${schema}.${name}`, describeFunction({
6297
+ returns,
6298
+ language,
6299
+ volatility: volatility[0].toLowerCase(),
6300
+ strict: false,
6301
+ parallel: "u",
6302
+ securityDefiner: false,
6303
+ leakproof: false,
6304
+ cost: 100,
6305
+ hasConfig: false,
6306
+ body
6307
+ }));
6308
+ }
6309
+ return expected;
6310
+ };
6311
+ /**
6312
+ * Whether the `rebase` schema and every RLS helper function are exactly what
6313
+ * {@link RLS_BOOTSTRAP_STATEMENTS} would make them — one catalogue read.
6314
+ *
6315
+ * The boot ran those statements unconditionally, on every boot and on every
6316
+ * source: four `CREATE OR REPLACE FUNCTION`s in a transaction under an
6317
+ * advisory lock. Replacing a function rewrites its `pg_proc` row even when
6318
+ * nothing changed, and every cached plan that inlined it — every plan of every
6319
+ * statement a policy applies to, on every connection — is invalidated by it.
6320
+ * A function that differs in anything `CREATE OR REPLACE` sets (body,
6321
+ * language, volatility, strictness, parallel safety, security, leakproofness,
6322
+ * cost, `SET` options), or is missing, answers `false`, and the statements run
6323
+ * as before.
6324
+ */
6325
+ async function rlsRuntimeIsCurrent(run) {
6326
+ const expected = expectedRlsFunctions();
6327
+ if (!expected) return false;
6328
+ const rows = await run(`SELECT n.nspname AS schema, p.proname AS name, pg_get_function_identity_arguments(p.oid) AS args,
6329
+ format_type(p.prorettype, NULL) AS returns, l.lanname AS language,
6330
+ p.provolatile::text AS volatility, p.proisstrict AS strict, p.proparallel::text AS parallel,
6331
+ p.prosecdef AS security_definer, p.proleakproof AS leakproof, p.procost AS cost,
6332
+ p.proconfig IS NOT NULL AS has_config, p.prosrc AS body
6333
+ FROM pg_proc p
6334
+ JOIN pg_namespace n ON p.pronamespace = n.oid
6335
+ JOIN pg_language l ON p.prolang = l.oid
6336
+ WHERE (n.nspname, p.proname) IN (${[...expected.keys()].map((key) => key.split(".")).map(([schema, name]) => `('${schema}', '${name}')`).join(", ")})`);
6337
+ const live = /* @__PURE__ */ new Map();
6338
+ for (const row of rows) {
6339
+ if (String(row.args ?? "") !== "") continue;
6340
+ live.set(`${row.schema}.${row.name}`, describeFunction({
6341
+ returns: String(row.returns),
6342
+ language: String(row.language),
6343
+ volatility: String(row.volatility),
6344
+ strict: row.strict === true,
6345
+ parallel: String(row.parallel),
6346
+ securityDefiner: row.security_definer === true,
6347
+ leakproof: row.leakproof === true,
6348
+ cost: Number(row.cost),
6349
+ hasConfig: row.has_config === true,
6350
+ body: String(row.body)
6351
+ }));
6352
+ }
6353
+ for (const [key, definition] of expected) if (live.get(key) !== definition) return false;
6354
+ return true;
6355
+ }
7332
6356
  /** The same statements as one script, for migration files and raw clients. */
7333
6357
  var RLS_BOOTSTRAP_SQL = "-- Rebase schema + RLS helper functions (required by the policies below)\n" + RLS_BOOTSTRAP_STATEMENTS.map((s) => `${s};`).join("\n\n") + "\n";
7334
6358
  /**
@@ -7494,6 +6518,6 @@ async function dropLegacyAuthSchema(run, report) {
7494
6518
  }
7495
6519
  }
7496
6520
  //#endregion
7497
- export { resolveLocalBin as A, loadCollectionsForCli as C, readSearchDdl as D, queryGeneratedColumnDependencies as E, forLibpq as F, detectDestructiveStatements as M, splitSqlStatements as N, readTriggersDdl as O, execa as P, getVectorExcludes as S, queryColumnTypes as T, getDevDatabaseUrl as _, ExcludeIntrospectionError as a, getTableExcludes as b, applyVectorDdl as c, describeReinstallCommand as d, detectProjectPackageManager as f, ensureDevDatabaseExists as g, dropGeneratedColumns as h, rls_bootstrap_sql_exports as i, decidePushSafety as j, readVectorDdl as k, describeBuildScriptRemedy as l, dropDevDatabase as m, RLS_BOOTSTRAP_STATEMENTS as n, applySearchDdl as o, diagnoseMissingBin as p, dropLegacyAuthSchema as r, applyTriggersDdl as s, RLS_BOOTSTRAP_SQL as t, describeDevAddCommand as u, getForeignIndexExcludes as v, promptConfirm as w, getTriggerExcludes as x, getSearchExcludes as y };
6521
+ export { rls_bootstrap_sql_exports as a, rlsRuntimeIsCurrent as i, RLS_BOOTSTRAP_STATEMENTS as n, execa as o, dropLegacyAuthSchema as r, RLS_BOOTSTRAP_SQL as t };
7498
6522
 
7499
- //# sourceMappingURL=rls-bootstrap-sql-B8EclDyM.js.map
6523
+ //# sourceMappingURL=rls-bootstrap-sql-Bd-hJk08.js.map