@voltro/cli 0.22.1 → 0.24.0

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 (76) hide show
  1. package/CHANGELOG.md +427 -0
  2. package/bin/voltro.mjs +39 -4
  3. package/dist/{apiBuild-DiWxVz-M.js → apiBuild-B2m4XK_8.js} +45 -23
  4. package/dist/apiBuild-UEM3QBke.js +2 -0
  5. package/dist/bin.js +3 -3
  6. package/dist/{commands-BxRaIOBG.js → commands-CaIhTsC2.js} +2944 -2557
  7. package/dist/{dbCommand-DVASmZj2.js → dbCommand-CC61CsAc.js} +474 -263
  8. package/dist/dbCommand-Cr__4ATv.js +2 -0
  9. package/dist/{dev-Dd3EZzj5.js → dev-DPkQVUTP.js} +1 -1
  10. package/dist/{dev-BRiPgbKw.js → dev-NCuEhNxs.js} +1593 -1575
  11. package/dist/index.js +1 -1
  12. package/dist/{inspectMetrics-9ZSuDeqD.js → inspectMetrics-dcC7gnxu.js} +566 -552
  13. package/dist/{serveCommand-DXJOARkO.js → serveCommand-C4gzDZzc.js} +425 -411
  14. package/dist/serveEntry.js +2 -2
  15. package/dist/{start-CsIjcOi-.js → start-C_4VUDT8.js} +327 -325
  16. package/dist/startEntry.js +2 -2
  17. package/package.json +17 -17
  18. package/templates/AGENTS.md +1 -1
  19. package/templates/agent-docs/_index.md +1 -1
  20. package/templates/agent-docs/_manifest.json +1 -1
  21. package/templates/agent-docs/authentication.md +21 -0
  22. package/templates/agent-docs/caching.md +21 -5
  23. package/templates/agent-docs/cli.md +61 -0
  24. package/templates/agent-docs/data.md +208 -170
  25. package/templates/agent-docs/database/migrations.md +123 -0
  26. package/templates/agent-docs/database/seedsdialects.md +12 -0
  27. package/templates/agent-docs/deployment.md +66 -1
  28. package/templates/agent-docs/plugins.md +42 -0
  29. package/templates/agent-docs/routing.md +23 -1
  30. package/templates/agent-docs/templates/apibackends.md +2 -2
  31. package/templates/agent-docs/whats-new.md +281 -32
  32. package/templates/agent-docs/workflows.md +30 -0
  33. package/templates/apps/api-ai/package.json +8 -7
  34. package/templates/apps/api-auth/package.json +9 -8
  35. package/templates/apps/api-backend/package.json +8 -7
  36. package/templates/apps/api-backend-deactivation/package.json +8 -7
  37. package/templates/apps/api-backend-mail/package.json +9 -8
  38. package/templates/apps/api-backend-mariadb/package.json +10 -9
  39. package/templates/apps/api-backend-storage/package.json +9 -8
  40. package/templates/apps/api-data-advanced/package.json +9 -8
  41. package/templates/apps/api-durable/package.json +9 -8
  42. package/templates/apps/api-feature-flags/package.json +10 -9
  43. package/templates/apps/api-governance/package.json +9 -8
  44. package/templates/apps/api-kv/package.json +9 -8
  45. package/templates/apps/api-moderation/package.json +9 -8
  46. package/templates/apps/api-observability/package.json +9 -8
  47. package/templates/apps/api-ratelimit/package.json +9 -8
  48. package/templates/apps/api-rbac/package.json +9 -8
  49. package/templates/apps/api-rest/package.json +8 -7
  50. package/templates/apps/api-saas/package.json +12 -11
  51. package/templates/apps/api-search/package.json +9 -8
  52. package/templates/apps/api-versioning/app.config.ts +6 -4
  53. package/templates/apps/api-versioning/package.json +9 -8
  54. package/templates/apps/api-webhooks/package.json +10 -9
  55. package/templates/apps/changelog/package.json +7 -6
  56. package/templates/apps/edge-functions/package.json +3 -2
  57. package/templates/apps/frontend-admin/package.json +9 -8
  58. package/templates/apps/frontend-app/package.json +9 -8
  59. package/templates/apps/frontend-blank/package.json +8 -7
  60. package/templates/apps/frontend-contact/package.json +8 -7
  61. package/templates/apps/frontend-contact/src/globals.d.ts +6 -0
  62. package/templates/apps/frontend-dashboard/package.json +8 -7
  63. package/templates/apps/frontend-docs/package.json +8 -7
  64. package/templates/apps/frontend-docs/src/globals.d.ts +6 -0
  65. package/templates/apps/frontend-i18n/package.json +7 -6
  66. package/templates/apps/frontend-landing/package.json +8 -7
  67. package/templates/apps/frontend-landing/src/globals.d.ts +6 -0
  68. package/templates/apps/frontend-spa/package.json +8 -7
  69. package/templates/apps/frontend-spa/src/globals.d.ts +6 -0
  70. package/templates/apps/frontend-ssr/package.json +8 -7
  71. package/templates/apps/frontend-ssr/src/globals.d.ts +6 -0
  72. package/templates/apps/frontend-ssr-api/package.json +9 -8
  73. package/templates/apps/frontend-static-blog/package.json +7 -6
  74. package/templates/apps/frontend-static-blog/src/globals.d.ts +6 -0
  75. package/dist/apiBuild-COyPDf3R.js +0 -2
  76. package/dist/dbCommand-CO3eSAZR.js +0 -2
@@ -1,45 +1,163 @@
1
1
  import { i as e, n as t, r as n, s as r, t as i } from "./seedRunner-D6eu-u5U.js";
2
- import { t as a } from "./frameworkTableAssembly-sg41s14F.js";
3
- import { g as o } from "./fileConventions-CBNPZVOV.js";
4
- import { a as s, n as c, t as l } from "./cliArgs-qdZSElM3.js";
5
- import { basename as u, join as d, relative as f, resolve as p } from "node:path";
6
- import { pathToFileURL as m } from "node:url";
7
- import { Effect as h } from "effect";
8
- import { createLogger as ee } from "@voltro/logger";
9
- import { promises as g, writeSync as te } from "node:fs";
10
- import { InMemoryDataStore as ne } from "@voltro/runtime";
11
- import { SqlClient as _ } from "@effect/sql";
12
- import { actorsTable as re, frameworkTables as v, isMigrationDefinition as y, registerCoreTables as ie } from "@voltro/database";
2
+ import { n as a } from "./appModuleLoader-C9r9mxZt.js";
3
+ import { t as o } from "./frameworkTableAssembly-sg41s14F.js";
4
+ import { g as s } from "./fileConventions-CBNPZVOV.js";
5
+ import { a as c, n as l, t as u } from "./cliArgs-qdZSElM3.js";
6
+ import { basename as d, join as f, relative as p, resolve as m } from "node:path";
7
+ import { pathToFileURL as h } from "node:url";
8
+ import { Effect as g } from "effect";
9
+ import { createLogger as _ } from "@voltro/logger";
10
+ import { promises as v, writeSync as y } from "node:fs";
11
+ import { InMemoryDataStore as ee } from "@voltro/runtime";
12
+ import { SqlClient as b } from "@effect/sql";
13
+ import { actorsTable as te, frameworkTables as x, isMigrationDefinition as ne, quoteIdent as re, registerCoreTables as ie } from "@voltro/database";
13
14
  import { createHash as ae } from "node:crypto";
14
- import { applyPlan as b, applySchema as x, declaredSnapshot as oe, destructiveScope as se, fingerprintSchema as S, ignoreTablesFromEnv as C, introspectSchema as ce, planMigrations as w, rollbackFileBasedMigration as le, runFileBasedMigrations as ue, shortFingerprint as T, squashMigrationPlans as de, unblockLossy as fe } from "@voltro/database/sql";
15
+ import { DEFAULT_CDC_CHANNEL as oe, applyPlan as se, applySchema as S, declaredSnapshot as ce, destructiveScope as le, detectReactiveTriggerDrift as ue, fingerprintSchema as C, ignoreTablesFromEnv as w, introspectSchema as T, pendingFileMigrationIds as de, planMigrations as E, reactiveTriggerRepairSql as fe, rollbackFileBasedMigration as pe, rollingDeployUnsafeOps as me, runFileBasedMigrations as he, shortFingerprint as D, squashMigrationPlans as ge, unblockLossy as _e } from "@voltro/database/sql";
15
16
  //#region src/cliError.ts
16
- var E = class extends Error {
17
+ var ve = class extends Error {
17
18
  isCliError = !0;
18
19
  code;
19
20
  constructor(e, t = 1) {
20
21
  super(e), this.name = "CliError", this.code = t;
21
22
  }
22
- }, pe = (e) => typeof e == "object" && !!e && e.isCliError === !0, D = /\.migration\.ts$/, O = async (e, t) => {
23
+ }, ye = (e) => typeof e == "object" && !!e && e.isCliError === !0, be = (e) => typeof e != "string", xe = (e, t, n, r) => {
24
+ let i = [], a = new Map(t.map((e) => [e.name, e])), o = new Map(n.map((e) => [e.name, e])), s = new Set(e.leaveUnset ?? []);
25
+ r > 0 && i.push({
26
+ kind: "target-not-empty",
27
+ detail: `'${e.into}' already holds ${r} row(s) — adopt MOVES rows into a table, it does not merge into one`,
28
+ fatal: !0
29
+ });
30
+ let c = [], l = /* @__PURE__ */ new Set();
31
+ for (let [t, n] of Object.entries(e.map)) {
32
+ if (!o.has(t)) {
33
+ i.push({
34
+ kind: "unknown-target-column",
35
+ detail: `'${e.into}' has no column '${t}'`,
36
+ fatal: !0
37
+ });
38
+ continue;
39
+ }
40
+ if (be(n)) {
41
+ c.push({
42
+ target: t,
43
+ source: n.expr,
44
+ raw: !0
45
+ });
46
+ continue;
47
+ }
48
+ if (!a.has(n)) {
49
+ i.push({
50
+ kind: "unknown-source-column",
51
+ detail: `'${e.from}' has no column '${n}' (mapped to '${t}')`,
52
+ fatal: !0
53
+ });
54
+ continue;
55
+ }
56
+ l.add(n), c.push({
57
+ target: t,
58
+ source: n,
59
+ raw: !1
60
+ });
61
+ }
62
+ for (let t of n) t.name in e.map || s.has(t.name) || t.nullable || t.hasDefault || i.push({
63
+ kind: "required-target-unmapped",
64
+ detail: `'${e.into}.${t.name}' is NOT NULL with no default and nothing maps to it`,
65
+ fatal: !0
66
+ });
67
+ for (let n of t) l.has(n.name) || i.push({
68
+ kind: "source-column-unmapped",
69
+ detail: `'${e.from}.${n.name}' is not carried across — its values are lost when the table is dropped`,
70
+ fatal: !1
71
+ });
72
+ let u = t.find((e) => e.idPrefix !== void 0), d = n.find((e) => e.idPrefix !== void 0), f = u !== void 0 && d !== void 0 && u.idPrefix !== d.idPrefix;
73
+ return f && i.push({
74
+ kind: "id-prefix-differs",
75
+ detail: `id prefix '${u.idPrefix}' → '${d.idPrefix}': every row gets a NEW id, so every reference to '${e.from}' elsewhere must be rewritten from the translation table — including ids inside JSON columns`,
76
+ fatal: !1
77
+ }), {
78
+ problems: i,
79
+ assignments: c.sort((e, t) => e.target.localeCompare(t.target)),
80
+ remintIds: f,
81
+ fatal: i.some((e) => e.fatal)
82
+ };
83
+ }, Se = (e, t) => [
84
+ `back up '${e.from}' to a restorable dump`,
85
+ `copy rows: INSERT INTO ${e.into} (${t.assignments.map((e) => e.target).join(", ")}) SELECT … FROM ${e.from}`,
86
+ ...t.remintIds ? ["record old id → new id in a translation table", `rewrite every reference to '${e.from}', JSON columns included`] : [],
87
+ "verify: row counts match and a sample round-trips",
88
+ `drop '${e.from}' — LAST, and only after the verify`
89
+ ], Ce = (e, t, n) => {
90
+ let r = [`adopt · ${e.from} (${n} row(s)) → ${e.into}`, ""];
91
+ for (let e of t.assignments) r.push(` ${e.target.padEnd(24)} ← ${e.raw ? `${e.source} (raw)` : e.source}`);
92
+ t.problems.length > 0 && r.push("");
93
+ for (let e of t.problems) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.detail}`);
94
+ if (r.push(""), t.fatal) return r.push(" REFUSED — fix the problems above. Nothing was written."), r;
95
+ r.push(" would run, in this order:");
96
+ for (let [n, i] of Se(e, t).entries()) r.push(` ${n + 1}. ${i}`);
97
+ return r;
98
+ }, we = (e) => e === "mysql" || e === "mariadb" ? (e) => `\`${e}\`` : e === "mssql" ? (e) => `[${e}]` : (e) => `"${e}"`, O = (e) => `${e}__adopt_snapshot`, Te = (e, t, n, r, i) => {
99
+ let a = we(n), o = O(e.from), s = t.assignments.map((e) => e.target), c = t.assignments.map((e) => e.raw ? e.source : a(e.source));
100
+ return i !== void 0 && (s.unshift(r), c.unshift(i)), {
101
+ snapshot: n === "mssql" ? `SELECT * INTO ${a(o)} FROM ${a(e.from)}` : `CREATE TABLE ${a(o)} AS SELECT * FROM ${a(e.from)}`,
102
+ copy: `INSERT INTO ${a(e.into)} (${s.map(a).join(", ")}) SELECT ${c.join(", ")} FROM ${a(e.from)}`,
103
+ translations: i === void 0 ? [] : [`CREATE TABLE ${a(`${o}__ids`)} (old_id TEXT, new_id TEXT)`],
104
+ verifySource: `SELECT COUNT(*) AS n FROM ${a(e.from)}`,
105
+ verifyTarget: `SELECT COUNT(*) AS n FROM ${a(e.into)}`,
106
+ drop: `DROP TABLE ${a(e.from)}`,
107
+ dropSnapshot: `DROP TABLE ${a(o)}`
108
+ };
109
+ }, k = (e) => {
110
+ let t = e[0];
111
+ if (t === void 0) return 0;
112
+ let n = t.n ?? t.N ?? Object.values(t)[0];
113
+ return Number(n ?? 0);
114
+ }, Ee = async (e, t, n, r, i, a, o = {}) => {
115
+ if (t.fatal) throw Error("adopt: the plan was refused — nothing was run");
116
+ let s = Te(e, t, n, r, i), c = O(e.from), l = k(await a(s.verifySource));
117
+ await a(s.snapshot);
118
+ for (let e of s.translations) await a(e);
119
+ await a(s.copy);
120
+ let u = k(await a(s.verifyTarget));
121
+ return u === l ? o.keepSource === !0 ? {
122
+ copied: u,
123
+ sourceRows: l,
124
+ verified: !0,
125
+ snapshotTable: c,
126
+ sourceDropped: !1
127
+ } : (await a(s.drop), {
128
+ copied: u,
129
+ sourceRows: l,
130
+ verified: !0,
131
+ snapshotTable: c,
132
+ sourceDropped: !0
133
+ }) : {
134
+ copied: u,
135
+ sourceRows: l,
136
+ verified: !1,
137
+ snapshotTable: c,
138
+ sourceDropped: !1
139
+ };
140
+ }, A = /\.migration\.ts$/, j = async (e, t) => {
23
141
  let n = [];
24
142
  for (let r of e) {
25
143
  let e;
26
144
  try {
27
- e = await import(m(r).href);
145
+ e = await import(h(r).href);
28
146
  } catch (e) {
29
147
  t.warn("migration: failed to import", { file: r }, e);
30
148
  continue;
31
149
  }
32
- let i = (e.default && y(e.default) ? e.default : void 0) ?? Object.values(e).find(y);
150
+ let i = (e.default && ne(e.default) ? e.default : void 0) ?? Object.values(e).find(ne);
33
151
  if (!i) {
34
152
  t.warn("migration: no defineMigration export", { file: r });
35
153
  continue;
36
154
  }
37
- i.id !== u(r).replace(/\.migration\.ts$/, "") && t.warn("migration: id does not match filename — id wins for ordering, but rename the file for clarity", {
155
+ i.id !== d(r).replace(/\.migration\.ts$/, "") && t.warn("migration: id does not match filename — id wins for ordering, but rename the file for clarity", {
38
156
  file: r,
39
157
  idInFile: i.id,
40
- idFromFile: u(r).replace(/\.migration\.ts$/, "")
158
+ idFromFile: d(r).replace(/\.migration\.ts$/, "")
41
159
  });
42
- let a = await g.readFile(r, "utf8").catch(() => ""), o = ae("sha256").update(a).digest("hex").slice(0, 16);
160
+ let a = await v.readFile(r, "utf8").catch(() => ""), o = ae("sha256").update(a).digest("hex").slice(0, 16);
43
161
  n.push({
44
162
  file: r,
45
163
  definition: i,
@@ -47,14 +165,14 @@ var E = class extends Error {
47
165
  });
48
166
  }
49
167
  return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
50
- }, k = (e) => {
168
+ }, M = (e) => {
51
169
  let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
52
- return h.runPromise(x(v, n).pipe(h.provide(e)));
53
- }, A = (e) => h.runPromise(h.gen(function* () {
54
- let e = yield* (yield* _.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(h.orElseSucceed(() => []));
170
+ return g.runPromise(S(x, n).pipe(g.provide(e)));
171
+ }, N = (e) => g.runPromise(g.gen(function* () {
172
+ let e = yield* (yield* b.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(g.orElseSucceed(() => []));
55
173
  return new Map(e.map((e) => [e.id, e]));
56
- }).pipe(h.provide(e))), j = (e, t) => h.runPromise(h.gen(function* () {
57
- yield* (yield* _.SqlClient)`
174
+ }).pipe(g.provide(e))), P = (e, t) => g.runPromise(g.gen(function* () {
175
+ yield* (yield* b.SqlClient)`
58
176
  INSERT INTO "_voltro_migrations"
59
177
  (id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
60
178
  VALUES
@@ -69,14 +187,14 @@ var E = class extends Error {
69
187
  "errorStack" = EXCLUDED."errorStack",
70
188
  "updatedAt" = now()
71
189
  `;
72
- }).pipe(h.provide(e))), me = async (e, t, n) => {
190
+ }).pipe(g.provide(e))), De = async (e, t, n) => {
73
191
  if (e.length === 0) return {
74
192
  applied: [],
75
193
  failed: null,
76
194
  skipped: []
77
195
  };
78
- await k(t);
79
- let r = await A(t), i = [], a = [], o = null;
196
+ await M(t);
197
+ let r = await N(t), i = [], a = [], o = null;
80
198
  for (let s of e) {
81
199
  let e = r.get(s.definition.id);
82
200
  if (e && e.status === "applied") {
@@ -97,7 +215,7 @@ var E = class extends Error {
97
215
  name: s.definition.name
98
216
  });
99
217
  let c = Date.now(), l = [];
100
- await j(t, {
218
+ await P(t, {
101
219
  id: s.definition.id,
102
220
  name: s.definition.name,
103
221
  hash: s.hash,
@@ -111,7 +229,7 @@ var E = class extends Error {
111
229
  for (let e of s.definition.steps) {
112
230
  let n = Date.now();
113
231
  try {
114
- await h.runPromise(e.apply.pipe(h.provide(t))), l.push({
232
+ await g.runPromise(e.apply.pipe(g.provide(t))), l.push({
115
233
  stepName: e.name,
116
234
  status: "applied",
117
235
  durationMs: Date.now() - n
@@ -138,7 +256,7 @@ var E = class extends Error {
138
256
  ...a.stack ? { stack: a.stack } : {}
139
257
  }
140
258
  };
141
- o = f, i.push(f), await j(t, {
259
+ o = f, i.push(f), await P(t, {
142
260
  id: s.definition.id,
143
261
  name: s.definition.name,
144
262
  hash: s.hash,
@@ -157,7 +275,7 @@ var E = class extends Error {
157
275
  status: "applied",
158
276
  durationMs: Date.now() - c,
159
277
  steps: l
160
- }), await j(t, {
278
+ }), await P(t, {
161
279
  id: s.definition.id,
162
280
  name: s.definition.name,
163
281
  hash: s.hash,
@@ -177,9 +295,9 @@ var E = class extends Error {
177
295
  failed: o,
178
296
  skipped: a
179
297
  };
180
- }, he = async (e, t, n, r = {}) => {
181
- await k(t);
182
- let i = await A(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
298
+ }, Oe = async (e, t, n, r = {}) => {
299
+ await M(t);
300
+ let i = await N(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
183
301
  if (a.length === 0) return n.info("migration: nothing to roll back"), {
184
302
  applied: [],
185
303
  failed: null,
@@ -197,7 +315,7 @@ var E = class extends Error {
197
315
  for (let n = e.definition.steps.length - 1; n >= 0; n--) {
198
316
  let o = e.definition.steps[n], s = Date.now();
199
317
  try {
200
- await h.runPromise(o.undo.pipe(h.provide(t))), i.push({
318
+ await g.runPromise(o.undo.pipe(g.provide(t))), i.push({
201
319
  stepName: o.name,
202
320
  status: "applied",
203
321
  durationMs: Date.now() - s
@@ -232,7 +350,7 @@ var E = class extends Error {
232
350
  status: "applied",
233
351
  durationMs: Date.now() - r,
234
352
  steps: i
235
- }), await j(t, {
353
+ }), await P(t, {
236
354
  id: e.definition.id,
237
355
  name: e.definition.name,
238
356
  hash: e.hash,
@@ -248,9 +366,9 @@ var E = class extends Error {
248
366
  failed: c,
249
367
  skipped: []
250
368
  };
251
- }, M = async (e, t) => {
252
- await k(t);
253
- let n = await A(t);
369
+ }, F = async (e, t) => {
370
+ await M(t);
371
+ let n = await N(t);
254
372
  return e.map((e) => {
255
373
  let t = n.get(e.definition.id);
256
374
  return t ? {
@@ -267,7 +385,7 @@ var E = class extends Error {
267
385
  hashMatches: !0
268
386
  };
269
387
  });
270
- }, N = (e) => {
388
+ }, I = (e) => {
271
389
  if (!e.failed) return "";
272
390
  let t = e.failed, n = [
273
391
  `✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
@@ -280,7 +398,7 @@ var E = class extends Error {
280
398
  ""
281
399
  ];
282
400
  return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
283
- }, P = ee({ scope: "voltro:db" }), ge = /* @__PURE__ */ new Set([
401
+ }, L = _({ scope: "voltro:db" }), ke = /* @__PURE__ */ new Set([
284
402
  "node_modules",
285
403
  "dist",
286
404
  "build",
@@ -288,17 +406,17 @@ var E = class extends Error {
288
406
  ".vite",
289
407
  ".next",
290
408
  ".git"
291
- ]), F = async (e, t) => {
409
+ ]), R = async (e, t) => {
292
410
  let n = [], r = async (e) => {
293
- let i = await g.readdir(e, { withFileTypes: !0 }).catch(() => []);
411
+ let i = await v.readdir(e, { withFileTypes: !0 }).catch(() => []);
294
412
  for (let a of i) {
295
- if (ge.has(a.name)) continue;
296
- let i = d(e, a.name);
413
+ if (ke.has(a.name)) continue;
414
+ let i = f(e, a.name);
297
415
  a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
298
416
  }
299
417
  };
300
418
  return await r(e), n;
301
- }, I = (e) => p(e.find((e) => !e.startsWith("-")) ?? process.cwd()), L = () => {
419
+ }, z = (e) => m(e.find((e) => !e.startsWith("-")) ?? process.cwd()), B = () => {
302
420
  let e = process.env.DB_MAX_CONNECTIONS ?? process.env.PG_MAX_CONNECTIONS, t = e !== void 0 && e !== "" ? Number(e) : NaN, n = Number.isInteger(t) && t > 0 ? { maxConnections: t } : {}, r = process.env.DB_SCHEMA, i = r && r.length > 0 ? { schema: r } : {}, a = process.env.DB_DIRECT_URL ?? process.env.DB_MIGRATE_URL ?? process.env.DB_URL ?? process.env.DB_PRIMARY_URL;
303
421
  return a ? {
304
422
  url: a,
@@ -313,63 +431,63 @@ var E = class extends Error {
313
431
  ...n,
314
432
  ...i
315
433
  };
316
- }, R = (e) => {
434
+ }, V = (e) => {
317
435
  let t = e;
318
436
  if (typeof t != "object" || !t) return !1;
319
437
  if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
320
438
  let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
321
439
  return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
322
- }, z = "Live-DB introspection got a corrupt / misaligned response from the database. This happens when `voltro db plan` / `apply` / `drift` / `migrate` runs through a connection POOLER (Supabase Supavisor / PgBouncer) on a LARGE schema — the pooler mis-frames a big catalog response. The introspection already batches by table to keep responses small; if a batch still trips it:\n 1. Lower the batch: set `VOLTRO_INTROSPECT_BATCH` to a smaller number of tables (default 20 — try 5 or 1). This keeps `db plan` on the normal pooler, no direct connection needed.\n 2. Or use a DIRECT (non-pooler) connection: set `DB_DIRECT_URL` (or `DB_MIGRATE_URL`) to the direct database URL — the runtime keeps `DB_URL`, so only the migration path is affected. (Supabase: direct = `db.<project-ref>.supabase.co` [IPv6-only on newer projects]; pooler = `aws-0-<region>.pooler.supabase.com`.)", B = () => {
440
+ }, H = "Live-DB introspection got a corrupt / misaligned response from the database. This happens when `voltro db plan` / `apply` / `drift` / `migrate` runs through a connection POOLER (Supabase Supavisor / PgBouncer) on a LARGE schema — the pooler mis-frames a big catalog response. The introspection already batches by table to keep responses small; if a batch still trips it:\n 1. Lower the batch: set `VOLTRO_INTROSPECT_BATCH` to a smaller number of tables (default 20 — try 5 or 1). This keeps `db plan` on the normal pooler, no direct connection needed.\n 2. Or use a DIRECT (non-pooler) connection: set `DB_DIRECT_URL` (or `DB_MIGRATE_URL`) to the direct database URL — the runtime keeps `DB_URL`, so only the migration path is affected. (Supabase: direct = `db.<project-ref>.supabase.co` [IPv6-only on newer projects]; pooler = `aws-0-<region>.pooler.supabase.com`.)", U = () => {
323
441
  let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
324
- if (e === "memory") throw new E("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
442
+ if (e === "memory") throw new ve("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
325
443
  return e;
326
- }, V = async () => (await r(B())).makeSqlLayer(L()), _e = async (e) => {
327
- let t = I(e), n = e.includes("--dry-run"), r = await O(await F(t, D), P);
328
- if (r.length === 0) return P.info("no migrations found", { root: t }), 0;
444
+ }, W = async () => (await r(U())).makeSqlLayer(B()), Ae = async (e) => {
445
+ let t = z(e), n = e.includes("--dry-run"), r = await j(await R(t, A), L);
446
+ if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
329
447
  if (n) {
330
448
  let e = /* @__PURE__ */ new Set();
331
449
  try {
332
- let t = await M(r, await V());
450
+ let t = await F(r, await W());
333
451
  e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
334
452
  } catch {
335
- P.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
453
+ L.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
336
454
  }
337
455
  let t = r.filter((t) => !e.has(t.definition.id));
338
- if (t.length === 0) return P.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
339
- P.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
456
+ if (t.length === 0) return L.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
457
+ L.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
340
458
  for (let e of t) console.log(` pending ${e.definition.id}`);
341
459
  return 0;
342
460
  }
343
- let i = await me(r, await V(), P);
344
- return i.failed ? (process.stderr.write(N(i) + "\n"), 1) : (P.info("migrate complete", {
461
+ let i = await De(r, await W(), L);
462
+ return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("migrate complete", {
345
463
  applied: i.applied.length,
346
464
  skipped: i.skipped.length
347
465
  }), 0);
348
- }, ve = async (e) => {
349
- let t = I(e), n = c(e, "--to"), r = await O(await F(t, D), P);
350
- if (r.length === 0) return P.info("no migrations found", { root: t }), 0;
351
- let i = await he(r, await V(), P, n ? { to: n } : {});
352
- return i.failed ? (process.stderr.write(N(i) + "\n"), 1) : (P.info("rollback complete", { reverted: i.applied.length }), 0);
353
- }, ye = async (e) => {
354
- let t = await O(await F(I(e), D), P);
466
+ }, je = async (e) => {
467
+ let t = z(e), n = l(e, "--to"), r = await j(await R(t, A), L);
468
+ if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
469
+ let i = await Oe(r, await W(), L, n ? { to: n } : {});
470
+ return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("rollback complete", { reverted: i.applied.length }), 0);
471
+ }, Me = async (e) => {
472
+ let t = await j(await R(z(e), A), L);
355
473
  if (t.length === 0) return console.log("no migrations found"), 0;
356
- let n = await M(t, await V());
474
+ let n = await F(t, await W());
357
475
  for (let e of n) {
358
476
  let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
359
477
  console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
360
478
  }
361
479
  return 0;
362
- }, be = async (e) => {
363
- let t = (c(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
364
- if (t === "memory") return P.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), P.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
365
- store: new ne(),
480
+ }, Ne = async (e) => {
481
+ let t = (l(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
482
+ if (t === "memory") return L.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), L.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
483
+ store: new ee(),
366
484
  dispose: async () => {},
367
485
  dialectId: "memory"
368
486
  };
369
487
  let n = await r(t);
370
- P.info(`voltro db seed: target store=${n.id} — rows will persist`);
488
+ L.info(`voltro db seed: target store=${n.id} — rows will persist`);
371
489
  let i = await n.makeStore({
372
- sqlLayer: n.makeSqlLayer(L()),
490
+ sqlLayer: n.makeSqlLayer(B()),
373
491
  changeStrategy: "inline"
374
492
  }), a = i;
375
493
  return {
@@ -379,77 +497,77 @@ var E = class extends Error {
379
497
  },
380
498
  dialectId: n.id
381
499
  };
382
- }, xe = async (r) => {
383
- let a = I(r), o = c(r, "--id"), s = c(r, "--lifecycle"), l = await t(await F(a, i), P);
384
- if (l.length === 0) return P.info("no seeds found", { root: a }), 0;
385
- let { store: u, dispose: d, dialectId: f } = await be(r);
500
+ }, Pe = async (r) => {
501
+ let a = z(r), o = l(r, "--id"), s = l(r, "--lifecycle"), c = await t(await R(a, i), L);
502
+ if (c.length === 0) return L.info("no seeds found", { root: a }), 0;
503
+ let { store: u, dispose: d, dialectId: f } = await Ne(r);
386
504
  try {
387
- return o ? +((await e(l, o, u, P)).status === "failed") : +((await n((s ? l.filter((e) => e.definition.lifecycle === s) : l.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
505
+ return o ? +((await e(c, o, u, L)).status === "failed") : +((await n((s ? c.filter((e) => e.definition.lifecycle === s) : c.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
388
506
  ...e,
389
507
  definition: {
390
508
  ...e.definition,
391
509
  lifecycle: "boot"
392
510
  }
393
- })), u, P, { force: !0 })).filter((e) => e.status === "failed").length > 0);
511
+ })), u, L, { force: !0 })).filter((e) => e.status === "failed").length > 0);
394
512
  } finally {
395
513
  await d().catch((e) => {
396
- P.warn("voltro db seed: store dispose failed", { error: String(e) });
514
+ L.warn("voltro db seed: store dispose failed", { error: String(e) });
397
515
  });
398
516
  }
399
- }, Se = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", H = async (e) => {
400
- let t = await F(e, o), n = [];
517
+ }, Fe = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", G = async (e) => {
518
+ let t = await R(e, s), n = [];
401
519
  for (let r of t) {
402
520
  let t;
403
521
  try {
404
- t = await import(m(r).href);
522
+ t = await import(h(r).href);
405
523
  } catch (t) {
406
- P.warn("db: failed to import schema file", {
407
- file: f(e, r),
524
+ L.warn("db: failed to import schema file", {
525
+ file: p(e, r),
408
526
  error: String(t)
409
527
  });
410
528
  continue;
411
529
  }
412
- for (let e of Object.values(t)) Se(e) && n.push(e);
530
+ for (let e of Object.values(t)) Fe(e) && n.push(e);
413
531
  }
414
532
  return n;
415
- }, U = (e) => {
533
+ }, K = (e) => {
416
534
  let t = new Map(e.map((e) => [e.tableName, e]));
417
- t.has("actors") || t.set("actors", re);
535
+ t.has("actors") || t.set("actors", te);
418
536
  let n = t.get("actors"), r = t.get("tenants");
419
537
  return ie({
420
538
  actors: () => n,
421
539
  ...r === void 0 ? {} : { tenants: () => r }
422
540
  }), [...t.values()];
423
- }, Ce = (e) => {
541
+ }, Ie = (e) => {
424
542
  let t = /* @__PURE__ */ new Map();
425
543
  for (let n of e) {
426
544
  let e = n.tableName;
427
545
  e !== void 0 && !t.has(e) && t.set(e, n);
428
546
  }
429
547
  return [...t.values()];
430
- }, W = (e) => {
431
- let t = se(process.env.VOLTRO_DESTRUCTIVE_OK);
548
+ }, q = (e) => {
549
+ let t = le(process.env.VOLTRO_DESTRUCTIVE_OK);
432
550
  if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
433
551
  plan: e,
434
552
  acknowledged: !1
435
553
  };
436
- let n = fe(e, t);
554
+ let n = _e(e, t);
437
555
  return {
438
556
  plan: n,
439
557
  acknowledged: !n.operations.some((e) => e.blocked)
440
558
  };
441
- }, G = async (e, t) => h.runPromise(t.pipe(h.provide(e))), K = async (e) => {
559
+ }, J = async (e, t) => g.runPromise(t.pipe(g.provide(e))), Y = async (e) => {
442
560
  try {
443
- return await G(e, _.SqlClient.pipe(h.flatMap(ce)));
561
+ return await J(e, b.SqlClient.pipe(g.flatMap(T)));
444
562
  } catch (e) {
445
563
  let t = String(e?.message ?? e);
446
- if (R(e) || /connection error/i.test(t)) {
447
- let n = /* @__PURE__ */ Error(`${z}\n\nOriginal error: ${t}`);
564
+ if (V(e) || /connection error/i.test(t)) {
565
+ let n = /* @__PURE__ */ Error(`${H}\n\nOriginal error: ${t}`);
448
566
  throw n.cause = e, n;
449
567
  }
450
568
  throw e;
451
569
  }
452
- }, we = {
570
+ }, Le = {
453
571
  safe: "\x1B[32m✓\x1B[0m",
454
572
  "needs-default": "\x1B[32m⊕\x1B[0m",
455
573
  "needs-backfill": "\x1B[33m⊕\x1B[0m",
@@ -457,7 +575,7 @@ var E = class extends Error {
457
575
  lossy: "\x1B[31m✗\x1B[0m",
458
576
  "online-required": "\x1B[36m◷\x1B[0m",
459
577
  "multi-step": "\x1B[35m⇉\x1B[0m"
460
- }, Te = (e) => {
578
+ }, Re = (e) => {
461
579
  let t = e.op, n;
462
580
  switch (t.kind) {
463
581
  case "create-table":
@@ -521,147 +639,201 @@ var E = class extends Error {
521
639
  n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
522
640
  break;
523
641
  }
524
- let r = we[e.classification], i = e.reason ? ` # ${e.reason}` : "", a = e.blocked ? `\n ! ${e.blocked.fix}` : "";
642
+ let r = Le[e.classification], i = e.reason ? ` # ${e.reason}` : "", a = e.blocked ? `\n ! ${e.blocked.fix}` : "";
525
643
  return ` ${r} ${n}${i}${a}`;
526
- }, q = (e) => {
644
+ }, ze = async (e, t) => {
645
+ let n = e.operations.filter((e) => e.classification === "needs-default" && e.op.kind === "add-column");
646
+ if (n.length === 0) return;
647
+ let r = [];
648
+ for (let e of n) {
649
+ let n = e.op, i = n.table, a = n.column?.name;
650
+ if (i === void 0 || a === void 0) continue;
651
+ let o = 0;
652
+ try {
653
+ let e = await J(t, b.SqlClient.pipe(g.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${re(i, U())}`))));
654
+ o = Number(Object.values(e[0] ?? { n: 0 })[0] ?? 0);
655
+ } catch {
656
+ continue;
657
+ }
658
+ o !== 0 && r.push(` ${o.toLocaleString("en-US")} existing row(s) in ${i} will get the default for ${a}`);
659
+ }
660
+ if (r.length !== 0) {
661
+ console.log("\n existing rows that a default will fill:");
662
+ for (let e of r) console.log(e);
663
+ console.log(" (the shape is correct either way — this is the VALUE a state diff cannot know)");
664
+ }
665
+ }, X = async (e, t) => {
666
+ if (U() !== "postgres") return;
667
+ let n = oe, r;
668
+ try {
669
+ r = await J(t, b.SqlClient.pipe(g.flatMap((t) => ue(t, e, n))));
670
+ } catch {
671
+ return;
672
+ }
673
+ let i = fe({
674
+ tables: e,
675
+ dialect: "postgres",
676
+ missing: r.missing,
677
+ stale: r.stale,
678
+ channel: n
679
+ });
680
+ if (i.length === 0) return;
681
+ L.info(`db apply: installing change triggers on ${r.missing.length} table(s)` + (r.stale.length > 0 ? `, removing ${r.stale.length} stale one(s)` : ""));
682
+ let a = 0;
683
+ for (let e of i) await J(t, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), a += 1;
684
+ L.info(`db apply: change triggers converged (${a} statement(s))`, {
685
+ installed: r.missing.length,
686
+ removed: r.stale.length
687
+ });
688
+ }, Z = (e) => {
527
689
  let t = e.summary, n = e.operations.length;
528
690
  if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
529
691
  console.log(" (schema is up to date)\n");
530
692
  return;
531
693
  }
532
- for (let t of e.operations) console.log(Te(t));
533
- console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`);
534
- }, J = (e) => {
535
- let t = c(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
694
+ for (let t of e.operations) console.log(Re(t));
695
+ console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`), Be(e);
696
+ }, Be = (e) => {
697
+ let t = me(e.operations);
698
+ if (t.length !== 0) {
699
+ console.log(` ⚠ ${t.length} operation(s) UNSAFE under a rolling deploy`), console.log(" (old + new instances overlap → old code breaks against the new schema):");
700
+ for (let e of t) console.log(` • ${e.op.op.kind}: ${e.reason}`), console.log(` → ${e.remedy}`);
701
+ console.log(" Deploy with a maintenance window / scale-to-zero, or split into"), console.log(" expand → deploy → contract. See docs: deployment/production-hardening.\n");
702
+ }
703
+ }, Ve = (e) => {
704
+ let t = l(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
536
705
  if (t === void 0) return;
537
706
  let n = Number(t);
538
707
  if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
539
708
  return n;
540
- }, Y = async (e) => {
541
- let t = await H(e);
709
+ }, Q = async (e) => {
710
+ let t = await G(e);
542
711
  if (t.length === 0) return;
543
- let n = await a({ root: e });
544
- return U(Ce([
712
+ let n = await o({ root: e });
713
+ return K(Ie([
545
714
  ...t,
546
- ...v,
715
+ ...x,
547
716
  ...n
548
717
  ]));
549
- }, Ee = async (e) => {
550
- let t = J(e), n = I(e), r = await Y(n);
551
- if (r === void 0) return P.warn("db plan: no schema files found", {
718
+ }, He = async (e) => {
719
+ let t = Ve(e), n = z(e), r = await Q(n);
720
+ if (r === void 0) return L.warn("db plan: no schema files found", {
552
721
  root: n,
553
722
  hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
554
723
  }), 1;
555
- let i = c(e, "--against");
724
+ let i = l(e, "--against");
556
725
  if (i) {
557
- let n = i, a = c(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, o = { accept: "application/json" };
726
+ let n = i, a = l(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, o = { accept: "application/json" };
558
727
  a && (o.authorization = `Bearer ${a}`);
559
728
  try {
560
729
  let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers: o });
561
- if (!i.ok) return P.error(`db plan --against: remote returned ${i.status}`, { url: e }), 1;
730
+ if (!i.ok) return L.error(`db plan --against: remote returned ${i.status}`, { url: e }), 1;
562
731
  let a = (await i.json()).drift?.liveSnapshot;
563
- if (!a) return P.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
564
- let s = w({
732
+ if (!a) return L.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
733
+ let s = E({
565
734
  declared: r,
566
735
  live: a,
567
- dialect: B(),
568
- ...C(),
736
+ dialect: U(),
737
+ ...w(),
569
738
  ...t === void 0 ? {} : { onlineAfter: t }
570
739
  });
571
- return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), q(s), s.summary.blocked > 0 ? 2 : 0;
740
+ return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), Z(s), s.summary.blocked > 0 ? 2 : 0;
572
741
  } catch (e) {
573
- return P.error(`db plan --against: ${e.message}`), 1;
742
+ return L.error(`db plan --against: ${e.message}`), 1;
574
743
  }
575
744
  }
576
- let a = w({
745
+ let a = E({
577
746
  declared: r,
578
- live: await K(await V()),
579
- dialect: B(),
580
- ...C(),
747
+ live: await Y(await W()),
748
+ dialect: U(),
749
+ ...w(),
581
750
  ...t === void 0 ? {} : { onlineAfter: t }
582
751
  });
583
- return e.includes("--json") ? (te(1, `${JSON.stringify(a, null, 2)}\n`), a.summary.blocked > 0 ? 2 : 0) : (q(a), a.summary.blocked > 0 ? 2 : 0);
584
- }, De = async (e, t, n) => {
752
+ return e.includes("--json") ? (y(1, `${JSON.stringify(a, null, 2)}\n`), a.summary.blocked > 0 ? 2 : 0) : (Z(a), a.summary.blocked > 0 ? 2 : 0);
753
+ }, Ue = async (e, t, n) => {
585
754
  let r;
586
755
  try {
587
- let t = await g.readFile(p(e), "utf8");
756
+ let t = await v.readFile(m(e), "utf8");
588
757
  r = JSON.parse(t);
589
758
  } catch (t) {
590
- return P.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
591
- }
592
- if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return P.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
593
- let i = await Y(n);
594
- if (i === void 0) return P.warn("db apply --plan: no schema files found", { root: n }), 1;
595
- let a = await V();
596
- await G(a, x(v, B()));
597
- let o = w({
759
+ return L.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
760
+ }
761
+ if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return L.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
762
+ let i = await Q(n);
763
+ if (i === void 0) return L.warn("db apply --plan: no schema files found", { root: n }), 1;
764
+ let a = await W();
765
+ await J(a, S(x, U()));
766
+ let o = await J(a, b.SqlClient.pipe(g.flatMap((e) => de(e, n)))).catch(() => []);
767
+ if (o.length > 0) return L.error(`db apply --plan: refusing — ${o.length} pending file-based migration(s).`), console.error(` ${o.join("\n ")}`), console.error(""), console.error(" These perform the changes a state diff cannot infer, so they change the shape"), console.error(" this plan was computed against. Apply them and regenerate the plan:"), console.error(""), console.error(" voltro db files ."), console.error(" voltro db plan --json > plan.json"), console.error(" voltro db apply --plan plan.json"), 1;
768
+ let s = E({
598
769
  declared: i,
599
- live: await K(a),
600
- dialect: B(),
601
- ...C()
770
+ live: await Y(a),
771
+ dialect: U(),
772
+ ...w()
602
773
  });
603
- if (o.fromFingerprint !== r.fromFingerprint) return P.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${r.fromFingerprint.slice(0, 16)} live=${o.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
604
- if (o.toFingerprint !== r.toFingerprint) return P.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${r.toFingerprint.slice(0, 16)} declared=${o.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
605
- let s = W(o), l = s.plan;
606
- if (l.summary.blocked > 0 && !s.acknowledged) return q(l), P.error(`db apply --plan: refusing — ${o.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (P.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), P.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
607
- if (s.acknowledged && P.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), l.operations.length === 0) return P.info("db apply --plan: schema is up to date — nothing to apply"), 0;
608
- q(l);
609
- let u = (process.env.NODE_ENV ?? "dev").toLowerCase(), d = c(t, "--note"), f = await G(a, _.SqlClient.pipe(h.flatMap((e) => b(e, l, {
774
+ if (s.fromFingerprint !== r.fromFingerprint) return L.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${r.fromFingerprint.slice(0, 16)} live=${s.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
775
+ if (s.toFingerprint !== r.toFingerprint) return L.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${r.toFingerprint.slice(0, 16)} declared=${s.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
776
+ let c = q(s), u = c.plan;
777
+ if (u.summary.blocked > 0 && !c.acknowledged) return Z(u), L.error(`db apply --plan: refusing — ${s.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (L.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), L.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
778
+ if (c.acknowledged && L.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), u.operations.length === 0) return L.info("db apply --plan: schema is up to date — nothing to apply"), await X(i, a), 0;
779
+ Z(u);
780
+ let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = l(t, "--note"), p = await J(a, b.SqlClient.pipe(g.flatMap((e) => se(e, u, {
610
781
  declared: i,
611
782
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
612
- environment: u === "production" ? "prod" : u === "staging" ? "staging" : "dev",
783
+ environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
613
784
  source: "file",
614
- ...d ? { notes: d } : {},
615
- replan: () => h.promise(() => K(a)).pipe(h.map((e) => w({
785
+ ...f ? { notes: f } : {},
786
+ replan: () => g.promise(() => Y(a)).pipe(g.map((e) => E({
616
787
  declared: i,
617
788
  live: e,
618
- dialect: B(),
619
- ...C()
789
+ dialect: U(),
790
+ ...w()
620
791
  })))
621
792
  }))));
622
- return P.info(`db apply --plan: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
623
- id: f.id,
624
- fingerprint: f.fingerprint.slice(0, 16)
625
- }), 0;
626
- }, Oe = async (e) => {
627
- let t = I(e), n = J(e), r = c(e, "--plan");
628
- if (r) return De(r, e, t);
629
- if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return P.error("voltro db apply: refusing — auto-apply on prod is not allowed."), console.error("In production, schema changes go through `voltro db plan --json > plan.json`"), console.error("then `voltro db apply --plan plan.json` as an explicit step in your deploy pipeline."), 3;
630
- let i = await Y(t);
631
- if (i === void 0) return P.warn("db apply: no schema files found", { root: t }), 1;
632
- let a = await V();
633
- await G(a, x(v, B()));
634
- let o = w({
793
+ return L.info(`db apply --plan: applied ${p.appliedOps} op(s) in ${p.durationMs}ms`, {
794
+ id: p.id,
795
+ fingerprint: p.fingerprint.slice(0, 16)
796
+ }), await X(i, a), 0;
797
+ }, We = async (e) => {
798
+ let t = z(e), n = Ve(e), r = l(e, "--plan");
799
+ if (r) return Ue(r, e, t);
800
+ if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return L.error("voltro db apply: refusing — auto-apply on prod is not allowed."), console.error("In production, schema changes go through `voltro db plan --json > plan.json`"), console.error("then `voltro db apply --plan plan.json` as an explicit step in your deploy pipeline."), 3;
801
+ let i = await Q(t);
802
+ if (i === void 0) return L.warn("db apply: no schema files found", { root: t }), 1;
803
+ let a = await W(), o = await $e(t, a);
804
+ if ("failed" in o) return L.error("db apply: a file-based migration FAILED — the schema diff was NOT run"), console.error(o.failed), console.error("Fix the migration and re-run. `voltro db files` runs them alone."), 1;
805
+ await J(a, S(x, U()));
806
+ let s = E({
635
807
  declared: i,
636
- live: await K(a),
637
- dialect: B(),
638
- ...C(),
808
+ live: await Y(a),
809
+ dialect: U(),
810
+ ...w(),
639
811
  ...n === void 0 ? {} : { onlineAfter: n }
640
812
  });
641
- q(o);
642
- let s = W(o), l = s.plan;
643
- if (l.summary.blocked > 0 && !s.acknowledged) return P.error(`db apply: refusing — ${l.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (P.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), P.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
644
- if (s.acknowledged && P.warn(`db apply: proceeding with ${o.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), l.operations.length === 0) return P.info("db apply: schema is up to date — nothing to apply"), await Ne(a), c(e, "--note") !== void 0 && (P.warn("db apply: --note was ignored — an empty plan writes no history row to attach it to."), console.error("To record that the CURRENT live schema is the accepted baseline, use `voltro db drift --accept`.")), 0;
645
- let u = c(e, "--note"), d = await G(a, _.SqlClient.pipe(h.flatMap((e) => b(e, l, {
813
+ Z(s), await ze(s, a);
814
+ let c = q(s), u = c.plan;
815
+ if (u.summary.blocked > 0 && !c.acknowledged) return L.error(`db apply: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (L.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), L.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
816
+ if (c.acknowledged && L.warn(`db apply: proceeding with ${s.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), u.operations.length === 0) return L.info("db apply: schema is up to date — nothing to apply"), await Ye(a), l(e, "--note") !== void 0 && (L.warn("db apply: --note was ignored — an empty plan writes no history row to attach it to."), console.error("To record that the CURRENT live schema is the accepted baseline, use `voltro db drift --accept`.")), await X(i, a), 0;
817
+ let d = l(e, "--note"), f = await J(a, b.SqlClient.pipe(g.flatMap((e) => se(e, u, {
646
818
  declared: i,
647
819
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
648
820
  environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
649
821
  source: "auto-diff",
650
- ...u ? { notes: u } : {},
651
- replan: () => h.promise(() => K(a)).pipe(h.map((e) => w({
822
+ ...d ? { notes: d } : {},
823
+ replan: () => g.promise(() => Y(a)).pipe(g.map((e) => E({
652
824
  declared: i,
653
825
  live: e,
654
- dialect: B(),
655
- ...C(),
826
+ dialect: U(),
827
+ ...w(),
656
828
  ...n === void 0 ? {} : { onlineAfter: n }
657
829
  })))
658
830
  }))));
659
- return P.info(`db apply: applied ${d.appliedOps} op(s) in ${d.durationMs}ms`, {
660
- id: d.id,
661
- fingerprint: d.fingerprint.slice(0, 16)
662
- }), 0;
663
- }, ke = async (e) => {
664
- let t = Math.max(1, Number(c(e, "--limit") ?? "20")), n = await G(await V(), _.SqlClient.pipe(h.flatMap((e) => e`
831
+ return L.info(`db apply: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
832
+ id: f.id,
833
+ fingerprint: f.fingerprint.slice(0, 16)
834
+ }), await X(i, a), 0;
835
+ }, Ge = async (e) => {
836
+ let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await J(await W(), b.SqlClient.pipe(g.flatMap((e) => e`
665
837
  SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
666
838
  ${e("appliedBy")}, ${e("environment")}, ${e("source")},
667
839
  ${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
@@ -669,25 +841,25 @@ var E = class extends Error {
669
841
  ORDER BY ${e("appliedAt")} DESC
670
842
  LIMIT ${t}
671
843
  `)));
672
- if (n.length === 0) return P.info("db plans: no planner-based migrations applied yet"), console.log("Run `voltro db apply` after a schema change to start a plan history."), 0;
844
+ if (n.length === 0) return L.info("db plans: no planner-based migrations applied yet"), console.log("Run `voltro db apply` after a schema change to start a plan history."), 0;
673
845
  console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
674
846
  for (let e of n) {
675
847
  let t = 0;
676
848
  try {
677
- t = JSON.parse(X(e.operations)).length;
849
+ t = JSON.parse(Xe(e.operations)).length;
678
850
  } catch {}
679
- console.log(` ${e.id} fp=${T(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${Z(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
851
+ console.log(` ${e.id} fp=${D(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${$(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
680
852
  }
681
853
  return console.log(), 0;
682
- }, Ae = async (e) => {
854
+ }, Ke = async (e) => {
683
855
  let t = e[0];
684
856
  if (!t) return console.error("voltro db restore-snapshot <plan-id>"), console.error(" Renames soft-dropped columns/tables from a plan back to their original names."), console.error(" Requires the plan to have been applied with VOLTRO_SOFT_DROP=1."), 2;
685
- let n = await V(), r = await G(n, _.SqlClient.pipe(h.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
857
+ let n = await W(), r = await J(n, b.SqlClient.pipe(g.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
686
858
  FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
687
859
  if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
688
860
  let i;
689
861
  try {
690
- i = JSON.parse(X(r[0].operations));
862
+ i = JSON.parse(Xe(r[0].operations));
691
863
  } catch {
692
864
  return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
693
865
  }
@@ -701,7 +873,7 @@ var E = class extends Error {
701
873
  if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
702
874
  let s = 0;
703
875
  for (let e of a) {
704
- let t = await G(n, _.SqlClient.pipe(h.flatMap((t) => t`
876
+ let t = await J(n, b.SqlClient.pipe(g.flatMap((t) => t`
705
877
  SELECT column_name FROM information_schema.columns
706
878
  WHERE table_schema = 'public'
707
879
  AND table_name = ${e.table}
@@ -713,11 +885,11 @@ var E = class extends Error {
713
885
  continue;
714
886
  }
715
887
  let r = t[0].column_name;
716
- await G(n, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table}" RENAME COLUMN "${r}" TO "${e.column}"`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
888
+ await J(n, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table}" RENAME COLUMN "${r}" TO "${e.column}"`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
717
889
  }
718
890
  let c = 0;
719
891
  for (let e of o) {
720
- let t = await G(n, _.SqlClient.pipe(h.flatMap((t) => t`
892
+ let t = await J(n, b.SqlClient.pipe(g.flatMap((t) => t`
721
893
  SELECT table_name FROM information_schema.tables
722
894
  WHERE table_schema = 'public'
723
895
  AND table_name LIKE ${e.table + "__dropped_%"}
@@ -728,57 +900,57 @@ var E = class extends Error {
728
900
  continue;
729
901
  }
730
902
  let r = t[0].table_name;
731
- await G(n, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${r}" RENAME TO "${e.table}"`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
903
+ await J(n, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${r}" RENAME TO "${e.table}"`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
732
904
  }
733
905
  return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
734
- }, je = async (e) => {
735
- let t = c(e, "--before");
906
+ }, qe = async (e) => {
907
+ let t = l(e, "--before");
736
908
  if (!t) return console.error("voltro db gc-snapshots --before <date> [--dry-run]"), console.error(" Permanently drops soft-dropped columns/tables (<name>__dropped_<ts>) older than <date>."), console.error(" <date> is ISO — e.g. 2026-01-01 or 2026-01-01T00:00:00Z. NOT reversible."), 2;
737
909
  let n = t.replace(/[^0-9]/g, "");
738
910
  if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
739
- let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await V(), o = await G(a, _.SqlClient.pipe(h.flatMap((e) => e`
911
+ let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await W(), o = await J(a, b.SqlClient.pipe(g.flatMap((e) => e`
740
912
  SELECT table_name, column_name FROM information_schema.columns
741
913
  WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
742
- `))), s = await G(a, _.SqlClient.pipe(h.flatMap((e) => e`
914
+ `))), s = await J(a, b.SqlClient.pipe(g.flatMap((e) => e`
743
915
  SELECT table_name FROM information_schema.tables
744
916
  WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
745
- `))), l = (e) => {
917
+ `))), c = (e) => {
746
918
  let t = /__dropped_(\d{14})$/.exec(e);
747
919
  return t ? t[1] : null;
748
920
  }, u = 0;
749
921
  for (let e of o) {
750
- let t = l(e.column_name);
751
- t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await G(a, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
922
+ let t = c(e.column_name);
923
+ t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
752
924
  }
753
925
  for (let e of s) {
754
- let t = l(e.table_name);
755
- t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await G(a, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
926
+ let t = c(e.table_name);
927
+ t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
756
928
  }
757
929
  return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
758
- }, Me = async (e) => {
759
- let t = c(e, "--before");
930
+ }, Je = async (e) => {
931
+ let t = l(e, "--before");
760
932
  if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
761
933
  let n = t;
762
934
  if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
763
- let r = c(e, "--note"), i = I(e), a = await Y(i);
764
- if (a === void 0) return P.warn("db squash: no schema files found", { root: i }), 1;
765
- let o = S(oe(a)), s = await V();
935
+ let r = l(e, "--note"), i = z(e), a = await Q(i);
936
+ if (a === void 0) return L.warn("db squash: no schema files found", { root: i }), 1;
937
+ let o = C(ce(a)), s = await W();
766
938
  try {
767
- let e = await G(s, de({
939
+ let e = await J(s, ge({
768
940
  before: n,
769
941
  fingerprint: o,
770
942
  appliedBy: process.env.USER ?? "cli",
771
943
  environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
772
944
  ...r === void 0 ? {} : { note: r }
773
945
  }));
774
- return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${T(e.snapshotFingerprint)}\n`), 0;
946
+ return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${D(e.snapshotFingerprint)}\n`), 0;
775
947
  } catch (e) {
776
948
  return console.error(`squash refused: ${e.message}`), 1;
777
949
  }
778
- }, Ne = async (e) => {
950
+ }, Ye = async (e) => {
779
951
  try {
780
- let t = S(await K(e));
781
- await G(e, _.SqlClient.pipe(h.flatMap((e) => e`
952
+ let t = C(await Y(e));
953
+ await J(e, b.SqlClient.pipe(g.flatMap((e) => e`
782
954
  UPDATE ${e("_voltro_migration_plans")}
783
955
  SET ${e("liveFingerprint")} = ${t}
784
956
  WHERE ${e("id")} = (
@@ -790,7 +962,7 @@ var E = class extends Error {
790
962
  AND ${e("liveFingerprint")} IS NULL
791
963
  `)));
792
964
  } catch {}
793
- }, X = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), Z = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), Q = (e) => e === void 0 ? {
965
+ }, Xe = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), $ = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), Ze = (e) => e === void 0 ? {
794
966
  accept: !1,
795
967
  reason: "no-declared-schema"
796
968
  } : e > 0 ? {
@@ -799,38 +971,38 @@ var E = class extends Error {
799
971
  } : {
800
972
  accept: !0,
801
973
  reason: "converged"
802
- }, Pe = async (e) => {
803
- let t = e.includes("--accept"), n = await V(), r = await G(n, _.SqlClient.pipe(h.flatMap((e) => e`
974
+ }, Qe = async (e) => {
975
+ let t = e.includes("--accept"), n = await W(), r = await J(n, b.SqlClient.pipe(g.flatMap((e) => e`
804
976
  SELECT * FROM ${e("_voltro_migration_plans")}
805
977
  ORDER BY ${e("appliedAt")} DESC
806
978
  LIMIT 1
807
- `))), i = await K(n), a = S(i);
808
- if (r.length === 0) return P.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${T(a)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
979
+ `))), i = await Y(n), a = C(i);
980
+ if (r.length === 0) return L.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${D(a)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
809
981
  let o = r[0];
810
- if (o.liveFingerprint === null || o.liveFingerprint === void 0) return P.warn("db drift: no drift baseline recorded yet — cannot compare"), console.log(), console.log(` last applied: ${o.id} at ${Z(o.appliedAt)}`), console.log(` live now: ${T(a)}`), console.log(), console.log("That row was written before the framework recorded a live-schema baseline,"), console.log("and the value it does carry is the DECLARED snapshot's hash — not comparable"), console.log("with a live one. Run `voltro db apply` once to establish the baseline (a no-op"), console.log("apply backfills it too); use `voltro db plan` meanwhile, which compares declared"), console.log("against live directly."), console.log(), console.log("Exit code 3 — NOT 0. \"Did not compare\" is not \"clean\", and a gate on the exit"), console.log("code has to be able to tell them apart."), 3;
811
- if (o.liveFingerprint === a) return P.info(`db drift: live matches last applied (${T(a)})`), 0;
982
+ if (o.liveFingerprint === null || o.liveFingerprint === void 0) return L.warn("db drift: no drift baseline recorded yet — cannot compare"), console.log(), console.log(` last applied: ${o.id} at ${$(o.appliedAt)}`), console.log(` live now: ${D(a)}`), console.log(), console.log("That row was written before the framework recorded a live-schema baseline,"), console.log("and the value it does carry is the DECLARED snapshot's hash — not comparable"), console.log("with a live one. Run `voltro db apply` once to establish the baseline (a no-op"), console.log("apply backfills it too); use `voltro db plan` meanwhile, which compares declared"), console.log("against live directly."), console.log(), console.log("Exit code 3 — NOT 0. \"Did not compare\" is not \"clean\", and a gate on the exit"), console.log("code has to be able to tell them apart."), 3;
983
+ if (o.liveFingerprint === a) return L.info(`db drift: live matches last applied (${D(a)})`), 0;
812
984
  let s = await (async () => {
813
985
  try {
814
- let t = await Y(I(e));
815
- return t === void 0 ? void 0 : w({
986
+ let t = await Q(z(e));
987
+ return t === void 0 ? void 0 : E({
816
988
  declared: t,
817
989
  live: i,
818
- dialect: B(),
819
- ...C()
990
+ dialect: U(),
991
+ ...w()
820
992
  }).operations;
821
993
  } catch {
822
994
  return;
823
995
  }
824
996
  })();
825
997
  if (t) {
826
- let e = Q(s?.length);
827
- if (e.reason === "no-declared-schema") return P.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
998
+ let e = Ze(s?.length);
999
+ if (e.reason === "no-declared-schema") return L.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
828
1000
  if (!e.accept && s !== void 0) {
829
- P.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
1001
+ L.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
830
1002
  for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
831
1003
  return s.length > 10 && console.log(` … and ${s.length - 10} more`), console.log(), console.log("`--accept` records the CURRENT live schema as the applied baseline. It is for a"), console.log("change that already landed out of band and left the schema correct — not for one"), console.log("that has not landed. Run `voltro db apply` instead; it writes a real baseline."), 4;
832
1004
  }
833
- return await G(n, _.SqlClient.pipe(h.flatMap((e) => e`
1005
+ return await J(n, b.SqlClient.pipe(g.flatMap((e) => e`
834
1006
  UPDATE ${e("_voltro_migration_plans")}
835
1007
  SET ${e("liveFingerprint")} = ${a}
836
1008
  WHERE ${e("id")} = (
@@ -839,31 +1011,43 @@ var E = class extends Error {
839
1011
  ORDER BY ${e("appliedAt")} DESC LIMIT 1
840
1012
  ) AS ${e("newest")}
841
1013
  )
842
- `))), P.info(`db drift: accepted the live schema as the baseline (${T(a)})`), console.log(), console.log("`voltro db plan` reports 0 operations against this schema, so the live state already"), console.log("matches your declaration — only the ledger did not know. It does now."), console.log(), console.log("No history entry was invented: the latest row's baseline was updated in place,"), console.log("because no migration ran."), 0;
1014
+ `))), L.info(`db drift: accepted the live schema as the baseline (${D(a)})`), console.log(), console.log("`voltro db plan` reports 0 operations against this schema, so the live state already"), console.log("matches your declaration — only the ledger did not know. It does now."), console.log(), console.log("No history entry was invented: the latest row's baseline was updated in place,"), console.log("because no migration ran."), 0;
1015
+ }
1016
+ return L.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${D(o.liveFingerprint)} at ${$(o.appliedAt)} (${o.id})`), console.log(` live now: ${D(a)}`), console.log(), console.log("Something changed the live schema after the last apply. This command can see"), console.log("THAT it changed, not what or who — the fingerprints are hashes, not a diff."), console.log(), s === void 0 ? (console.log("No schema files found here, so the declared side could not be compared."), console.log("Run `voltro db plan` from your project root for the actual difference."), 4) : s.length === 0 ? (console.log("Your DECLARED schema is already satisfied — `voltro db plan` reports 0 operations"), console.log("against this database. So the live schema is not wrong, only unrecorded: something"), console.log("applied a change without going through the planner (a hand-run ALTER, a DBA"), console.log("window, a restored dump), or it touched a table your code does not declare."), console.log(), console.log("If that was deliberate and the schema is right, record it:"), console.log(), console.log(" voltro db drift --accept"), console.log(), console.log("It updates the latest ledger row's baseline to the live schema and invents no"), console.log("history entry. Drift then measures from here."), 4) : (console.log(`\`voltro db plan\` reports ${s.length} operation(s) still pending against your`), console.log("declared schema, so this is not merely an unrecorded change — run it for the diff,"), console.log("then `voltro db apply`, which writes a fresh baseline of its own."), 4);
1017
+ }, $e = async (e, t) => {
1018
+ let n = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
1019
+ try {
1020
+ let r = await J(t, b.SqlClient.pipe(g.flatMap((t) => he(t, {
1021
+ projectRoot: e,
1022
+ env: n,
1023
+ appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
1024
+ }))));
1025
+ return r.applied.length > 0 && L.info(`db apply: applied ${r.applied.length} pending file-based migration(s) first`, { applied: r.applied.map((e) => `${e.id} (${e.durationMs}ms)`) }), { applied: r.applied.length };
1026
+ } catch (e) {
1027
+ return { failed: e };
843
1028
  }
844
- return P.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${T(o.liveFingerprint)} at ${Z(o.appliedAt)} (${o.id})`), console.log(` live now: ${T(a)}`), console.log(), console.log("Something changed the live schema after the last apply. This command can see"), console.log("THAT it changed, not what or who — the fingerprints are hashes, not a diff."), console.log(), s === void 0 ? (console.log("No schema files found here, so the declared side could not be compared."), console.log("Run `voltro db plan` from your project root for the actual difference."), 4) : s.length === 0 ? (console.log("Your DECLARED schema is already satisfied — `voltro db plan` reports 0 operations"), console.log("against this database. So the live schema is not wrong, only unrecorded: something"), console.log("applied a change without going through the planner (a hand-run ALTER, a DBA"), console.log("window, a restored dump), or it touched a table your code does not declare."), console.log(), console.log("If that was deliberate and the schema is right, record it:"), console.log(), console.log(" voltro db drift --accept"), console.log(), console.log("It updates the latest ledger row's baseline to the live schema and invents no"), console.log("history entry. Drift then measures from here."), 4) : (console.log(`\`voltro db plan\` reports ${s.length} operation(s) still pending against your`), console.log("declared schema, so this is not merely an unrecorded change — run it for the diff,"), console.log("then `voltro db apply`, which writes a fresh baseline of its own."), 4);
845
- }, Fe = async (e) => {
846
- let t = I(e), n = await V(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
847
- await G(n, x(v, B()));
848
- let i = await G(n, _.SqlClient.pipe(h.flatMap((e) => ue(e, {
1029
+ }, et = async (e) => {
1030
+ let t = z(e), n = await W(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
1031
+ await J(n, S(x, U()));
1032
+ let i = await J(n, b.SqlClient.pipe(g.flatMap((e) => he(e, {
849
1033
  projectRoot: t,
850
1034
  env: r,
851
1035
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
852
1036
  }))));
853
- return i.applied.length === 0 ? (P.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (P.info(`db files: applied ${i.applied.length} migration(s)`, {
1037
+ return i.applied.length === 0 ? (L.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (L.info(`db files: applied ${i.applied.length} migration(s)`, {
854
1038
  applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
855
1039
  skipped: i.skipped.length
856
1040
  }), 0);
857
- }, Ie = async (e) => {
858
- let t = I(e), n = e.find((e) => !e.startsWith("--"));
859
- if (!n) return P.error("voltro db rollback-file <migration-id>"), 2;
860
- if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return P.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
861
- let r = await G(await V(), _.SqlClient.pipe(h.flatMap((e) => le(e, {
1041
+ }, tt = async (e) => {
1042
+ let t = z(e), n = e.find((e) => !e.startsWith("--"));
1043
+ if (!n) return L.error("voltro db rollback-file <migration-id>"), 2;
1044
+ if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return L.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
1045
+ let r = await J(await W(), b.SqlClient.pipe(g.flatMap((e) => pe(e, {
862
1046
  projectRoot: t,
863
1047
  id: n
864
1048
  }))));
865
- return P.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
866
- }, Le = () => {
1049
+ return L.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
1050
+ }, nt = () => {
867
1051
  let e = /* @__PURE__ */ new Date();
868
1052
  return [
869
1053
  e.getUTCFullYear(),
@@ -873,7 +1057,7 @@ var E = class extends Error {
873
1057
  String(e.getUTCMinutes()).padStart(2, "0"),
874
1058
  String(e.getUTCSeconds()).padStart(2, "0")
875
1059
  ].join("");
876
- }, Re = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", ze = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
1060
+ }, rt = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", it = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
877
1061
  import { Effect } from 'effect'
878
1062
 
879
1063
  // A hand-written migration. Reach for this when the DECLARATIVE differ can't
@@ -905,36 +1089,63 @@ export default defineMigration({
905
1089
  }),
906
1090
  ],
907
1091
  })
908
- `, $ = {
909
- plan: Ee,
910
- apply: Oe,
911
- plans: ke,
912
- drift: Pe,
913
- files: Fe,
914
- "rollback-file": Ie,
915
- migrate: _e,
916
- rollback: ve,
917
- status: ye,
918
- seed: xe,
1092
+ `, at = {
1093
+ plan: He,
1094
+ apply: We,
1095
+ plans: Ge,
1096
+ drift: Qe,
1097
+ files: et,
1098
+ "rollback-file": tt,
1099
+ migrate: Ae,
1100
+ rollback: je,
1101
+ status: Me,
1102
+ seed: Pe,
919
1103
  generate: async (e) => {
920
- let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? p(t) : process.cwd(), o = Re(r ?? l(i) ?? "migration"), c = `${Le()}_${o}`, u = d(a, "migrations"), m = d(u, `${c}.migration.ts`);
921
- return await g.mkdir(u, { recursive: !0 }), await g.writeFile(m, ze(c, o), "utf8"), P.info(`created ${f(a, m)}`), console.error("Fill in the apply/undo steps, then run `voltro db migrate` (rollback with `voltro db rollback`)."), console.error("For a plain schema shape change you may not need this — `voltro db apply` diffs declared vs live."), 0;
1104
+ let { value: t, rest: n } = c(e, "--root"), { value: r, rest: i } = c(n, "--name"), a = t ? m(t) : process.cwd(), o = rt(r ?? u(i) ?? "migration"), s = `${nt()}_${o}`, l = f(a, "migrations"), d = f(l, `${s}.migration.ts`);
1105
+ return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d, it(s, o), "utf8"), L.info(`created ${p(a, d)}`), console.error("Fill in the apply/undo steps, then run `voltro db migrate` (rollback with `voltro db rollback`)."), console.error("For a plain schema shape change you may not need this — `voltro db apply` diffs declared vs live."), 0;
922
1106
  },
923
- squash: Me,
924
- "restore-snapshot": Ae,
925
- "gc-snapshots": je
926
- }, Be = async (e) => {
1107
+ squash: Je,
1108
+ "restore-snapshot": Ke,
1109
+ "gc-snapshots": qe,
1110
+ adopt: async (e) => {
1111
+ let t = (t) => {
1112
+ let n = e.indexOf(`--${t}`);
1113
+ return n === -1 ? void 0 : e[n + 1];
1114
+ }, n = t("from"), r = t("into"), i = t("map");
1115
+ if (n === void 0 || r === void 0 || i === void 0) return console.error("voltro db adopt --from <table> --into <table> --map <file.ts> [--apply] [--keep-source]"), console.error(" Dry run unless --apply. The map file default-exports { map, leaveUnset? }."), 1;
1116
+ let o = (await a(m(process.cwd(), i))).default;
1117
+ if (o?.map === void 0) return console.error(`voltro db adopt: ${i} must default-export { map: { <targetColumn>: <source> } }`), 1;
1118
+ let s = {
1119
+ from: n,
1120
+ into: r,
1121
+ map: o.map,
1122
+ ...o.leaveUnset ? { leaveUnset: o.leaveUnset } : {}
1123
+ }, c = U(), l = await W(), u = await J(l, b.SqlClient.pipe(g.flatMap(T))), d = (e) => u.tables.find((t) => t.name === e)?.columns.map((e) => ({
1124
+ name: e.name,
1125
+ nullable: e.nullable,
1126
+ hasDefault: e.defaultValue !== void 0 && e.defaultValue !== null
1127
+ })), f = d(n), p = d(r);
1128
+ if (f === void 0) return console.error(`voltro db adopt: '${n}' does not exist in the database`), 1;
1129
+ if (p === void 0) return console.error(`voltro db adopt: '${r}' does not exist in the database — boot once so the plugin's table is created`), 1;
1130
+ let h = async (e) => await J(l, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), _ = async (e) => Number(Object.values((await h(`SELECT COUNT(*) AS n FROM "${e}"`))[0] ?? { n: 0 })[0] ?? 0), v = xe(s, f, p, await _(r));
1131
+ for (let e of Ce(s, v, await _(n))) console.log(e);
1132
+ if (v.fatal) return 1;
1133
+ if (!e.includes("--apply")) return console.log(""), console.log(" (dry run — nothing written. Re-run with --apply.)"), 0;
1134
+ let y = await Ee(s, v, c, "id", void 0, h, { keepSource: e.includes("--keep-source") });
1135
+ return console.log(""), console.log(` copied ${y.copied}/${y.sourceRows} row(s)`), console.log(` snapshot: ${y.snapshotTable} — restore from here, then drop it yourself`), y.verified ? (console.log(y.sourceDropped ? ` dropped ${n}` : ` ${n} left in place (--keep-source)`), 0) : (console.error(" ✗ counts DIFFER — the source was NOT dropped. Compare the two tables before doing anything else."), 1);
1136
+ }
1137
+ }, ot = async (e) => {
927
1138
  let [t, ...n] = e;
928
1139
  if (!t) {
929
1140
  console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
930
- for (let e of Object.keys($)) console.error(` ${e}`);
1141
+ for (let e of Object.keys(at)) console.error(` ${e}`);
931
1142
  return 1;
932
1143
  }
933
- let r = $[t];
1144
+ let r = at[t];
934
1145
  if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
935
- (process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && P.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
1146
+ (process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && L.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
936
1147
  let i = (e) => {
937
- R(e) ? P.error(z) : console.error(e), process.exit(1);
1148
+ V(e) ? L.error(H) : console.error(e), process.exit(1);
938
1149
  };
939
1150
  process.on("uncaughtException", i);
940
1151
  try {
@@ -944,4 +1155,4 @@ export default defineMigration({
944
1155
  }
945
1156
  };
946
1157
  //#endregion
947
- export { U as a, E as c, Q as i, pe as l, L as n, R as o, H as r, Be as s, z as t };
1158
+ export { K as a, ve as c, Ze as i, ye as l, B as n, V as o, G as r, ot as s, H as t };