@voltro/cli 0.21.0 → 0.22.1
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 +316 -0
- package/dist/apiBuild-COyPDf3R.js +2 -0
- package/dist/{apiBuild-BHOtsQkp.js → apiBuild-DiWxVz-M.js} +2 -2
- package/dist/bin.js +3 -3
- package/dist/{commands-BZoimvvS.js → commands-BxRaIOBG.js} +1548 -1246
- package/dist/dbCommand-CO3eSAZR.js +2 -0
- package/dist/{dbCommand-CDBbZ-Ac.js → dbCommand-DVASmZj2.js} +295 -253
- package/dist/{dev-DKb92NYX.js → dev-BRiPgbKw.js} +1738 -1702
- package/dist/{dev-CZQ73Yoh.js → dev-Dd3EZzj5.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/inspect-BA67TF6v.js +2 -0
- package/dist/inspect-_ldwsAwH.js +945 -0
- package/dist/{inspectMetrics-DMjWBQif.js → inspectMetrics-9ZSuDeqD.js} +148 -115
- package/dist/{manifestBuild-P9yuCY2d.js → manifestBuild-Bs1Uw22_.js} +1 -1
- package/dist/manifestBuild-i-fRHg_H.js +2 -0
- package/dist/{serveCommand-qHJCs0cX.js → serveCommand-DXJOARkO.js} +305 -306
- package/dist/serveEntry.js +2 -2
- package/dist/{start-B1x6Z60h.js → start-CsIjcOi-.js} +3 -3
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +118 -5
- package/templates/agent-docs/data.md +58 -0
- package/templates/agent-docs/database/migrations.md +142 -20
- package/templates/agent-docs/plugins.md +1 -1
- package/templates/agent-docs/schema-driven-ui.md +2 -2
- package/templates/agent-docs/templates/apibackends.md +1 -1
- package/templates/agent-docs/whats-new.md +32 -114
- 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/package.json +8 -8
- 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/package.json +9 -9
- 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-K7M6Rt0U.js +0 -2
- package/dist/dbCommand-oADAj6ez.js +0 -2
- package/dist/inspect-DcZ04OME.js +0 -2
- package/dist/inspect-Dwx0_tUj.js +0 -921
- package/dist/manifestBuild-D1MzJAiQ.js +0 -2
|
@@ -5,21 +5,21 @@ import { a as s, n as c, t as l } from "./cliArgs-qdZSElM3.js";
|
|
|
5
5
|
import { basename as u, join as d, relative as f, resolve as p } from "node:path";
|
|
6
6
|
import { pathToFileURL as m } from "node:url";
|
|
7
7
|
import { Effect as h } from "effect";
|
|
8
|
-
import { createLogger as
|
|
9
|
-
import { promises as
|
|
10
|
-
import { InMemoryDataStore as
|
|
11
|
-
import { SqlClient as
|
|
12
|
-
import { actorsTable as
|
|
13
|
-
import { createHash as
|
|
14
|
-
import { applyPlan as
|
|
8
|
+
import { createLogger as ee } from "@voltro/logger";
|
|
9
|
+
import { promises as g, writeSync as te } from "node:fs";
|
|
10
|
+
import { InMemoryDataStore as ne } from "@voltro/runtime";
|
|
11
|
+
import { SqlClient as _ } from "@effect/sql";
|
|
12
|
+
import { actorsTable as re, frameworkTables as v, isMigrationDefinition as y, registerCoreTables as ie } from "@voltro/database";
|
|
13
|
+
import { createHash as ae } from "node:crypto";
|
|
14
|
+
import { applyPlan as b, applySchema as x, declaredSnapshot as oe, destructiveScope as se, fingerprintSchema as S, ignoreTablesFromEnv as C, introspectSchema as ce, planMigrations as w, rollbackFileBasedMigration as le, runFileBasedMigrations as ue, shortFingerprint as T, squashMigrationPlans as de, unblockLossy as fe } from "@voltro/database/sql";
|
|
15
15
|
//#region src/cliError.ts
|
|
16
|
-
var
|
|
16
|
+
var E = class extends Error {
|
|
17
17
|
isCliError = !0;
|
|
18
18
|
code;
|
|
19
19
|
constructor(e, t = 1) {
|
|
20
20
|
super(e), this.name = "CliError", this.code = t;
|
|
21
21
|
}
|
|
22
|
-
},
|
|
22
|
+
}, pe = (e) => typeof e == "object" && !!e && e.isCliError === !0, D = /\.migration\.ts$/, O = async (e, t) => {
|
|
23
23
|
let n = [];
|
|
24
24
|
for (let r of e) {
|
|
25
25
|
let e;
|
|
@@ -29,7 +29,7 @@ var O = class extends Error {
|
|
|
29
29
|
t.warn("migration: failed to import", { file: r }, e);
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
|
-
let i = (e.default &&
|
|
32
|
+
let i = (e.default && y(e.default) ? e.default : void 0) ?? Object.values(e).find(y);
|
|
33
33
|
if (!i) {
|
|
34
34
|
t.warn("migration: no defineMigration export", { file: r });
|
|
35
35
|
continue;
|
|
@@ -39,7 +39,7 @@ var O = class extends Error {
|
|
|
39
39
|
idInFile: i.id,
|
|
40
40
|
idFromFile: u(r).replace(/\.migration\.ts$/, "")
|
|
41
41
|
});
|
|
42
|
-
let a = await
|
|
42
|
+
let a = await g.readFile(r, "utf8").catch(() => ""), o = ae("sha256").update(a).digest("hex").slice(0, 16);
|
|
43
43
|
n.push({
|
|
44
44
|
file: r,
|
|
45
45
|
definition: i,
|
|
@@ -47,14 +47,14 @@ var O = class extends Error {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
|
|
50
|
-
},
|
|
50
|
+
}, k = (e) => {
|
|
51
51
|
let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
|
|
52
|
-
return h.runPromise(
|
|
53
|
-
},
|
|
54
|
-
let e = yield* (yield*
|
|
52
|
+
return h.runPromise(x(v, n).pipe(h.provide(e)));
|
|
53
|
+
}, A = (e) => h.runPromise(h.gen(function* () {
|
|
54
|
+
let e = yield* (yield* _.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(h.orElseSucceed(() => []));
|
|
55
55
|
return new Map(e.map((e) => [e.id, e]));
|
|
56
|
-
}).pipe(h.provide(e))),
|
|
57
|
-
yield* (yield*
|
|
56
|
+
}).pipe(h.provide(e))), j = (e, t) => h.runPromise(h.gen(function* () {
|
|
57
|
+
yield* (yield* _.SqlClient)`
|
|
58
58
|
INSERT INTO "_voltro_migrations"
|
|
59
59
|
(id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
|
|
60
60
|
VALUES
|
|
@@ -69,14 +69,14 @@ var O = class extends Error {
|
|
|
69
69
|
"errorStack" = EXCLUDED."errorStack",
|
|
70
70
|
"updatedAt" = now()
|
|
71
71
|
`;
|
|
72
|
-
}).pipe(h.provide(e))),
|
|
72
|
+
}).pipe(h.provide(e))), me = async (e, t, n) => {
|
|
73
73
|
if (e.length === 0) return {
|
|
74
74
|
applied: [],
|
|
75
75
|
failed: null,
|
|
76
76
|
skipped: []
|
|
77
77
|
};
|
|
78
|
-
await
|
|
79
|
-
let r = await
|
|
78
|
+
await k(t);
|
|
79
|
+
let r = await A(t), i = [], a = [], o = null;
|
|
80
80
|
for (let s of e) {
|
|
81
81
|
let e = r.get(s.definition.id);
|
|
82
82
|
if (e && e.status === "applied") {
|
|
@@ -97,7 +97,7 @@ var O = class extends Error {
|
|
|
97
97
|
name: s.definition.name
|
|
98
98
|
});
|
|
99
99
|
let c = Date.now(), l = [];
|
|
100
|
-
await
|
|
100
|
+
await j(t, {
|
|
101
101
|
id: s.definition.id,
|
|
102
102
|
name: s.definition.name,
|
|
103
103
|
hash: s.hash,
|
|
@@ -138,7 +138,7 @@ var O = class extends Error {
|
|
|
138
138
|
...a.stack ? { stack: a.stack } : {}
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
-
o = f, i.push(f), await
|
|
141
|
+
o = f, i.push(f), await j(t, {
|
|
142
142
|
id: s.definition.id,
|
|
143
143
|
name: s.definition.name,
|
|
144
144
|
hash: s.hash,
|
|
@@ -157,7 +157,7 @@ var O = class extends Error {
|
|
|
157
157
|
status: "applied",
|
|
158
158
|
durationMs: Date.now() - c,
|
|
159
159
|
steps: l
|
|
160
|
-
}), await
|
|
160
|
+
}), await j(t, {
|
|
161
161
|
id: s.definition.id,
|
|
162
162
|
name: s.definition.name,
|
|
163
163
|
hash: s.hash,
|
|
@@ -177,9 +177,9 @@ var O = class extends Error {
|
|
|
177
177
|
failed: o,
|
|
178
178
|
skipped: a
|
|
179
179
|
};
|
|
180
|
-
},
|
|
181
|
-
await
|
|
182
|
-
let i = await
|
|
180
|
+
}, he = async (e, t, n, r = {}) => {
|
|
181
|
+
await k(t);
|
|
182
|
+
let i = await A(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
|
|
183
183
|
if (a.length === 0) return n.info("migration: nothing to roll back"), {
|
|
184
184
|
applied: [],
|
|
185
185
|
failed: null,
|
|
@@ -232,7 +232,7 @@ var O = class extends Error {
|
|
|
232
232
|
status: "applied",
|
|
233
233
|
durationMs: Date.now() - r,
|
|
234
234
|
steps: i
|
|
235
|
-
}), await
|
|
235
|
+
}), await j(t, {
|
|
236
236
|
id: e.definition.id,
|
|
237
237
|
name: e.definition.name,
|
|
238
238
|
hash: e.hash,
|
|
@@ -248,9 +248,9 @@ var O = class extends Error {
|
|
|
248
248
|
failed: c,
|
|
249
249
|
skipped: []
|
|
250
250
|
};
|
|
251
|
-
},
|
|
252
|
-
await
|
|
253
|
-
let n = await
|
|
251
|
+
}, M = async (e, t) => {
|
|
252
|
+
await k(t);
|
|
253
|
+
let n = await A(t);
|
|
254
254
|
return e.map((e) => {
|
|
255
255
|
let t = n.get(e.definition.id);
|
|
256
256
|
return t ? {
|
|
@@ -267,7 +267,7 @@ var O = class extends Error {
|
|
|
267
267
|
hashMatches: !0
|
|
268
268
|
};
|
|
269
269
|
});
|
|
270
|
-
},
|
|
270
|
+
}, N = (e) => {
|
|
271
271
|
if (!e.failed) return "";
|
|
272
272
|
let t = e.failed, n = [
|
|
273
273
|
`✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
|
|
@@ -280,7 +280,7 @@ var O = class extends Error {
|
|
|
280
280
|
""
|
|
281
281
|
];
|
|
282
282
|
return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
|
|
283
|
-
},
|
|
283
|
+
}, P = ee({ scope: "voltro:db" }), ge = /* @__PURE__ */ new Set([
|
|
284
284
|
"node_modules",
|
|
285
285
|
"dist",
|
|
286
286
|
"build",
|
|
@@ -288,17 +288,17 @@ var O = class extends Error {
|
|
|
288
288
|
".vite",
|
|
289
289
|
".next",
|
|
290
290
|
".git"
|
|
291
|
-
]),
|
|
291
|
+
]), F = async (e, t) => {
|
|
292
292
|
let n = [], r = async (e) => {
|
|
293
|
-
let i = await
|
|
293
|
+
let i = await g.readdir(e, { withFileTypes: !0 }).catch(() => []);
|
|
294
294
|
for (let a of i) {
|
|
295
|
-
if (
|
|
295
|
+
if (ge.has(a.name)) continue;
|
|
296
296
|
let i = d(e, a.name);
|
|
297
297
|
a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
|
|
298
298
|
}
|
|
299
299
|
};
|
|
300
300
|
return await r(e), n;
|
|
301
|
-
},
|
|
301
|
+
}, I = (e) => p(e.find((e) => !e.startsWith("-")) ?? process.cwd()), L = () => {
|
|
302
302
|
let e = process.env.DB_MAX_CONNECTIONS ?? process.env.PG_MAX_CONNECTIONS, t = e !== void 0 && e !== "" ? Number(e) : NaN, n = Number.isInteger(t) && t > 0 ? { maxConnections: t } : {}, r = process.env.DB_SCHEMA, i = r && r.length > 0 ? { schema: r } : {}, a = process.env.DB_DIRECT_URL ?? process.env.DB_MIGRATE_URL ?? process.env.DB_URL ?? process.env.DB_PRIMARY_URL;
|
|
303
303
|
return a ? {
|
|
304
304
|
url: a,
|
|
@@ -313,63 +313,63 @@ var O = class extends Error {
|
|
|
313
313
|
...n,
|
|
314
314
|
...i
|
|
315
315
|
};
|
|
316
|
-
},
|
|
316
|
+
}, R = (e) => {
|
|
317
317
|
let t = e;
|
|
318
318
|
if (typeof t != "object" || !t) return !1;
|
|
319
319
|
if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
|
|
320
320
|
let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
|
|
321
321
|
return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
|
|
322
|
-
},
|
|
322
|
+
}, z = "Live-DB introspection got a corrupt / misaligned response from the database. This happens when `voltro db plan` / `apply` / `drift` / `migrate` runs through a connection POOLER (Supabase Supavisor / PgBouncer) on a LARGE schema — the pooler mis-frames a big catalog response. The introspection already batches by table to keep responses small; if a batch still trips it:\n 1. Lower the batch: set `VOLTRO_INTROSPECT_BATCH` to a smaller number of tables (default 20 — try 5 or 1). This keeps `db plan` on the normal pooler, no direct connection needed.\n 2. Or use a DIRECT (non-pooler) connection: set `DB_DIRECT_URL` (or `DB_MIGRATE_URL`) to the direct database URL — the runtime keeps `DB_URL`, so only the migration path is affected. (Supabase: direct = `db.<project-ref>.supabase.co` [IPv6-only on newer projects]; pooler = `aws-0-<region>.pooler.supabase.com`.)", B = () => {
|
|
323
323
|
let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
324
|
-
if (e === "memory") throw new
|
|
324
|
+
if (e === "memory") throw new E("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
|
|
325
325
|
return e;
|
|
326
|
-
},
|
|
327
|
-
let t =
|
|
328
|
-
if (r.length === 0) return
|
|
326
|
+
}, V = async () => (await r(B())).makeSqlLayer(L()), _e = async (e) => {
|
|
327
|
+
let t = I(e), n = e.includes("--dry-run"), r = await O(await F(t, D), P);
|
|
328
|
+
if (r.length === 0) return P.info("no migrations found", { root: t }), 0;
|
|
329
329
|
if (n) {
|
|
330
330
|
let e = /* @__PURE__ */ new Set();
|
|
331
331
|
try {
|
|
332
|
-
let t = await
|
|
332
|
+
let t = await M(r, await V());
|
|
333
333
|
e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
|
|
334
334
|
} catch {
|
|
335
|
-
|
|
335
|
+
P.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
|
|
336
336
|
}
|
|
337
337
|
let t = r.filter((t) => !e.has(t.definition.id));
|
|
338
|
-
if (t.length === 0) return
|
|
339
|
-
|
|
338
|
+
if (t.length === 0) return P.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
|
|
339
|
+
P.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
|
|
340
340
|
for (let e of t) console.log(` pending ${e.definition.id}`);
|
|
341
341
|
return 0;
|
|
342
342
|
}
|
|
343
|
-
let i = await
|
|
344
|
-
return i.failed ? (process.stderr.write(
|
|
343
|
+
let i = await me(r, await V(), P);
|
|
344
|
+
return i.failed ? (process.stderr.write(N(i) + "\n"), 1) : (P.info("migrate complete", {
|
|
345
345
|
applied: i.applied.length,
|
|
346
346
|
skipped: i.skipped.length
|
|
347
347
|
}), 0);
|
|
348
|
-
},
|
|
349
|
-
let t =
|
|
350
|
-
if (r.length === 0) return
|
|
351
|
-
let i = await
|
|
352
|
-
return i.failed ? (process.stderr.write(
|
|
353
|
-
},
|
|
354
|
-
let t = await
|
|
348
|
+
}, ve = async (e) => {
|
|
349
|
+
let t = I(e), n = c(e, "--to"), r = await O(await F(t, D), P);
|
|
350
|
+
if (r.length === 0) return P.info("no migrations found", { root: t }), 0;
|
|
351
|
+
let i = await he(r, await V(), P, n ? { to: n } : {});
|
|
352
|
+
return i.failed ? (process.stderr.write(N(i) + "\n"), 1) : (P.info("rollback complete", { reverted: i.applied.length }), 0);
|
|
353
|
+
}, ye = async (e) => {
|
|
354
|
+
let t = await O(await F(I(e), D), P);
|
|
355
355
|
if (t.length === 0) return console.log("no migrations found"), 0;
|
|
356
|
-
let n = await
|
|
356
|
+
let n = await M(t, await V());
|
|
357
357
|
for (let e of n) {
|
|
358
358
|
let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
|
|
359
359
|
console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
|
|
360
360
|
}
|
|
361
361
|
return 0;
|
|
362
|
-
},
|
|
362
|
+
}, be = async (e) => {
|
|
363
363
|
let t = (c(e, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
|
|
364
|
-
if (t === "memory") return
|
|
365
|
-
store: new
|
|
364
|
+
if (t === "memory") return P.warn("voltro db seed: using in-memory store (explicit opt-in via --store memory / DB_DIALECT=memory)"), P.warn(" rows written by this run will NOT persist beyond the process. set DB_DIALECT=postgres or unset to seed the real DB."), {
|
|
365
|
+
store: new ne(),
|
|
366
366
|
dispose: async () => {},
|
|
367
367
|
dialectId: "memory"
|
|
368
368
|
};
|
|
369
369
|
let n = await r(t);
|
|
370
|
-
|
|
370
|
+
P.info(`voltro db seed: target store=${n.id} — rows will persist`);
|
|
371
371
|
let i = await n.makeStore({
|
|
372
|
-
sqlLayer: n.makeSqlLayer(
|
|
372
|
+
sqlLayer: n.makeSqlLayer(L()),
|
|
373
373
|
changeStrategy: "inline"
|
|
374
374
|
}), a = i;
|
|
375
375
|
return {
|
|
@@ -379,77 +379,77 @@ var O = class extends Error {
|
|
|
379
379
|
},
|
|
380
380
|
dialectId: n.id
|
|
381
381
|
};
|
|
382
|
-
},
|
|
383
|
-
let a =
|
|
384
|
-
if (l.length === 0) return
|
|
385
|
-
let { store: u, dispose: d, dialectId: f } = await
|
|
382
|
+
}, xe = async (r) => {
|
|
383
|
+
let a = I(r), o = c(r, "--id"), s = c(r, "--lifecycle"), l = await t(await F(a, i), P);
|
|
384
|
+
if (l.length === 0) return P.info("no seeds found", { root: a }), 0;
|
|
385
|
+
let { store: u, dispose: d, dialectId: f } = await be(r);
|
|
386
386
|
try {
|
|
387
|
-
return o ? +((await e(l, o, u,
|
|
387
|
+
return o ? +((await e(l, o, u, P)).status === "failed") : +((await n((s ? l.filter((e) => e.definition.lifecycle === s) : l.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
|
|
388
388
|
...e,
|
|
389
389
|
definition: {
|
|
390
390
|
...e.definition,
|
|
391
391
|
lifecycle: "boot"
|
|
392
392
|
}
|
|
393
|
-
})), u,
|
|
393
|
+
})), u, P, { force: !0 })).filter((e) => e.status === "failed").length > 0);
|
|
394
394
|
} finally {
|
|
395
395
|
await d().catch((e) => {
|
|
396
|
-
|
|
396
|
+
P.warn("voltro db seed: store dispose failed", { error: String(e) });
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
|
-
},
|
|
400
|
-
let t = await
|
|
399
|
+
}, Se = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", H = async (e) => {
|
|
400
|
+
let t = await F(e, o), n = [];
|
|
401
401
|
for (let r of t) {
|
|
402
402
|
let t;
|
|
403
403
|
try {
|
|
404
404
|
t = await import(m(r).href);
|
|
405
405
|
} catch (t) {
|
|
406
|
-
|
|
406
|
+
P.warn("db: failed to import schema file", {
|
|
407
407
|
file: f(e, r),
|
|
408
408
|
error: String(t)
|
|
409
409
|
});
|
|
410
410
|
continue;
|
|
411
411
|
}
|
|
412
|
-
for (let e of Object.values(t))
|
|
412
|
+
for (let e of Object.values(t)) Se(e) && n.push(e);
|
|
413
413
|
}
|
|
414
414
|
return n;
|
|
415
|
-
},
|
|
415
|
+
}, U = (e) => {
|
|
416
416
|
let t = new Map(e.map((e) => [e.tableName, e]));
|
|
417
|
-
t.has("actors") || t.set("actors",
|
|
417
|
+
t.has("actors") || t.set("actors", re);
|
|
418
418
|
let n = t.get("actors"), r = t.get("tenants");
|
|
419
|
-
return
|
|
419
|
+
return ie({
|
|
420
420
|
actors: () => n,
|
|
421
421
|
...r === void 0 ? {} : { tenants: () => r }
|
|
422
422
|
}), [...t.values()];
|
|
423
|
-
},
|
|
423
|
+
}, Ce = (e) => {
|
|
424
424
|
let t = /* @__PURE__ */ new Map();
|
|
425
425
|
for (let n of e) {
|
|
426
426
|
let e = n.tableName;
|
|
427
427
|
e !== void 0 && !t.has(e) && t.set(e, n);
|
|
428
428
|
}
|
|
429
429
|
return [...t.values()];
|
|
430
|
-
},
|
|
431
|
-
let t =
|
|
430
|
+
}, W = (e) => {
|
|
431
|
+
let t = se(process.env.VOLTRO_DESTRUCTIVE_OK);
|
|
432
432
|
if (t === void 0 || !e.operations.filter((e) => e.blocked).every((e) => e.classification === "lossy")) return {
|
|
433
433
|
plan: e,
|
|
434
434
|
acknowledged: !1
|
|
435
435
|
};
|
|
436
|
-
let n =
|
|
436
|
+
let n = fe(e, t);
|
|
437
437
|
return {
|
|
438
438
|
plan: n,
|
|
439
439
|
acknowledged: !n.operations.some((e) => e.blocked)
|
|
440
440
|
};
|
|
441
|
-
},
|
|
441
|
+
}, G = async (e, t) => h.runPromise(t.pipe(h.provide(e))), K = async (e) => {
|
|
442
442
|
try {
|
|
443
|
-
return await
|
|
443
|
+
return await G(e, _.SqlClient.pipe(h.flatMap(ce)));
|
|
444
444
|
} catch (e) {
|
|
445
445
|
let t = String(e?.message ?? e);
|
|
446
|
-
if (
|
|
447
|
-
let n = /* @__PURE__ */ Error(`${
|
|
446
|
+
if (R(e) || /connection error/i.test(t)) {
|
|
447
|
+
let n = /* @__PURE__ */ Error(`${z}\n\nOriginal error: ${t}`);
|
|
448
448
|
throw n.cause = e, n;
|
|
449
449
|
}
|
|
450
450
|
throw e;
|
|
451
451
|
}
|
|
452
|
-
},
|
|
452
|
+
}, we = {
|
|
453
453
|
safe: "\x1B[32m✓\x1B[0m",
|
|
454
454
|
"needs-default": "\x1B[32m⊕\x1B[0m",
|
|
455
455
|
"needs-backfill": "\x1B[33m⊕\x1B[0m",
|
|
@@ -457,7 +457,7 @@ var O = class extends Error {
|
|
|
457
457
|
lossy: "\x1B[31m✗\x1B[0m",
|
|
458
458
|
"online-required": "\x1B[36m◷\x1B[0m",
|
|
459
459
|
"multi-step": "\x1B[35m⇉\x1B[0m"
|
|
460
|
-
},
|
|
460
|
+
}, Te = (e) => {
|
|
461
461
|
let t = e.op, n;
|
|
462
462
|
switch (t.kind) {
|
|
463
463
|
case "create-table":
|
|
@@ -490,6 +490,12 @@ var O = class extends Error {
|
|
|
490
490
|
case "drop-index":
|
|
491
491
|
n = `DROP INDEX ${t.index}`;
|
|
492
492
|
break;
|
|
493
|
+
case "rename-index":
|
|
494
|
+
n = `ALTER INDEX ${t.from} RENAME TO ${t.to}`;
|
|
495
|
+
break;
|
|
496
|
+
case "rename-table":
|
|
497
|
+
n = `ALTER TABLE ${t.from} RENAME TO ${t.to}`;
|
|
498
|
+
break;
|
|
493
499
|
case "add-unique":
|
|
494
500
|
n = `ALTER TABLE ${t.table} ADD UNIQUE (${t.column})`;
|
|
495
501
|
break;
|
|
@@ -515,150 +521,147 @@ var O = class extends Error {
|
|
|
515
521
|
n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
|
|
516
522
|
break;
|
|
517
523
|
}
|
|
518
|
-
let r =
|
|
524
|
+
let r = we[e.classification], i = e.reason ? ` [2m# ${e.reason}[0m` : "", a = e.blocked ? `\n [31m! ${e.blocked.fix}[0m` : "";
|
|
519
525
|
return ` ${r} ${n}${i}${a}`;
|
|
520
|
-
},
|
|
526
|
+
}, q = (e) => {
|
|
521
527
|
let t = e.summary, n = e.operations.length;
|
|
522
528
|
if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
|
|
523
529
|
console.log(" (schema is up to date)\n");
|
|
524
530
|
return;
|
|
525
531
|
}
|
|
526
|
-
for (let t of e.operations) console.log(
|
|
532
|
+
for (let t of e.operations) console.log(Te(t));
|
|
527
533
|
console.log(), console.log(` safe: ${t.safe} needs-default: ${t.needsDefault} needs-backfill: ${t.needsBackfill}`), console.log(` rename: ${t.needsRenameAnnotation} lossy: ${t.lossy} blocked: ${t.blocked}`), console.log(` fingerprint: ${e.fromFingerprint.slice(0, 16)} → ${e.toFingerprint.slice(0, 16)}\n`);
|
|
528
|
-
},
|
|
534
|
+
}, J = (e) => {
|
|
529
535
|
let t = c(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
|
|
530
536
|
if (t === void 0) return;
|
|
531
537
|
let n = Number(t);
|
|
532
538
|
if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
|
|
533
539
|
return n;
|
|
534
|
-
},
|
|
535
|
-
let t =
|
|
536
|
-
if (
|
|
540
|
+
}, Y = async (e) => {
|
|
541
|
+
let t = await H(e);
|
|
542
|
+
if (t.length === 0) return;
|
|
543
|
+
let n = await a({ root: e });
|
|
544
|
+
return U(Ce([
|
|
545
|
+
...t,
|
|
546
|
+
...v,
|
|
547
|
+
...n
|
|
548
|
+
]));
|
|
549
|
+
}, Ee = async (e) => {
|
|
550
|
+
let t = J(e), n = I(e), r = await Y(n);
|
|
551
|
+
if (r === void 0) return P.warn("db plan: no schema files found", {
|
|
537
552
|
root: n,
|
|
538
553
|
hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
|
|
539
554
|
}), 1;
|
|
540
|
-
let i =
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
])), s = c(e, "--against");
|
|
545
|
-
if (s) {
|
|
546
|
-
let n = s, r = c(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, i = { accept: "application/json" };
|
|
547
|
-
r && (i.authorization = `Bearer ${r}`);
|
|
555
|
+
let i = c(e, "--against");
|
|
556
|
+
if (i) {
|
|
557
|
+
let n = i, a = c(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, o = { accept: "application/json" };
|
|
558
|
+
a && (o.authorization = `Bearer ${a}`);
|
|
548
559
|
try {
|
|
549
|
-
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`,
|
|
550
|
-
if (!
|
|
551
|
-
let a = (await
|
|
552
|
-
if (!a) return
|
|
553
|
-
let s =
|
|
554
|
-
declared:
|
|
560
|
+
let e = n.endsWith("/_voltro/inspect/migrations") ? n : `${n.replace(/\/+$/, "")}/_voltro/inspect/migrations`, i = await fetch(e, { headers: o });
|
|
561
|
+
if (!i.ok) return P.error(`db plan --against: remote returned ${i.status}`, { url: e }), 1;
|
|
562
|
+
let a = (await i.json()).drift?.liveSnapshot;
|
|
563
|
+
if (!a) return P.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
|
|
564
|
+
let s = w({
|
|
565
|
+
declared: r,
|
|
555
566
|
live: a,
|
|
556
|
-
dialect:
|
|
557
|
-
...
|
|
567
|
+
dialect: B(),
|
|
568
|
+
...C(),
|
|
558
569
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
559
570
|
});
|
|
560
|
-
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`),
|
|
571
|
+
return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), q(s), s.summary.blocked > 0 ? 2 : 0;
|
|
561
572
|
} catch (e) {
|
|
562
|
-
return
|
|
573
|
+
return P.error(`db plan --against: ${e.message}`), 1;
|
|
563
574
|
}
|
|
564
575
|
}
|
|
565
|
-
let
|
|
566
|
-
declared:
|
|
567
|
-
live: await
|
|
568
|
-
dialect:
|
|
569
|
-
...
|
|
576
|
+
let a = w({
|
|
577
|
+
declared: r,
|
|
578
|
+
live: await K(await V()),
|
|
579
|
+
dialect: B(),
|
|
580
|
+
...C(),
|
|
570
581
|
...t === void 0 ? {} : { onlineAfter: t }
|
|
571
582
|
});
|
|
572
|
-
return e.includes("--json") ? (
|
|
573
|
-
},
|
|
583
|
+
return e.includes("--json") ? (te(1, `${JSON.stringify(a, null, 2)}\n`), a.summary.blocked > 0 ? 2 : 0) : (q(a), a.summary.blocked > 0 ? 2 : 0);
|
|
584
|
+
}, De = async (e, t, n) => {
|
|
574
585
|
let r;
|
|
575
586
|
try {
|
|
576
|
-
let t = await
|
|
587
|
+
let t = await g.readFile(p(e), "utf8");
|
|
577
588
|
r = JSON.parse(t);
|
|
578
589
|
} catch (t) {
|
|
579
|
-
return
|
|
590
|
+
return P.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
|
|
580
591
|
}
|
|
581
|
-
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return
|
|
582
|
-
let i = await
|
|
583
|
-
if (i
|
|
584
|
-
let
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
declared: s,
|
|
592
|
-
live: await Y(l),
|
|
593
|
-
dialect: H(),
|
|
594
|
-
...T()
|
|
592
|
+
if (!Array.isArray(r?.operations) || typeof r?.fromFingerprint != "string" || typeof r?.toFingerprint != "string") return P.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
|
|
593
|
+
let i = await Y(n);
|
|
594
|
+
if (i === void 0) return P.warn("db apply --plan: no schema files found", { root: n }), 1;
|
|
595
|
+
let a = await V();
|
|
596
|
+
await G(a, x(v, B()));
|
|
597
|
+
let o = w({
|
|
598
|
+
declared: i,
|
|
599
|
+
live: await K(a),
|
|
600
|
+
dialect: B(),
|
|
601
|
+
...C()
|
|
595
602
|
});
|
|
596
|
-
if (
|
|
597
|
-
if (
|
|
598
|
-
let
|
|
599
|
-
if (
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
let
|
|
603
|
-
declared:
|
|
603
|
+
if (o.fromFingerprint !== r.fromFingerprint) return P.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${r.fromFingerprint.slice(0, 16)} live=${o.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
|
|
604
|
+
if (o.toFingerprint !== r.toFingerprint) return P.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${r.toFingerprint.slice(0, 16)} declared=${o.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
|
|
605
|
+
let s = W(o), l = s.plan;
|
|
606
|
+
if (l.summary.blocked > 0 && !s.acknowledged) return q(l), P.error(`db apply --plan: refusing — ${o.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (P.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), P.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
607
|
+
if (s.acknowledged && P.warn("db apply --plan: proceeding with acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)"), l.operations.length === 0) return P.info("db apply --plan: schema is up to date — nothing to apply"), 0;
|
|
608
|
+
q(l);
|
|
609
|
+
let u = (process.env.NODE_ENV ?? "dev").toLowerCase(), d = c(t, "--note"), f = await G(a, _.SqlClient.pipe(h.flatMap((e) => b(e, l, {
|
|
610
|
+
declared: i,
|
|
604
611
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
|
|
605
|
-
environment:
|
|
612
|
+
environment: u === "production" ? "prod" : u === "staging" ? "staging" : "dev",
|
|
606
613
|
source: "file",
|
|
607
|
-
...
|
|
608
|
-
replan: () => h.promise(() =>
|
|
609
|
-
declared:
|
|
614
|
+
...d ? { notes: d } : {},
|
|
615
|
+
replan: () => h.promise(() => K(a)).pipe(h.map((e) => w({
|
|
616
|
+
declared: i,
|
|
610
617
|
live: e,
|
|
611
|
-
dialect:
|
|
612
|
-
...
|
|
618
|
+
dialect: B(),
|
|
619
|
+
...C()
|
|
613
620
|
})))
|
|
614
621
|
}))));
|
|
615
|
-
return
|
|
616
|
-
id:
|
|
617
|
-
fingerprint:
|
|
622
|
+
return P.info(`db apply --plan: applied ${f.appliedOps} op(s) in ${f.durationMs}ms`, {
|
|
623
|
+
id: f.id,
|
|
624
|
+
fingerprint: f.fingerprint.slice(0, 16)
|
|
618
625
|
}), 0;
|
|
619
|
-
},
|
|
620
|
-
let t =
|
|
621
|
-
if (r) return
|
|
622
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
623
|
-
let i = await
|
|
624
|
-
if (i
|
|
625
|
-
let
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
declared: s,
|
|
633
|
-
live: await Y(l),
|
|
634
|
-
dialect: H(),
|
|
635
|
-
...T(),
|
|
626
|
+
}, Oe = async (e) => {
|
|
627
|
+
let t = I(e), n = J(e), r = c(e, "--plan");
|
|
628
|
+
if (r) return De(r, e, t);
|
|
629
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return P.error("voltro db apply: refusing — auto-apply on prod is not allowed."), console.error("In production, schema changes go through `voltro db plan --json > plan.json`"), console.error("then `voltro db apply --plan plan.json` as an explicit step in your deploy pipeline."), 3;
|
|
630
|
+
let i = await Y(t);
|
|
631
|
+
if (i === void 0) return P.warn("db apply: no schema files found", { root: t }), 1;
|
|
632
|
+
let a = await V();
|
|
633
|
+
await G(a, x(v, B()));
|
|
634
|
+
let o = w({
|
|
635
|
+
declared: i,
|
|
636
|
+
live: await K(a),
|
|
637
|
+
dialect: B(),
|
|
638
|
+
...C(),
|
|
636
639
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
637
640
|
});
|
|
638
|
-
|
|
639
|
-
let
|
|
640
|
-
if (
|
|
641
|
-
if (
|
|
642
|
-
let
|
|
643
|
-
declared:
|
|
641
|
+
q(o);
|
|
642
|
+
let s = W(o), l = s.plan;
|
|
643
|
+
if (l.summary.blocked > 0 && !s.acknowledged) return P.error(`db apply: refusing — ${l.summary.blocked} blocked operation(s). See the fix hints above.`), process.env.VOLTRO_DESTRUCTIVE_OK !== void 0 && (P.error(" VOLTRO_DESTRUCTIVE_OK is set but did not cover this plan — it only relaxes"), P.error(" `lossy` operations, and only the tables it names. Anything else stays refused.")), 2;
|
|
644
|
+
if (s.acknowledged && P.warn(`db apply: proceeding with ${o.summary.blocked} acknowledged lossy operation(s) (VOLTRO_DESTRUCTIVE_OK)`), l.operations.length === 0) return P.info("db apply: schema is up to date — nothing to apply"), await Ne(a), c(e, "--note") !== void 0 && (P.warn("db apply: --note was ignored — an empty plan writes no history row to attach it to."), console.error("To record that the CURRENT live schema is the accepted baseline, use `voltro db drift --accept`.")), 0;
|
|
645
|
+
let u = c(e, "--note"), d = await G(a, _.SqlClient.pipe(h.flatMap((e) => b(e, l, {
|
|
646
|
+
declared: i,
|
|
644
647
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
|
|
645
648
|
environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
|
|
646
649
|
source: "auto-diff",
|
|
647
|
-
...
|
|
648
|
-
replan: () => h.promise(() =>
|
|
649
|
-
declared:
|
|
650
|
+
...u ? { notes: u } : {},
|
|
651
|
+
replan: () => h.promise(() => K(a)).pipe(h.map((e) => w({
|
|
652
|
+
declared: i,
|
|
650
653
|
live: e,
|
|
651
|
-
dialect:
|
|
652
|
-
...
|
|
654
|
+
dialect: B(),
|
|
655
|
+
...C(),
|
|
653
656
|
...n === void 0 ? {} : { onlineAfter: n }
|
|
654
657
|
})))
|
|
655
658
|
}))));
|
|
656
|
-
return
|
|
657
|
-
id:
|
|
658
|
-
fingerprint:
|
|
659
|
+
return P.info(`db apply: applied ${d.appliedOps} op(s) in ${d.durationMs}ms`, {
|
|
660
|
+
id: d.id,
|
|
661
|
+
fingerprint: d.fingerprint.slice(0, 16)
|
|
659
662
|
}), 0;
|
|
660
|
-
},
|
|
661
|
-
let t = Math.max(1, Number(c(e, "--limit") ?? "20")), n = await
|
|
663
|
+
}, ke = async (e) => {
|
|
664
|
+
let t = Math.max(1, Number(c(e, "--limit") ?? "20")), n = await G(await V(), _.SqlClient.pipe(h.flatMap((e) => e`
|
|
662
665
|
SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")},
|
|
663
666
|
${e("appliedBy")}, ${e("environment")}, ${e("source")},
|
|
664
667
|
${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}
|
|
@@ -666,25 +669,25 @@ var O = class extends Error {
|
|
|
666
669
|
ORDER BY ${e("appliedAt")} DESC
|
|
667
670
|
LIMIT ${t}
|
|
668
671
|
`)));
|
|
669
|
-
if (n.length === 0) return
|
|
672
|
+
if (n.length === 0) return P.info("db plans: no planner-based migrations applied yet"), console.log("Run `voltro db apply` after a schema change to start a plan history."), 0;
|
|
670
673
|
console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
|
|
671
674
|
for (let e of n) {
|
|
672
675
|
let t = 0;
|
|
673
676
|
try {
|
|
674
|
-
t = JSON.parse(
|
|
677
|
+
t = JSON.parse(X(e.operations)).length;
|
|
675
678
|
} catch {}
|
|
676
|
-
console.log(` ${e.id} fp=${
|
|
679
|
+
console.log(` ${e.id} fp=${T(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${Z(e.appliedAt)} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
|
|
677
680
|
}
|
|
678
681
|
return console.log(), 0;
|
|
679
|
-
},
|
|
682
|
+
}, Ae = async (e) => {
|
|
680
683
|
let t = e[0];
|
|
681
684
|
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;
|
|
682
|
-
let n = await
|
|
685
|
+
let n = await V(), r = await G(n, _.SqlClient.pipe(h.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}
|
|
683
686
|
FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
|
|
684
687
|
if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
|
|
685
688
|
let i;
|
|
686
689
|
try {
|
|
687
|
-
i = JSON.parse(
|
|
690
|
+
i = JSON.parse(X(r[0].operations));
|
|
688
691
|
} catch {
|
|
689
692
|
return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
|
|
690
693
|
}
|
|
@@ -698,7 +701,7 @@ var O = class extends Error {
|
|
|
698
701
|
if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
|
|
699
702
|
let s = 0;
|
|
700
703
|
for (let e of a) {
|
|
701
|
-
let t = await
|
|
704
|
+
let t = await G(n, _.SqlClient.pipe(h.flatMap((t) => t`
|
|
702
705
|
SELECT column_name FROM information_schema.columns
|
|
703
706
|
WHERE table_schema = 'public'
|
|
704
707
|
AND table_name = ${e.table}
|
|
@@ -710,11 +713,11 @@ var O = class extends Error {
|
|
|
710
713
|
continue;
|
|
711
714
|
}
|
|
712
715
|
let r = t[0].column_name;
|
|
713
|
-
await
|
|
716
|
+
await G(n, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table}" RENAME COLUMN "${r}" TO "${e.column}"`)))), s++, console.log(` ↩ restored ${e.table}.${e.column} (from ${r})`);
|
|
714
717
|
}
|
|
715
718
|
let c = 0;
|
|
716
719
|
for (let e of o) {
|
|
717
|
-
let t = await
|
|
720
|
+
let t = await G(n, _.SqlClient.pipe(h.flatMap((t) => t`
|
|
718
721
|
SELECT table_name FROM information_schema.tables
|
|
719
722
|
WHERE table_schema = 'public'
|
|
720
723
|
AND table_name LIKE ${e.table + "__dropped_%"}
|
|
@@ -725,18 +728,18 @@ var O = class extends Error {
|
|
|
725
728
|
continue;
|
|
726
729
|
}
|
|
727
730
|
let r = t[0].table_name;
|
|
728
|
-
await
|
|
731
|
+
await G(n, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${r}" RENAME TO "${e.table}"`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
|
|
729
732
|
}
|
|
730
733
|
return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
|
|
731
|
-
},
|
|
734
|
+
}, je = async (e) => {
|
|
732
735
|
let t = c(e, "--before");
|
|
733
736
|
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;
|
|
734
737
|
let n = t.replace(/[^0-9]/g, "");
|
|
735
738
|
if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
|
|
736
|
-
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await
|
|
739
|
+
let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await V(), o = await G(a, _.SqlClient.pipe(h.flatMap((e) => e`
|
|
737
740
|
SELECT table_name, column_name FROM information_schema.columns
|
|
738
741
|
WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
|
|
739
|
-
`))), s = await
|
|
742
|
+
`))), s = await G(a, _.SqlClient.pipe(h.flatMap((e) => e`
|
|
740
743
|
SELECT table_name FROM information_schema.tables
|
|
741
744
|
WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
|
|
742
745
|
`))), l = (e) => {
|
|
@@ -745,41 +748,37 @@ var O = class extends Error {
|
|
|
745
748
|
}, u = 0;
|
|
746
749
|
for (let e of o) {
|
|
747
750
|
let t = l(e.column_name);
|
|
748
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await
|
|
751
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await G(a, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
|
|
749
752
|
}
|
|
750
753
|
for (let e of s) {
|
|
751
754
|
let t = l(e.table_name);
|
|
752
|
-
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await
|
|
755
|
+
t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await G(a, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
|
|
753
756
|
}
|
|
754
757
|
return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
|
|
755
|
-
},
|
|
758
|
+
}, Me = async (e) => {
|
|
756
759
|
let t = c(e, "--before");
|
|
757
760
|
if (!t) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
|
|
758
761
|
let n = t;
|
|
759
762
|
if (Number.isNaN(new Date(n).getTime())) return console.error(`voltro db squash: --before '${n}' is not a valid ISO date`), 2;
|
|
760
|
-
let r = c(e, "--note"), i =
|
|
761
|
-
if (
|
|
762
|
-
let
|
|
763
|
-
...o,
|
|
764
|
-
...b,
|
|
765
|
-
...s
|
|
766
|
-
])))), u = await U();
|
|
763
|
+
let r = c(e, "--note"), i = I(e), a = await Y(i);
|
|
764
|
+
if (a === void 0) return P.warn("db squash: no schema files found", { root: i }), 1;
|
|
765
|
+
let o = S(oe(a)), s = await V();
|
|
767
766
|
try {
|
|
768
|
-
let e = await
|
|
767
|
+
let e = await G(s, de({
|
|
769
768
|
before: n,
|
|
770
|
-
fingerprint:
|
|
769
|
+
fingerprint: o,
|
|
771
770
|
appliedBy: process.env.USER ?? "cli",
|
|
772
771
|
environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
|
|
773
772
|
...r === void 0 ? {} : { note: r }
|
|
774
773
|
}));
|
|
775
|
-
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${
|
|
774
|
+
return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${T(e.snapshotFingerprint)}\n`), 0;
|
|
776
775
|
} catch (e) {
|
|
777
776
|
return console.error(`squash refused: ${e.message}`), 1;
|
|
778
777
|
}
|
|
779
|
-
},
|
|
778
|
+
}, Ne = async (e) => {
|
|
780
779
|
try {
|
|
781
|
-
let t =
|
|
782
|
-
await
|
|
780
|
+
let t = S(await K(e));
|
|
781
|
+
await G(e, _.SqlClient.pipe(h.flatMap((e) => e`
|
|
783
782
|
UPDATE ${e("_voltro_migration_plans")}
|
|
784
783
|
SET ${e("liveFingerprint")} = ${t}
|
|
785
784
|
WHERE ${e("id")} = (
|
|
@@ -791,37 +790,80 @@ var O = class extends Error {
|
|
|
791
790
|
AND ${e("liveFingerprint")} IS NULL
|
|
792
791
|
`)));
|
|
793
792
|
} catch {}
|
|
794
|
-
},
|
|
795
|
-
|
|
793
|
+
}, X = (e) => typeof e == "string" ? e : e == null ? "" : JSON.stringify(e), Z = (e) => e instanceof Date ? e.toISOString() : String(e ?? ""), Q = (e) => e === void 0 ? {
|
|
794
|
+
accept: !1,
|
|
795
|
+
reason: "no-declared-schema"
|
|
796
|
+
} : e > 0 ? {
|
|
797
|
+
accept: !1,
|
|
798
|
+
reason: "pending"
|
|
799
|
+
} : {
|
|
800
|
+
accept: !0,
|
|
801
|
+
reason: "converged"
|
|
802
|
+
}, Pe = async (e) => {
|
|
803
|
+
let t = e.includes("--accept"), n = await V(), r = await G(n, _.SqlClient.pipe(h.flatMap((e) => e`
|
|
796
804
|
SELECT * FROM ${e("_voltro_migration_plans")}
|
|
797
805
|
ORDER BY ${e("appliedAt")} DESC
|
|
798
806
|
LIMIT 1
|
|
799
|
-
`))),
|
|
800
|
-
if (
|
|
801
|
-
let
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
let
|
|
805
|
-
|
|
806
|
-
|
|
807
|
+
`))), i = await K(n), a = S(i);
|
|
808
|
+
if (r.length === 0) return P.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${T(a)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
|
|
809
|
+
let o = r[0];
|
|
810
|
+
if (o.liveFingerprint === null || o.liveFingerprint === void 0) return P.warn("db drift: no drift baseline recorded yet — cannot compare"), console.log(), console.log(` last applied: ${o.id} at ${Z(o.appliedAt)}`), console.log(` live now: ${T(a)}`), console.log(), console.log("That row was written before the framework recorded a live-schema baseline,"), console.log("and the value it does carry is the DECLARED snapshot's hash — not comparable"), console.log("with a live one. Run `voltro db apply` once to establish the baseline (a no-op"), console.log("apply backfills it too); use `voltro db plan` meanwhile, which compares declared"), console.log("against live directly."), console.log(), console.log("Exit code 3 — NOT 0. \"Did not compare\" is not \"clean\", and a gate on the exit"), console.log("code has to be able to tell them apart."), 3;
|
|
811
|
+
if (o.liveFingerprint === a) return P.info(`db drift: live matches last applied (${T(a)})`), 0;
|
|
812
|
+
let s = await (async () => {
|
|
813
|
+
try {
|
|
814
|
+
let t = await Y(I(e));
|
|
815
|
+
return t === void 0 ? void 0 : w({
|
|
816
|
+
declared: t,
|
|
817
|
+
live: i,
|
|
818
|
+
dialect: B(),
|
|
819
|
+
...C()
|
|
820
|
+
}).operations;
|
|
821
|
+
} catch {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
})();
|
|
825
|
+
if (t) {
|
|
826
|
+
let e = Q(s?.length);
|
|
827
|
+
if (e.reason === "no-declared-schema") return P.error("db drift --accept: no schema files found — cannot verify the live schema is current."), 1;
|
|
828
|
+
if (!e.accept && s !== void 0) {
|
|
829
|
+
P.error(`db drift --accept: refusing — ${s.length} operation(s) still pending against your declared schema.`), console.log();
|
|
830
|
+
for (let e of s.slice(0, 10)) console.log(` - ${e.op.kind} ${"table" in e.op ? `[${e.op.table}]` : ""}`);
|
|
831
|
+
return s.length > 10 && console.log(` … and ${s.length - 10} more`), console.log(), console.log("`--accept` records the CURRENT live schema as the applied baseline. It is for a"), console.log("change that already landed out of band and left the schema correct — not for one"), console.log("that has not landed. Run `voltro db apply` instead; it writes a real baseline."), 4;
|
|
832
|
+
}
|
|
833
|
+
return await G(n, _.SqlClient.pipe(h.flatMap((e) => e`
|
|
834
|
+
UPDATE ${e("_voltro_migration_plans")}
|
|
835
|
+
SET ${e("liveFingerprint")} = ${a}
|
|
836
|
+
WHERE ${e("id")} = (
|
|
837
|
+
SELECT ${e("id")} FROM (
|
|
838
|
+
SELECT ${e("id")} FROM ${e("_voltro_migration_plans")}
|
|
839
|
+
ORDER BY ${e("appliedAt")} DESC LIMIT 1
|
|
840
|
+
) AS ${e("newest")}
|
|
841
|
+
)
|
|
842
|
+
`))), P.info(`db drift: accepted the live schema as the baseline (${T(a)})`), console.log(), console.log("`voltro db plan` reports 0 operations against this schema, so the live state already"), console.log("matches your declaration — only the ledger did not know. It does now."), console.log(), console.log("No history entry was invented: the latest row's baseline was updated in place,"), console.log("because no migration ran."), 0;
|
|
843
|
+
}
|
|
844
|
+
return P.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` baseline: ${T(o.liveFingerprint)} at ${Z(o.appliedAt)} (${o.id})`), console.log(` live now: ${T(a)}`), console.log(), console.log("Something changed the live schema after the last apply. This command can see"), console.log("THAT it changed, not what or who — the fingerprints are hashes, not a diff."), console.log(), s === void 0 ? (console.log("No schema files found here, so the declared side could not be compared."), console.log("Run `voltro db plan` from your project root for the actual difference."), 4) : s.length === 0 ? (console.log("Your DECLARED schema is already satisfied — `voltro db plan` reports 0 operations"), console.log("against this database. So the live schema is not wrong, only unrecorded: something"), console.log("applied a change without going through the planner (a hand-run ALTER, a DBA"), console.log("window, a restored dump), or it touched a table your code does not declare."), console.log(), console.log("If that was deliberate and the schema is right, record it:"), console.log(), console.log(" voltro db drift --accept"), console.log(), console.log("It updates the latest ledger row's baseline to the live schema and invents no"), console.log("history entry. Drift then measures from here."), 4) : (console.log(`\`voltro db plan\` reports ${s.length} operation(s) still pending against your`), console.log("declared schema, so this is not merely an unrecorded change — run it for the diff,"), console.log("then `voltro db apply`, which writes a fresh baseline of its own."), 4);
|
|
845
|
+
}, Fe = async (e) => {
|
|
846
|
+
let t = I(e), n = await V(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
|
|
847
|
+
await G(n, x(v, B()));
|
|
848
|
+
let i = await G(n, _.SqlClient.pipe(h.flatMap((e) => ue(e, {
|
|
807
849
|
projectRoot: t,
|
|
808
850
|
env: r,
|
|
809
851
|
appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
|
|
810
852
|
}))));
|
|
811
|
-
return i.applied.length === 0 ? (
|
|
853
|
+
return i.applied.length === 0 ? (P.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (P.info(`db files: applied ${i.applied.length} migration(s)`, {
|
|
812
854
|
applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
|
|
813
855
|
skipped: i.skipped.length
|
|
814
856
|
}), 0);
|
|
815
|
-
},
|
|
816
|
-
let t =
|
|
817
|
-
if (!n) return
|
|
818
|
-
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return
|
|
819
|
-
let r = await
|
|
857
|
+
}, Ie = async (e) => {
|
|
858
|
+
let t = I(e), n = e.find((e) => !e.startsWith("--"));
|
|
859
|
+
if (!n) return P.error("voltro db rollback-file <migration-id>"), 2;
|
|
860
|
+
if ((process.env.NODE_ENV ?? "").toLowerCase() === "production") return P.error("voltro db rollback-file: refusing on prod — schema rollback runs as an explicit deploy step."), 3;
|
|
861
|
+
let r = await G(await V(), _.SqlClient.pipe(h.flatMap((e) => le(e, {
|
|
820
862
|
projectRoot: t,
|
|
821
863
|
id: n
|
|
822
864
|
}))));
|
|
823
|
-
return
|
|
824
|
-
},
|
|
865
|
+
return P.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
|
|
866
|
+
}, Le = () => {
|
|
825
867
|
let e = /* @__PURE__ */ new Date();
|
|
826
868
|
return [
|
|
827
869
|
e.getUTCFullYear(),
|
|
@@ -831,7 +873,7 @@ var O = class extends Error {
|
|
|
831
873
|
String(e.getUTCMinutes()).padStart(2, "0"),
|
|
832
874
|
String(e.getUTCSeconds()).padStart(2, "0")
|
|
833
875
|
].join("");
|
|
834
|
-
},
|
|
876
|
+
}, Re = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", ze = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
|
|
835
877
|
import { Effect } from 'effect'
|
|
836
878
|
|
|
837
879
|
// A hand-written migration. Reach for this when the DECLARATIVE differ can't
|
|
@@ -863,36 +905,36 @@ export default defineMigration({
|
|
|
863
905
|
}),
|
|
864
906
|
],
|
|
865
907
|
})
|
|
866
|
-
`,
|
|
867
|
-
plan:
|
|
868
|
-
apply:
|
|
869
|
-
plans:
|
|
870
|
-
drift:
|
|
871
|
-
files:
|
|
872
|
-
"rollback-file":
|
|
873
|
-
migrate:
|
|
874
|
-
rollback:
|
|
875
|
-
status:
|
|
876
|
-
seed:
|
|
908
|
+
`, $ = {
|
|
909
|
+
plan: Ee,
|
|
910
|
+
apply: Oe,
|
|
911
|
+
plans: ke,
|
|
912
|
+
drift: Pe,
|
|
913
|
+
files: Fe,
|
|
914
|
+
"rollback-file": Ie,
|
|
915
|
+
migrate: _e,
|
|
916
|
+
rollback: ve,
|
|
917
|
+
status: ye,
|
|
918
|
+
seed: xe,
|
|
877
919
|
generate: async (e) => {
|
|
878
|
-
let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? p(t) : process.cwd(), o =
|
|
879
|
-
return await
|
|
920
|
+
let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? p(t) : process.cwd(), o = Re(r ?? l(i) ?? "migration"), c = `${Le()}_${o}`, u = d(a, "migrations"), m = d(u, `${c}.migration.ts`);
|
|
921
|
+
return await g.mkdir(u, { recursive: !0 }), await g.writeFile(m, ze(c, o), "utf8"), P.info(`created ${f(a, m)}`), console.error("Fill in the apply/undo steps, then run `voltro db migrate` (rollback with `voltro db rollback`)."), console.error("For a plain schema shape change you may not need this — `voltro db apply` diffs declared vs live."), 0;
|
|
880
922
|
},
|
|
881
|
-
squash:
|
|
882
|
-
"restore-snapshot":
|
|
883
|
-
"gc-snapshots":
|
|
884
|
-
},
|
|
923
|
+
squash: Me,
|
|
924
|
+
"restore-snapshot": Ae,
|
|
925
|
+
"gc-snapshots": je
|
|
926
|
+
}, Be = async (e) => {
|
|
885
927
|
let [t, ...n] = e;
|
|
886
928
|
if (!t) {
|
|
887
929
|
console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
|
|
888
|
-
for (let e of Object.keys(
|
|
930
|
+
for (let e of Object.keys($)) console.error(` ${e}`);
|
|
889
931
|
return 1;
|
|
890
932
|
}
|
|
891
|
-
let r =
|
|
933
|
+
let r = $[t];
|
|
892
934
|
if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
|
|
893
|
-
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) &&
|
|
935
|
+
(process.env.DB_DIRECT_URL !== void 0 || process.env.DB_MIGRATE_URL !== void 0) && P.info("using a DIRECT (non-pooler) connection for the db-command path (DB_DIRECT_URL/DB_MIGRATE_URL set)");
|
|
894
936
|
let i = (e) => {
|
|
895
|
-
|
|
937
|
+
R(e) ? P.error(z) : console.error(e), process.exit(1);
|
|
896
938
|
};
|
|
897
939
|
process.on("uncaughtException", i);
|
|
898
940
|
try {
|
|
@@ -902,4 +944,4 @@ export default defineMigration({
|
|
|
902
944
|
}
|
|
903
945
|
};
|
|
904
946
|
//#endregion
|
|
905
|
-
export {
|
|
947
|
+
export { U as a, E as c, Q as i, pe as l, L as n, R as o, H as r, Be as s, z as t };
|