@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.
- package/CHANGELOG.md +670 -0
- package/THIRD-PARTY-NOTICES.md +2 -2
- package/dist/{apiBuild-B2m4XK_8.js → apiBuild-BtrTyKnF.js} +15 -15
- package/dist/apiBuild-DDJ0It4j.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/{commands-CaIhTsC2.js → commands-D-9iYF2D.js} +1889 -1591
- package/dist/dbCommand-DrzXimKf.js +2 -0
- package/dist/{dbCommand-CC61CsAc.js → dbCommand-uuNCrFAb.js} +172 -163
- package/dist/{dev-DPkQVUTP.js → dev-BnWq4jeA.js} +1 -1
- package/dist/{dev-NCuEhNxs.js → dev-Dbm6SWtn.js} +2369 -1774
- package/dist/fileConventions-3bffWssN.js +30 -0
- package/dist/{frameworkTableAssembly-sg41s14F.js → frameworkTableAssembly-BwHU9Euq.js} +12 -8
- package/dist/frameworkTableAssembly-lrjZtk0G.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspect-_ldwsAwH.js → inspect-CUCCzw2I.js} +20 -4
- package/dist/inspect-gt8bq-Tz.js +2 -0
- package/dist/{inspectMetrics-dcC7gnxu.js → inspectMetrics-BU90mvJN.js} +15 -15
- package/dist/{manifestBuild-Bs1Uw22_.js → manifestBuild-BnzAxp2O.js} +1 -1
- package/dist/manifestBuild-ifczArzr.js +2 -0
- package/dist/serveCommand-XBXuwJty.js +1294 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{start-C_4VUDT8.js → start-BGXIf6zT.js} +2 -2
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.core.md +12 -5
- package/templates/AGENTS.md +14 -7
- package/templates/agent-docs/_index.md +2 -2
- package/templates/agent-docs/_manifest.json +3 -3
- package/templates/agent-docs/cli.md +15 -0
- package/templates/agent-docs/data.md +339 -2
- package/templates/agent-docs/database/migrations.md +40 -0
- package/templates/agent-docs/database/schema.md +127 -0
- package/templates/agent-docs/deployment.md +28 -0
- package/templates/agent-docs/plugins.md +26 -4
- package/templates/agent-docs/reference.md +2 -2
- package/templates/agent-docs/security.md +18 -0
- package/templates/agent-docs/templates/apibackends.md +5 -5
- package/templates/agent-docs/templates/overview.md +1 -1
- package/templates/agent-docs/whats-new.md +278 -168
- package/templates/agent-docs/workflows.md +160 -42
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/README.md +2 -1
- package/templates/apps/api-durable/events/orders.event.ts +18 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +17 -15
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +6 -4
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/README.md +2 -2
- package/templates/apps/api-webhooks/events/orders.event.ts +35 -0
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/api-webhooks/template.json +1 -1
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/dist/apiBuild-UEM3QBke.js +0 -2
- package/dist/dbCommand-Cr__4ATv.js +0 -2
- package/dist/fileConventions-CBNPZVOV.js +0 -29
- package/dist/frameworkTableAssembly-DRJrAVr1.js +0 -2
- package/dist/inspect-BA67TF6v.js +0 -2
- package/dist/manifestBuild-i-fRHg_H.js +0 -2
- package/dist/serveCommand-C4gzDZzc.js +0 -1175
- 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-
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
},
|
|
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 (
|
|
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
|
-
},
|
|
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
|
-
],
|
|
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
|
|
96
|
+
for (let [n, i] of Ce(e, t).entries()) r.push(` ${n + 1}. ${i}`);
|
|
97
97
|
return r;
|
|
98
|
-
},
|
|
99
|
-
let a =
|
|
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
|
-
},
|
|
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 =
|
|
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(
|
|
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))),
|
|
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
|
-
},
|
|
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" }),
|
|
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 (
|
|
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
|
|
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()),
|
|
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
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
|
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
|
-
},
|
|
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))
|
|
530
|
+
for (let e of Object.values(t)) Ie(e) && n.push(e);
|
|
531
531
|
}
|
|
532
532
|
return n;
|
|
533
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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 =
|
|
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
|
-
},
|
|
559
|
+
}, q = async (e, t) => g.runPromise(t.pipe(g.provide(e))), J = async (e) => {
|
|
560
560
|
try {
|
|
561
|
-
return await
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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 =
|
|
642
|
+
let r = Ve[e.classification], i = e.reason ? ` [2m# ${e.reason}[0m` : "", a = e.blocked ? `\n [31m! ${e.blocked.fix}[0m` : "";
|
|
643
643
|
return ` ${r} ${n}${i}${a}`;
|
|
644
|
-
},
|
|
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
|
|
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
|
-
},
|
|
665
|
+
}, Y = async (e, t) => {
|
|
666
666
|
if (U() !== "postgres") return;
|
|
667
667
|
let n = oe, r;
|
|
668
668
|
try {
|
|
669
|
-
r = await
|
|
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 =
|
|
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
|
|
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
|
-
},
|
|
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(
|
|
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`),
|
|
696
|
-
},
|
|
697
|
-
let t =
|
|
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
|
-
},
|
|
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
|
-
},
|
|
710
|
-
let t = await
|
|
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
|
|
720
|
+
return Re(ze([
|
|
714
721
|
...t,
|
|
715
722
|
...x,
|
|
716
723
|
...n
|
|
717
724
|
]));
|
|
718
|
-
},
|
|
719
|
-
let t =
|
|
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
|
-
...
|
|
744
|
+
...T(),
|
|
738
745
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
739
746
|
});
|
|
740
|
-
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`),
|
|
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
|
|
754
|
+
live: await J(await K()),
|
|
748
755
|
dialect: U(),
|
|
749
|
-
...
|
|
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) : (
|
|
753
|
-
},
|
|
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
|
|
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
|
|
765
|
-
await
|
|
766
|
-
let o = await
|
|
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
|
|
777
|
+
live: await J(a),
|
|
771
778
|
dialect: U(),
|
|
772
|
-
...
|
|
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 =
|
|
777
|
-
if (u.summary.blocked > 0 && !c.acknowledged) return
|
|
778
|
-
if (c.acknowledged && L.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), u
|
|
779
|
-
|
|
780
|
-
|
|
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(() =>
|
|
794
|
+
replan: () => g.promise(() => J(a)).pipe(g.map((e) => E({
|
|
787
795
|
declared: i,
|
|
788
796
|
live: e,
|
|
789
797
|
dialect: U(),
|
|
790
|
-
...
|
|
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
|
|
797
|
-
},
|
|
798
|
-
let t = z(e), n =
|
|
799
|
-
if (r) return
|
|
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
|
|
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
|
|
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
|
|
813
|
+
await q(a, C(x, U()));
|
|
806
814
|
let s = E({
|
|
807
815
|
declared: i,
|
|
808
|
-
live: await
|
|
816
|
+
live: await J(a),
|
|
809
817
|
dialect: U(),
|
|
810
|
-
...
|
|
818
|
+
...T(),
|
|
811
819
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
812
820
|
});
|
|
813
|
-
|
|
814
|
-
let c =
|
|
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
|
|
817
|
-
|
|
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(() =>
|
|
832
|
+
replan: () => g.promise(() => J(a)).pipe(g.map((e) => E({
|
|
824
833
|
declared: i,
|
|
825
834
|
live: e,
|
|
826
835
|
dialect: U(),
|
|
827
|
-
...
|
|
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
|
|
835
|
-
},
|
|
836
|
-
let t = Math.max(1, Number(l(e, "--limit") ?? "20")), n = await
|
|
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(
|
|
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 ${
|
|
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
|
-
},
|
|
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
|
|
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(
|
|
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
|
|
885
|
+
let t = await q(n, b.SqlClient.pipe(g.flatMap((t) => t`
|
|
877
886
|
SELECT column_name FROM information_schema.columns
|
|
878
|
-
WHERE
|
|
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
|
|
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
|
|
901
|
+
let t = await q(n, b.SqlClient.pipe(g.flatMap((t) => t`
|
|
893
902
|
SELECT table_name FROM information_schema.tables
|
|
894
|
-
WHERE
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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
|
|
914
|
-
`))), s = await
|
|
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
|
|
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
|
|
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
|
|
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
|
-
},
|
|
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
|
|
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 =
|
|
946
|
+
let o = w(ce(a)), s = await K();
|
|
938
947
|
try {
|
|
939
|
-
let e = await
|
|
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
|
-
},
|
|
959
|
+
}, et = async (e) => {
|
|
951
960
|
try {
|
|
952
|
-
let t =
|
|
953
|
-
await
|
|
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
|
-
},
|
|
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
|
-
},
|
|
975
|
-
let t = e.includes("--accept"), n = await
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
-
...
|
|
1000
|
+
...T()
|
|
992
1001
|
}).operations;
|
|
993
1002
|
} catch {
|
|
994
1003
|
return;
|
|
995
1004
|
}
|
|
996
1005
|
})();
|
|
997
1006
|
if (t) {
|
|
998
|
-
let e =
|
|
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
|
|
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 ${
|
|
1017
|
-
},
|
|
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
|
|
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
|
-
},
|
|
1030
|
-
let t = z(e), n = await
|
|
1031
|
-
await
|
|
1032
|
-
let i = await
|
|
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
|
-
},
|
|
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
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
`,
|
|
1093
|
-
plan:
|
|
1094
|
-
apply:
|
|
1095
|
-
plans:
|
|
1096
|
-
drift:
|
|
1097
|
-
files:
|
|
1098
|
-
"rollback-file":
|
|
1099
|
-
migrate:
|
|
1100
|
-
rollback:
|
|
1101
|
-
status:
|
|
1102
|
-
seed:
|
|
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 =
|
|
1105
|
-
return await v.mkdir(l, { recursive: !0 }), await v.writeFile(d,
|
|
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:
|
|
1108
|
-
"restore-snapshot":
|
|
1109
|
-
"gc-snapshots":
|
|
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
|
|
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
|
|
1131
|
-
for (let e of
|
|
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
|
|
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
|
-
},
|
|
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(
|
|
1150
|
+
for (let e of Object.keys($)) console.error(` ${e}`);
|
|
1142
1151
|
return 1;
|
|
1143
1152
|
}
|
|
1144
|
-
let r =
|
|
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 {
|
|
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 };
|