@voltro/cli 0.24.0 → 0.26.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 (90) hide show
  1. package/CHANGELOG.md +670 -0
  2. package/THIRD-PARTY-NOTICES.md +2 -2
  3. package/dist/{apiBuild-B2m4XK_8.js → apiBuild-BtrTyKnF.js} +15 -15
  4. package/dist/apiBuild-DDJ0It4j.js +2 -0
  5. package/dist/bin.js +3 -3
  6. package/dist/{commands-CaIhTsC2.js → commands-D-9iYF2D.js} +1889 -1591
  7. package/dist/dbCommand-DrzXimKf.js +2 -0
  8. package/dist/{dbCommand-CC61CsAc.js → dbCommand-uuNCrFAb.js} +172 -163
  9. package/dist/{dev-DPkQVUTP.js → dev-BnWq4jeA.js} +1 -1
  10. package/dist/{dev-NCuEhNxs.js → dev-Dbm6SWtn.js} +2369 -1774
  11. package/dist/fileConventions-3bffWssN.js +30 -0
  12. package/dist/{frameworkTableAssembly-sg41s14F.js → frameworkTableAssembly-BwHU9Euq.js} +12 -8
  13. package/dist/frameworkTableAssembly-lrjZtk0G.js +2 -0
  14. package/dist/index.js +1 -1
  15. package/dist/{inspect-_ldwsAwH.js → inspect-CUCCzw2I.js} +20 -4
  16. package/dist/inspect-gt8bq-Tz.js +2 -0
  17. package/dist/{inspectMetrics-dcC7gnxu.js → inspectMetrics-BU90mvJN.js} +15 -15
  18. package/dist/{manifestBuild-Bs1Uw22_.js → manifestBuild-BnzAxp2O.js} +1 -1
  19. package/dist/manifestBuild-ifczArzr.js +2 -0
  20. package/dist/serveCommand-XBXuwJty.js +1294 -0
  21. package/dist/serveEntry.js +2 -2
  22. package/dist/{start-C_4VUDT8.js → start-BGXIf6zT.js} +2 -2
  23. package/dist/startEntry.js +2 -2
  24. package/package.json +17 -17
  25. package/templates/AGENTS.core.md +12 -5
  26. package/templates/AGENTS.md +14 -7
  27. package/templates/agent-docs/_index.md +2 -2
  28. package/templates/agent-docs/_manifest.json +3 -3
  29. package/templates/agent-docs/cli.md +15 -0
  30. package/templates/agent-docs/data.md +339 -2
  31. package/templates/agent-docs/database/migrations.md +40 -0
  32. package/templates/agent-docs/database/schema.md +127 -0
  33. package/templates/agent-docs/deployment.md +28 -0
  34. package/templates/agent-docs/plugins.md +26 -4
  35. package/templates/agent-docs/reference.md +2 -2
  36. package/templates/agent-docs/security.md +18 -0
  37. package/templates/agent-docs/templates/apibackends.md +5 -5
  38. package/templates/agent-docs/templates/overview.md +1 -1
  39. package/templates/agent-docs/whats-new.md +278 -168
  40. package/templates/agent-docs/workflows.md +160 -42
  41. package/templates/apps/api-ai/package.json +7 -7
  42. package/templates/apps/api-auth/package.json +8 -8
  43. package/templates/apps/api-backend/package.json +7 -7
  44. package/templates/apps/api-backend-deactivation/package.json +7 -7
  45. package/templates/apps/api-backend-mail/package.json +8 -8
  46. package/templates/apps/api-backend-mariadb/package.json +9 -9
  47. package/templates/apps/api-backend-storage/package.json +8 -8
  48. package/templates/apps/api-data-advanced/package.json +8 -8
  49. package/templates/apps/api-durable/README.md +2 -1
  50. package/templates/apps/api-durable/events/orders.event.ts +18 -0
  51. package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +17 -15
  52. package/templates/apps/api-durable/package.json +8 -8
  53. package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +6 -4
  54. package/templates/apps/api-feature-flags/package.json +9 -9
  55. package/templates/apps/api-governance/package.json +8 -8
  56. package/templates/apps/api-kv/package.json +8 -8
  57. package/templates/apps/api-moderation/package.json +8 -8
  58. package/templates/apps/api-observability/package.json +8 -8
  59. package/templates/apps/api-ratelimit/package.json +8 -8
  60. package/templates/apps/api-rbac/package.json +8 -8
  61. package/templates/apps/api-rest/package.json +7 -7
  62. package/templates/apps/api-saas/package.json +11 -11
  63. package/templates/apps/api-search/package.json +8 -8
  64. package/templates/apps/api-versioning/package.json +8 -8
  65. package/templates/apps/api-webhooks/README.md +2 -2
  66. package/templates/apps/api-webhooks/events/orders.event.ts +35 -0
  67. package/templates/apps/api-webhooks/package.json +9 -9
  68. package/templates/apps/api-webhooks/template.json +1 -1
  69. package/templates/apps/changelog/package.json +6 -6
  70. package/templates/apps/edge-functions/package.json +2 -2
  71. package/templates/apps/frontend-admin/package.json +8 -8
  72. package/templates/apps/frontend-app/package.json +8 -8
  73. package/templates/apps/frontend-blank/package.json +7 -7
  74. package/templates/apps/frontend-contact/package.json +7 -7
  75. package/templates/apps/frontend-dashboard/package.json +7 -7
  76. package/templates/apps/frontend-docs/package.json +7 -7
  77. package/templates/apps/frontend-i18n/package.json +6 -6
  78. package/templates/apps/frontend-landing/package.json +7 -7
  79. package/templates/apps/frontend-spa/package.json +7 -7
  80. package/templates/apps/frontend-ssr/package.json +7 -7
  81. package/templates/apps/frontend-ssr-api/package.json +8 -8
  82. package/templates/apps/frontend-static-blog/package.json +6 -6
  83. package/dist/apiBuild-UEM3QBke.js +0 -2
  84. package/dist/dbCommand-Cr__4ATv.js +0 -2
  85. package/dist/fileConventions-CBNPZVOV.js +0 -29
  86. package/dist/frameworkTableAssembly-DRJrAVr1.js +0 -2
  87. package/dist/inspect-BA67TF6v.js +0 -2
  88. package/dist/manifestBuild-i-fRHg_H.js +0 -2
  89. package/dist/serveCommand-C4gzDZzc.js +0 -1175
  90. package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +0 -22
@@ -1,7 +1,7 @@
1
1
  import { i as e, n as t, r as n, s as r, t as i } from "./seedRunner-D6eu-u5U.js";
2
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";
3
+ import { t as o } from "./frameworkTableAssembly-BwHU9Euq.js";
4
+ import { _ as s } from "./fileConventions-3bffWssN.js";
5
5
  import { a as c, n as l, t as u } from "./cliArgs-qdZSElM3.js";
6
6
  import { basename as d, join as f, relative as p, resolve as m } from "node:path";
7
7
  import { pathToFileURL as h } from "node:url";
@@ -12,15 +12,15 @@ import { InMemoryDataStore as ee } from "@voltro/runtime";
12
12
  import { SqlClient as b } from "@effect/sql";
13
13
  import { actorsTable as te, frameworkTables as x, isMigrationDefinition as ne, quoteIdent as re, registerCoreTables as ie } from "@voltro/database";
14
14
  import { createHash as ae } from "node:crypto";
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
+ import { DEFAULT_CDC_CHANNEL as oe, applyPlan as S, applySchema as C, assessRollingDeployGate as se, declaredSnapshot as ce, destructiveScope as le, detectReactiveTriggerDrift as ue, fingerprintSchema as w, ignoreTablesFromEnv as T, introspectSchema as de, pendingFileMigrationIds as fe, planMigrations as E, reactiveTriggerRepairSql as pe, rollbackFileBasedMigration as me, rollingDeployUnsafeOps as he, runFileBasedMigrations as ge, shortFingerprint as D, squashMigrationPlans as _e, unblockLossy as ve } from "@voltro/database/sql";
16
16
  //#region src/cliError.ts
17
- var ve = class extends Error {
17
+ var ye = class extends Error {
18
18
  isCliError = !0;
19
19
  code;
20
20
  constructor(e, t = 1) {
21
21
  super(e), this.name = "CliError", this.code = t;
22
22
  }
23
- }, ye = (e) => typeof e == "object" && !!e && e.isCliError === !0, be = (e) => typeof e != "string", xe = (e, t, n, r) => {
23
+ }, be = (e) => typeof e == "object" && !!e && e.isCliError === !0, xe = (e) => typeof e != "string", Se = (e, t, n, r) => {
24
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
25
  r > 0 && i.push({
26
26
  kind: "target-not-empty",
@@ -37,7 +37,7 @@ var ve = class extends Error {
37
37
  });
38
38
  continue;
39
39
  }
40
- if (be(n)) {
40
+ if (xe(n)) {
41
41
  c.push({
42
42
  target: t,
43
43
  source: n.expr,
@@ -80,23 +80,23 @@ var ve = class extends Error {
80
80
  remintIds: f,
81
81
  fatal: i.some((e) => e.fatal)
82
82
  };
83
- }, Se = (e, t) => [
83
+ }, Ce = (e, t) => [
84
84
  `back up '${e.from}' to a restorable dump`,
85
85
  `copy rows: INSERT INTO ${e.into} (${t.assignments.map((e) => e.target).join(", ")}) SELECT … FROM ${e.from}`,
86
86
  ...t.remintIds ? ["record old id → new id in a translation table", `rewrite every reference to '${e.from}', JSON columns included`] : [],
87
87
  "verify: row counts match and a sample round-trips",
88
88
  `drop '${e.from}' — LAST, and only after the verify`
89
- ], Ce = (e, t, n) => {
89
+ ], we = (e, t, n) => {
90
90
  let r = [`adopt · ${e.from} (${n} row(s)) → ${e.into}`, ""];
91
91
  for (let e of t.assignments) r.push(` ${e.target.padEnd(24)} ← ${e.raw ? `${e.source} (raw)` : e.source}`);
92
92
  t.problems.length > 0 && r.push("");
93
93
  for (let e of t.problems) r.push(` ${e.fatal ? "✗" : "⚠"} ${e.detail}`);
94
94
  if (r.push(""), t.fatal) return r.push(" REFUSED — fix the problems above. Nothing was written."), r;
95
95
  r.push(" would run, in this order:");
96
- for (let [n, i] of Se(e, t).entries()) r.push(` ${n + 1}. ${i}`);
96
+ for (let [n, i] of Ce(e, t).entries()) r.push(` ${n + 1}. ${i}`);
97
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));
98
+ }, Te = (e) => e === "mysql" || e === "mariadb" ? (e) => `\`${e}\`` : e === "mssql" ? (e) => `[${e}]` : (e) => `"${e}"`, O = (e) => `${e}__adopt_snapshot`, Ee = (e, t, n, r, i) => {
99
+ let a = Te(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
100
  return i !== void 0 && (s.unshift(r), c.unshift(i)), {
101
101
  snapshot: n === "mssql" ? `SELECT * INTO ${a(o)} FROM ${a(e.from)}` : `CREATE TABLE ${a(o)} AS SELECT * FROM ${a(e.from)}`,
102
102
  copy: `INSERT INTO ${a(e.into)} (${s.map(a).join(", ")}) SELECT ${c.join(", ")} FROM ${a(e.from)}`,
@@ -111,9 +111,9 @@ var ve = class extends Error {
111
111
  if (t === void 0) return 0;
112
112
  let n = t.n ?? t.N ?? Object.values(t)[0];
113
113
  return Number(n ?? 0);
114
- }, Ee = async (e, t, n, r, i, a, o = {}) => {
114
+ }, De = async (e, t, n, r, i, a, o = {}) => {
115
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));
116
+ let s = Ee(e, t, n, r, i), c = O(e.from), l = k(await a(s.verifySource));
117
117
  await a(s.snapshot);
118
118
  for (let e of s.translations) await a(e);
119
119
  await a(s.copy);
@@ -167,7 +167,7 @@ var ve = class extends Error {
167
167
  return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
168
168
  }, M = (e) => {
169
169
  let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
170
- return g.runPromise(S(x, n).pipe(g.provide(e)));
170
+ return g.runPromise(C(x, n).pipe(g.provide(e)));
171
171
  }, N = (e) => g.runPromise(g.gen(function* () {
172
172
  let e = yield* (yield* b.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(g.orElseSucceed(() => []));
173
173
  return new Map(e.map((e) => [e.id, e]));
@@ -187,7 +187,7 @@ var ve = class extends Error {
187
187
  "errorStack" = EXCLUDED."errorStack",
188
188
  "updatedAt" = now()
189
189
  `;
190
- }).pipe(g.provide(e))), De = async (e, t, n) => {
190
+ }).pipe(g.provide(e))), Oe = async (e, t, n) => {
191
191
  if (e.length === 0) return {
192
192
  applied: [],
193
193
  failed: null,
@@ -295,7 +295,7 @@ var ve = class extends Error {
295
295
  failed: o,
296
296
  skipped: a
297
297
  };
298
- }, Oe = async (e, t, n, r = {}) => {
298
+ }, ke = async (e, t, n, r = {}) => {
299
299
  await M(t);
300
300
  let i = await N(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
301
301
  if (a.length === 0) return n.info("migration: nothing to roll back"), {
@@ -398,7 +398,7 @@ var ve = class extends Error {
398
398
  ""
399
399
  ];
400
400
  return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
401
- }, L = _({ scope: "voltro:db" }), ke = /* @__PURE__ */ new Set([
401
+ }, L = _({ scope: "voltro:db" }), Ae = /* @__PURE__ */ new Set([
402
402
  "node_modules",
403
403
  "dist",
404
404
  "build",
@@ -410,7 +410,7 @@ var ve = class extends Error {
410
410
  let n = [], r = async (e) => {
411
411
  let i = await v.readdir(e, { withFileTypes: !0 }).catch(() => []);
412
412
  for (let a of i) {
413
- if (ke.has(a.name)) continue;
413
+ if (Ae.has(a.name)) continue;
414
414
  let i = f(e, a.name);
415
415
  a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
416
416
  }
@@ -439,15 +439,15 @@ var ve = class extends Error {
439
439
  return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
440
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 = () => {
441
441
  let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
442
- if (e === "memory") throw new ve("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
442
+ if (e === "memory") throw new ye("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
443
443
  return e;
444
- }, W = async () => (await r(U())).makeSqlLayer(B()), Ae = async (e) => {
444
+ }, W = (e) => re(e, U()), G = (e) => e === "mysql" || e === "mariadb" ? "table_schema = DATABASE()" : e === "mssql" ? "table_schema = SCHEMA_NAME()" : "table_schema = 'public'", K = async () => (await r(U())).makeSqlLayer(B()), je = async (e) => {
445
445
  let t = z(e), n = e.includes("--dry-run"), r = await j(await R(t, A), L);
446
446
  if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
447
447
  if (n) {
448
448
  let e = /* @__PURE__ */ new Set();
449
449
  try {
450
- let t = await F(r, await W());
450
+ let t = await F(r, await K());
451
451
  e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
452
452
  } catch {
453
453
  L.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
@@ -458,26 +458,26 @@ var ve = class extends Error {
458
458
  for (let e of t) console.log(` pending ${e.definition.id}`);
459
459
  return 0;
460
460
  }
461
- let i = await De(r, await W(), L);
461
+ let i = await Oe(r, await K(), L);
462
462
  return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("migrate complete", {
463
463
  applied: i.applied.length,
464
464
  skipped: i.skipped.length
465
465
  }), 0);
466
- }, je = async (e) => {
466
+ }, Me = async (e) => {
467
467
  let t = z(e), n = l(e, "--to"), r = await j(await R(t, A), L);
468
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 } : {});
469
+ let i = await ke(r, await K(), L, n ? { to: n } : {});
470
470
  return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("rollback complete", { reverted: i.applied.length }), 0);
471
- }, Me = async (e) => {
471
+ }, Ne = async (e) => {
472
472
  let t = await j(await R(z(e), A), L);
473
473
  if (t.length === 0) return console.log("no migrations found"), 0;
474
- let n = await F(t, await W());
474
+ let n = await F(t, await K());
475
475
  for (let e of n) {
476
476
  let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
477
477
  console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
478
478
  }
479
479
  return 0;
480
- }, Ne = async (e) => {
480
+ }, Pe = async (e) => {
481
481
  let t = (l(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
482
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
483
  store: new ee(),
@@ -497,10 +497,10 @@ var ve = class extends Error {
497
497
  },
498
498
  dialectId: n.id
499
499
  };
500
- }, Pe = async (r) => {
500
+ }, Fe = async (r) => {
501
501
  let a = z(r), o = l(r, "--id"), s = l(r, "--lifecycle"), c = await t(await R(a, i), L);
502
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);
503
+ let { store: u, dispose: d, dialectId: f } = await Pe(r);
504
504
  try {
505
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) => ({
506
506
  ...e,
@@ -514,7 +514,7 @@ var ve = class extends Error {
514
514
  L.warn("voltro db seed: store dispose failed", { error: String(e) });
515
515
  });
516
516
  }
517
- }, Fe = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", G = async (e) => {
517
+ }, Ie = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", Le = async (e) => {
518
518
  let t = await R(e, s), n = [];
519
519
  for (let r of t) {
520
520
  let t;
@@ -527,10 +527,10 @@ var ve = class extends Error {
527
527
  });
528
528
  continue;
529
529
  }
530
- for (let e of Object.values(t)) Fe(e) && n.push(e);
530
+ for (let e of Object.values(t)) Ie(e) && n.push(e);
531
531
  }
532
532
  return n;
533
- }, K = (e) => {
533
+ }, Re = (e) => {
534
534
  let t = new Map(e.map((e) => [e.tableName, e]));
535
535
  t.has("actors") || t.set("actors", te);
536
536
  let n = t.get("actors"), r = t.get("tenants");
@@ -538,27 +538,27 @@ var ve = class extends Error {
538
538
  actors: () => n,
539
539
  ...r === void 0 ? {} : { tenants: () => r }
540
540
  }), [...t.values()];
541
- }, Ie = (e) => {
541
+ }, ze = (e) => {
542
542
  let t = /* @__PURE__ */ new Map();
543
543
  for (let n of e) {
544
544
  let e = n.tableName;
545
545
  e !== void 0 && !t.has(e) && t.set(e, n);
546
546
  }
547
547
  return [...t.values()];
548
- }, q = (e) => {
548
+ }, Be = (e) => {
549
549
  let t = le(process.env.VOLTRO_DESTRUCTIVE_OK);
550
550
  if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
551
551
  plan: e,
552
552
  acknowledged: !1
553
553
  };
554
- let n = _e(e, t);
554
+ let n = ve(e, t);
555
555
  return {
556
556
  plan: n,
557
557
  acknowledged: !n.operations.some((e) => e.blocked)
558
558
  };
559
- }, J = async (e, t) => g.runPromise(t.pipe(g.provide(e))), Y = async (e) => {
559
+ }, q = async (e, t) => g.runPromise(t.pipe(g.provide(e))), J = async (e) => {
560
560
  try {
561
- return await J(e, b.SqlClient.pipe(g.flatMap(T)));
561
+ return await q(e, b.SqlClient.pipe(g.flatMap(de)));
562
562
  } catch (e) {
563
563
  let t = String(e?.message ?? e);
564
564
  if (V(e) || /connection error/i.test(t)) {
@@ -567,7 +567,7 @@ var ve = class extends Error {
567
567
  }
568
568
  throw e;
569
569
  }
570
- }, Le = {
570
+ }, Ve = {
571
571
  safe: "\x1B[32m✓\x1B[0m",
572
572
  "needs-default": "\x1B[32m⊕\x1B[0m",
573
573
  "needs-backfill": "\x1B[33m⊕\x1B[0m",
@@ -575,7 +575,7 @@ var ve = class extends Error {
575
575
  lossy: "\x1B[31m✗\x1B[0m",
576
576
  "online-required": "\x1B[36m◷\x1B[0m",
577
577
  "multi-step": "\x1B[35m⇉\x1B[0m"
578
- }, Re = (e) => {
578
+ }, He = (e) => {
579
579
  let t = e.op, n;
580
580
  switch (t.kind) {
581
581
  case "create-table":
@@ -639,9 +639,9 @@ var ve = class extends Error {
639
639
  n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
640
640
  break;
641
641
  }
642
- let r = Le[e.classification], i = e.reason ? ` # ${e.reason}` : "", a = e.blocked ? `\n ! ${e.blocked.fix}` : "";
642
+ let r = Ve[e.classification], i = e.reason ? ` # ${e.reason}` : "", a = e.blocked ? `\n ! ${e.blocked.fix}` : "";
643
643
  return ` ${r} ${n}${i}${a}`;
644
- }, ze = async (e, t) => {
644
+ }, Ue = async (e, t) => {
645
645
  let n = e.operations.filter((e) => e.classification === "needs-default" && e.op.kind === "add-column");
646
646
  if (n.length === 0) return;
647
647
  let r = [];
@@ -650,7 +650,7 @@ var ve = class extends Error {
650
650
  if (i === void 0 || a === void 0) continue;
651
651
  let o = 0;
652
652
  try {
653
- let e = await J(t, b.SqlClient.pipe(g.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${re(i, U())}`))));
653
+ let e = await q(t, b.SqlClient.pipe(g.flatMap((e) => e.unsafe(`SELECT COUNT(*) AS n FROM ${re(i, U())}`))));
654
654
  o = Number(Object.values(e[0] ?? { n: 0 })[0] ?? 0);
655
655
  } catch {
656
656
  continue;
@@ -662,15 +662,15 @@ var ve = class extends Error {
662
662
  for (let e of r) console.log(e);
663
663
  console.log(" (the shape is correct either way — this is the VALUE a state diff cannot know)");
664
664
  }
665
- }, X = async (e, t) => {
665
+ }, Y = async (e, t) => {
666
666
  if (U() !== "postgres") return;
667
667
  let n = oe, r;
668
668
  try {
669
- r = await J(t, b.SqlClient.pipe(g.flatMap((t) => ue(t, e, n))));
669
+ r = await q(t, b.SqlClient.pipe(g.flatMap((t) => ue(t, e, n))));
670
670
  } catch {
671
671
  return;
672
672
  }
673
- let i = fe({
673
+ let i = pe({
674
674
  tables: e,
675
675
  dialect: "postgres",
676
676
  missing: r.missing,
@@ -680,43 +680,50 @@ var ve = class extends Error {
680
680
  if (i.length === 0) return;
681
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
682
  let a = 0;
683
- for (let e of i) await J(t, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), a += 1;
683
+ for (let e of i) await q(t, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(e)))), a += 1;
684
684
  L.info(`db apply: change triggers converged (${a} statement(s))`, {
685
685
  installed: r.missing.length,
686
686
  removed: r.stale.length
687
687
  });
688
- }, Z = (e) => {
688
+ }, X = (e) => {
689
689
  let t = e.summary, n = e.operations.length;
690
690
  if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
691
691
  console.log(" (schema is up to date)\n");
692
692
  return;
693
693
  }
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);
694
+ for (let t of e.operations) console.log(He(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`), We(e);
696
+ }, We = (e) => {
697
+ let t = he(e.operations);
698
698
  if (t.length !== 0) {
699
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
700
  for (let e of t) console.log(` • ${e.op.op.kind}: ${e.reason}`), console.log(` → ${e.remedy}`);
701
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
702
  }
703
- }, Ve = (e) => {
703
+ }, Ge = (e, t) => {
704
+ let n = se({
705
+ enabled: process.env.VOLTRO_ROLLING_DEPLOY === "1",
706
+ forced: t.includes("--force"),
707
+ unsafeCount: he(e.operations).length
708
+ });
709
+ return n.refuse ? (L.error(`db apply: ${n.message}`), !0) : (n.warnForced && L.warn(`db apply: ${n.message}`), !1);
710
+ }, Ke = (e) => {
704
711
  let t = l(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
705
712
  if (t === void 0) return;
706
713
  let n = Number(t);
707
714
  if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
708
715
  return n;
709
- }, Q = async (e) => {
710
- let t = await G(e);
716
+ }, Z = async (e) => {
717
+ let t = await Le(e);
711
718
  if (t.length === 0) return;
712
719
  let n = await o({ root: e });
713
- return K(Ie([
720
+ return Re(ze([
714
721
  ...t,
715
722
  ...x,
716
723
  ...n
717
724
  ]));
718
- }, He = async (e) => {
719
- let t = Ve(e), n = z(e), r = await Q(n);
725
+ }, qe = async (e) => {
726
+ let t = Ke(e), n = z(e), r = await Z(n);
720
727
  if (r === void 0) return L.warn("db plan: no schema files found", {
721
728
  root: n,
722
729
  hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
@@ -734,23 +741,23 @@ var ve = class extends Error {
734
741
  declared: r,
735
742
  live: a,
736
743
  dialect: U(),
737
- ...w(),
744
+ ...T(),
738
745
  ...t === void 0 ? {} : { onlineAfter: t }
739
746
  });
740
- return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), Z(s), s.summary.blocked > 0 ? 2 : 0;
747
+ return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), X(s), s.summary.blocked > 0 ? 2 : 0;
741
748
  } catch (e) {
742
749
  return L.error(`db plan --against: ${e.message}`), 1;
743
750
  }
744
751
  }
745
752
  let a = E({
746
753
  declared: r,
747
- live: await Y(await W()),
754
+ live: await J(await K()),
748
755
  dialect: U(),
749
- ...w(),
756
+ ...T(),
750
757
  ...t === void 0 ? {} : { onlineAfter: t }
751
758
  });
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) => {
759
+ return e.includes("--json") ? (y(1, `${JSON.stringify(a, null, 2)}\n`), a.summary.blocked > 0 ? 2 : 0) : (X(a), a.summary.blocked > 0 ? 2 : 0);
760
+ }, Je = async (e, t, n) => {
754
761
  let r;
755
762
  try {
756
763
  let t = await v.readFile(m(e), "utf8");
@@ -759,81 +766,83 @@ var ve = class extends Error {
759
766
  return L.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
760
767
  }
761
768
  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);
769
+ let i = await Z(n);
763
770
  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(() => []);
771
+ let a = await K();
772
+ await q(a, C(x, U()));
773
+ let o = await q(a, b.SqlClient.pipe(g.flatMap((e) => fe(e, n)))).catch(() => []);
767
774
  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
775
  let s = E({
769
776
  declared: i,
770
- live: await Y(a),
777
+ live: await J(a),
771
778
  dialect: U(),
772
- ...w()
779
+ ...T()
773
780
  });
774
781
  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
782
  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, {
783
+ let c = Be(s), u = c.plan;
784
+ if (u.summary.blocked > 0 && !c.acknowledged) return X(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;
785
+ if (c.acknowledged && L.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), Ge(u, t)) return 2;
786
+ if (u.operations.length === 0) return L.info("db apply --plan: schema is up to date — no DDL to apply (change triggers are reconciled separately below)"), await Y(i, a), 0;
787
+ X(u);
788
+ let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = l(t, "--note"), p = await q(a, b.SqlClient.pipe(g.flatMap((e) => S(e, u, {
781
789
  declared: i,
782
790
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
783
791
  environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
784
792
  source: "file",
785
793
  ...f ? { notes: f } : {},
786
- replan: () => g.promise(() => Y(a)).pipe(g.map((e) => E({
794
+ replan: () => g.promise(() => J(a)).pipe(g.map((e) => E({
787
795
  declared: i,
788
796
  live: e,
789
797
  dialect: U(),
790
- ...w()
798
+ ...T()
791
799
  })))
792
800
  }))));
793
801
  return L.info(`db apply --plan: applied ${p.appliedOps} op(s) in ${p.durationMs}ms`, {
794
802
  id: p.id,
795
803
  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);
804
+ }), await Y(i, a), 0;
805
+ }, Ye = async (e) => {
806
+ let t = z(e), n = Ke(e), r = l(e, "--plan");
807
+ if (r) return Je(r, e, t);
800
808
  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);
809
+ let i = await Z(t);
802
810
  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);
811
+ let a = await K(), o = await it(t, a);
804
812
  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()));
813
+ await q(a, C(x, U()));
806
814
  let s = E({
807
815
  declared: i,
808
- live: await Y(a),
816
+ live: await J(a),
809
817
  dialect: U(),
810
- ...w(),
818
+ ...T(),
811
819
  ...n === void 0 ? {} : { onlineAfter: n }
812
820
  });
813
- Z(s), await ze(s, a);
814
- let c = q(s), u = c.plan;
821
+ X(s), await Ue(s, a);
822
+ let c = Be(s), u = c.plan;
815
823
  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, {
824
+ if (c.acknowledged && L.warn(`db apply: proceeding with ${s.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), Ge(u, e)) return 2;
825
+ if (u.operations.length === 0) return L.info("db apply: schema is up to date — no DDL to apply (change triggers are reconciled separately below)"), await et(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 Y(i, a), 0;
826
+ let d = l(e, "--note"), f = await q(a, b.SqlClient.pipe(g.flatMap((e) => S(e, u, {
818
827
  declared: i,
819
828
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
820
829
  environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
821
830
  source: "auto-diff",
822
831
  ...d ? { notes: d } : {},
823
- replan: () => g.promise(() => Y(a)).pipe(g.map((e) => E({
832
+ replan: () => g.promise(() => J(a)).pipe(g.map((e) => E({
824
833
  declared: i,
825
834
  live: e,
826
835
  dialect: U(),
827
- ...w(),
836
+ ...T(),
828
837
  ...n === void 0 ? {} : { onlineAfter: n }
829
838
  })))
830
839
  }))));
831
840
  return L.info(`db apply: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
832
841
  id: f.id,
833
842
  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`
843
+ }), await Y(i, a), 0;
844
+ }, Xe = async (e) => {
845
+ let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await q(await K(), b.SqlClient.pipe(g.flatMap((e) => e`
837
846
  SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
838
847
  ${e("appliedBy")}, ${e("environment")}, ${e("source")},
839
848
  ${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
@@ -846,20 +855,20 @@ var ve = class extends Error {
846
855
  for (let e of n) {
847
856
  let t = 0;
848
857
  try {
849
- t = JSON.parse(Xe(e.operations)).length;
858
+ t = JSON.parse(tt(e.operations)).length;
850
859
  } catch {}
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`);
860
+ console.log(` ${e.id} fp=${D(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${Q(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
852
861
  }
853
862
  return console.log(), 0;
854
- }, Ke = async (e) => {
863
+ }, Ze = async (e) => {
855
864
  let t = e[0];
856
865
  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;
857
- let n = await W(), r = await J(n, b.SqlClient.pipe(g.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
866
+ let n = await K(), r = await q(n, b.SqlClient.pipe(g.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
858
867
  FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
859
868
  if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
860
869
  let i;
861
870
  try {
862
- i = JSON.parse(Xe(r[0].operations));
871
+ i = JSON.parse(tt(r[0].operations));
863
872
  } catch {
864
873
  return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
865
874
  }
@@ -873,9 +882,9 @@ var ve = class extends Error {
873
882
  if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
874
883
  let s = 0;
875
884
  for (let e of a) {
876
- let t = await J(n, b.SqlClient.pipe(g.flatMap((t) => t`
885
+ let t = await q(n, b.SqlClient.pipe(g.flatMap((t) => t`
877
886
  SELECT column_name FROM information_schema.columns
878
- WHERE table_schema = 'public'
887
+ WHERE ${t.unsafe(G(U()))}
879
888
  AND table_name = ${e.table}
880
889
  AND column_name LIKE ${e.column + "__dropped_%"}
881
890
  ORDER BY column_name DESC LIMIT 1
@@ -885,13 +894,13 @@ var ve = class extends Error {
885
894
  continue;
886
895
  }
887
896
  let r = t[0].column_name;
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})`);
897
+ await q(n, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE ${W(e.table)} RENAME COLUMN ${W(r)} TO ${W(e.column)}`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
889
898
  }
890
899
  let c = 0;
891
900
  for (let e of o) {
892
- let t = await J(n, b.SqlClient.pipe(g.flatMap((t) => t`
901
+ let t = await q(n, b.SqlClient.pipe(g.flatMap((t) => t`
893
902
  SELECT table_name FROM information_schema.tables
894
- WHERE table_schema = 'public'
903
+ WHERE ${t.unsafe(G(U()))}
895
904
  AND table_name LIKE ${e.table + "__dropped_%"}
896
905
  ORDER BY table_name DESC LIMIT 1
897
906
  `)));
@@ -900,43 +909,43 @@ var ve = class extends Error {
900
909
  continue;
901
910
  }
902
911
  let r = t[0].table_name;
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})`);
912
+ await q(n, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE ${W(r)} RENAME TO ${W(e.table)}`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
904
913
  }
905
914
  return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
906
- }, qe = async (e) => {
915
+ }, Qe = async (e) => {
907
916
  let t = l(e, "--before");
908
917
  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;
909
918
  let n = t.replace(/[^0-9]/g, "");
910
919
  if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
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`
920
+ let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await K(), o = await q(a, b.SqlClient.pipe(g.flatMap((e) => e`
912
921
  SELECT table_name, column_name FROM information_schema.columns
913
- WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
914
- `))), s = await J(a, b.SqlClient.pipe(g.flatMap((e) => e`
922
+ WHERE ${e.unsafe(G(U()))} AND column_name LIKE ${"%__dropped_%"}
923
+ `))), s = await q(a, b.SqlClient.pipe(g.flatMap((e) => e`
915
924
  SELECT table_name FROM information_schema.tables
916
- WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
925
+ WHERE ${e.unsafe(G(U()))} AND table_name LIKE ${"%__dropped_%"}
917
926
  `))), c = (e) => {
918
927
  let t = /__dropped_(\d{14})$/.exec(e);
919
928
  return t ? t[1] : null;
920
929
  }, u = 0;
921
930
  for (let e of o) {
922
931
  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++);
932
+ t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await q(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`ALTER TABLE ${W(e.table_name)} DROP COLUMN ${W(e.column_name)}`)))), u++);
924
933
  }
925
934
  for (let e of s) {
926
935
  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++);
936
+ t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await q(a, b.SqlClient.pipe(g.flatMap((t) => t.unsafe(`DROP TABLE ${W(e.table_name)}`)))), u++);
928
937
  }
929
938
  return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
930
- }, Je = async (e) => {
939
+ }, $e = async (e) => {
931
940
  let t = l(e, "--before");
932
941
  if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
933
942
  let n = t;
934
943
  if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
935
- let r = l(e, "--note"), i = z(e), a = await Q(i);
944
+ let r = l(e, "--note"), i = z(e), a = await Z(i);
936
945
  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();
946
+ let o = w(ce(a)), s = await K();
938
947
  try {
939
- let e = await J(s, ge({
948
+ let e = await q(s, _e({
940
949
  before: n,
941
950
  fingerprint: o,
942
951
  appliedBy: process.env.USER ?? "cli",
@@ -947,10 +956,10 @@ var ve = class extends Error {
947
956
  } catch (e) {
948
957
  return console.error(`squash refused: ${e.message}`), 1;
949
958
  }
950
- }, Ye = async (e) => {
959
+ }, et = async (e) => {
951
960
  try {
952
- let t = C(await Y(e));
953
- await J(e, b.SqlClient.pipe(g.flatMap((e) => e`
961
+ let t = w(await J(e));
962
+ await q(e, b.SqlClient.pipe(g.flatMap((e) => e`
954
963
  UPDATE ${e("_voltro_migration_plans")}
955
964
  SET ${e("liveFingerprint")} = ${t}
956
965
  WHERE ${e("id")} = (
@@ -962,7 +971,7 @@ var ve = class extends Error {
962
971
  AND ${e("liveFingerprint")} IS NULL
963
972
  `)));
964
973
  } catch {}
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 ? {
974
+ }, tt = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), Q = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), nt = (e) => e === void 0 ? {
966
975
  accept: !1,
967
976
  reason: "no-declared-schema"
968
977
  } : e > 0 ? {
@@ -971,38 +980,38 @@ var ve = class extends Error {
971
980
  } : {
972
981
  accept: !0,
973
982
  reason: "converged"
974
- }, Qe = async (e) => {
975
- let t = e.includes("--accept"), n = await W(), r = await J(n, b.SqlClient.pipe(g.flatMap((e) => e`
983
+ }, rt = async (e) => {
984
+ let t = e.includes("--accept"), n = await K(), r = await q(n, b.SqlClient.pipe(g.flatMap((e) => e`
976
985
  SELECT * FROM ${e("_voltro_migration_plans")}
977
986
  ORDER BY ${e("appliedAt")} DESC
978
987
  LIMIT 1
979
- `))), i = await Y(n), a = C(i);
988
+ `))), i = await J(n), a = w(i);
980
989
  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;
981
990
  let o = r[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;
991
+ 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 ${Q(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
992
  if (o.liveFingerprint === a) return L.info(`db drift: live matches last applied (${D(a)})`), 0;
984
993
  let s = await (async () => {
985
994
  try {
986
- let t = await Q(z(e));
995
+ let t = await Z(z(e));
987
996
  return t === void 0 ? void 0 : E({
988
997
  declared: t,
989
998
  live: i,
990
999
  dialect: U(),
991
- ...w()
1000
+ ...T()
992
1001
  }).operations;
993
1002
  } catch {
994
1003
  return;
995
1004
  }
996
1005
  })();
997
1006
  if (t) {
998
- let e = Ze(s?.length);
1007
+ let e = nt(s?.length);
999
1008
  if (e.reason === "no-declared-schema") return L.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
1000
1009
  if (!e.accept && s !== void 0) {
1001
1010
  L.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
1002
1011
  for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
1003
1012
  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;
1004
1013
  }
1005
- return await J(n, b.SqlClient.pipe(g.flatMap((e) => e`
1014
+ return await q(n, b.SqlClient.pipe(g.flatMap((e) => e`
1006
1015
  UPDATE ${e("_voltro_migration_plans")}
1007
1016
  SET ${e("liveFingerprint")} = ${a}
1008
1017
  WHERE ${e("id")} = (
@@ -1013,11 +1022,11 @@ var ve = class extends Error {
1013
1022
  )
1014
1023
  `))), 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
1024
  }
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) => {
1025
+ return L.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${D(o.liveFingerprint)} at ${Q(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);
1026
+ }, it = async (e, t) => {
1018
1027
  let n = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
1019
1028
  try {
1020
- let r = await J(t, b.SqlClient.pipe(g.flatMap((t) => he(t, {
1029
+ let r = await q(t, b.SqlClient.pipe(g.flatMap((t) => ge(t, {
1021
1030
  projectRoot: e,
1022
1031
  env: n,
1023
1032
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
@@ -1026,10 +1035,10 @@ var ve = class extends Error {
1026
1035
  } catch (e) {
1027
1036
  return { failed: e };
1028
1037
  }
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, {
1038
+ }, at = async (e) => {
1039
+ let t = z(e), n = await K(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
1040
+ await q(n, C(x, U()));
1041
+ let i = await q(n, b.SqlClient.pipe(g.flatMap((e) => ge(e, {
1033
1042
  projectRoot: t,
1034
1043
  env: r,
1035
1044
  appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
@@ -1038,16 +1047,16 @@ var ve = class extends Error {
1038
1047
  applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
1039
1048
  skipped: i.skipped.length
1040
1049
  }), 0);
1041
- }, tt = async (e) => {
1050
+ }, ot = async (e) => {
1042
1051
  let t = z(e), n = e.find((e) => !e.startsWith("--"));
1043
1052
  if (!n) return L.error("voltro db rollback-file <migration-id>"), 2;
1044
1053
  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, {
1054
+ let r = await q(await K(), b.SqlClient.pipe(g.flatMap((e) => me(e, {
1046
1055
  projectRoot: t,
1047
1056
  id: n
1048
1057
  }))));
1049
1058
  return L.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
1050
- }, nt = () => {
1059
+ }, st = () => {
1051
1060
  let e = /* @__PURE__ */ new Date();
1052
1061
  return [
1053
1062
  e.getUTCFullYear(),
@@ -1057,7 +1066,7 @@ var ve = class extends Error {
1057
1066
  String(e.getUTCMinutes()).padStart(2, "0"),
1058
1067
  String(e.getUTCSeconds()).padStart(2, "0")
1059
1068
  ].join("");
1060
- }, rt = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", it = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
1069
+ }, ct = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", lt = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
1061
1070
  import { Effect } from 'effect'
1062
1071
 
1063
1072
  // A hand-written migration. Reach for this when the DECLARATIVE differ can't
@@ -1089,24 +1098,24 @@ export default defineMigration({
1089
1098
  }),
1090
1099
  ],
1091
1100
  })
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,
1101
+ `, $ = {
1102
+ plan: qe,
1103
+ apply: Ye,
1104
+ plans: Xe,
1105
+ drift: rt,
1106
+ files: at,
1107
+ "rollback-file": ot,
1108
+ migrate: je,
1109
+ rollback: Me,
1110
+ status: Ne,
1111
+ seed: Fe,
1103
1112
  generate: async (e) => {
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;
1113
+ let { value: t, rest: n } = c(e, "--root"), { value: r, rest: i } = c(n, "--name"), a = t ? m(t) : process.cwd(), o = ct(r ?? u(i) ?? "migration"), s = `${st()}_${o}`, l = f(a, "migrations"), d = f(l, `${s}.migration.ts`);
1114
+ return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d, lt(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;
1106
1115
  },
1107
- squash: Je,
1108
- "restore-snapshot": Ke,
1109
- "gc-snapshots": qe,
1116
+ squash: $e,
1117
+ "restore-snapshot": Ze,
1118
+ "gc-snapshots": Qe,
1110
1119
  adopt: async (e) => {
1111
1120
  let t = (t) => {
1112
1121
  let n = e.indexOf(`--${t}`);
@@ -1120,28 +1129,28 @@ export default defineMigration({
1120
1129
  into: r,
1121
1130
  map: o.map,
1122
1131
  ...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) => ({
1132
+ }, c = U(), l = await K(), u = await q(l, b.SqlClient.pipe(g.flatMap(de))), d = (e) => u.tables.find((t) => t.name === e)?.columns.map((e) => ({
1124
1133
  name: e.name,
1125
1134
  nullable: e.nullable,
1126
1135
  hasDefault: e.defaultValue !== void 0 && e.defaultValue !== null
1127
1136
  })), f = d(n), p = d(r);
1128
1137
  if (f === void 0) return console.error(`voltro db adopt: '${n}' does not exist in the database`), 1;
1129
1138
  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);
1139
+ let h = async (e) => await q(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 = Se(s, f, p, await _(r));
1140
+ for (let e of we(s, v, await _(n))) console.log(e);
1132
1141
  if (v.fatal) return 1;
1133
1142
  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") });
1143
+ let y = await De(s, v, c, "id", void 0, h, { keepSource: e.includes("--keep-source") });
1135
1144
  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
1145
  }
1137
- }, ot = async (e) => {
1146
+ }, ut = async (e) => {
1138
1147
  let [t, ...n] = e;
1139
1148
  if (!t) {
1140
1149
  console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
1141
- for (let e of Object.keys(at)) console.error(` ${e}`);
1150
+ for (let e of Object.keys($)) console.error(` ${e}`);
1142
1151
  return 1;
1143
1152
  }
1144
- let r = at[t];
1153
+ let r = $[t];
1145
1154
  if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
1146
1155
  (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)");
1147
1156
  let i = (e) => {
@@ -1155,4 +1164,4 @@ export default defineMigration({
1155
1164
  }
1156
1165
  };
1157
1166
  //#endregion
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 };
1167
+ export { Re as a, ye as c, nt as i, be as l, B as n, V as o, Le as r, ut as s, H as t };