@voltro/cli 0.11.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +436 -0
  2. package/THIRD-PARTY-NOTICES.md +4 -4
  3. package/dist/{apiBuild-zFDv0u8y.js → apiBuild-GefjNI5d.js} +10 -9
  4. package/dist/apiBuild-s7swuEU5.js +2 -0
  5. package/dist/appModuleLoader-C9r9mxZt.js +19 -0
  6. package/dist/bin.js +24 -6
  7. package/dist/cliArgs-qdZSElM3.js +28 -0
  8. package/dist/{commands-BDGYrBhK.js → commands-BmNi1RgZ.js} +2580 -2493
  9. package/dist/dbCommand-Bt6BCG7g.js +2 -0
  10. package/dist/dbCommand-DNrkb58g.js +862 -0
  11. package/dist/dev-YHbHmeXt.js +3 -0
  12. package/dist/{dev-CGt0PP1f.js → dev-d0LjACxE.js} +1591 -2332
  13. package/dist/{devActivity-1WtIVyHc.js → devActivity-BhIu6ncs.js} +7 -1
  14. package/dist/devActivity.js +1 -1
  15. package/dist/index.d.ts +22 -0
  16. package/dist/index.js +3 -3
  17. package/dist/inspect-DcZ04OME.js +2 -0
  18. package/dist/inspect-Dwx0_tUj.js +921 -0
  19. package/dist/inspectMetrics-uEfIS1vp.js +3399 -0
  20. package/dist/manifestBuild-D1MzJAiQ.js +2 -0
  21. package/dist/manifestBuild-P9yuCY2d.js +39 -0
  22. package/dist/seedRunner-DZmhoqNt.js +821 -0
  23. package/dist/serveCommand-B2-YV9TU.js +1112 -0
  24. package/dist/serveEntry.js +4 -4
  25. package/dist/{start-Crl39M38.js → start-DKcQd52a.js} +348 -337
  26. package/dist/startEntry.js +2 -2
  27. package/dist/startup.js +1 -1
  28. package/dist/{startupRunner-DhlX9nqd.js → startupRunner-DPGFchOa.js} +19 -33
  29. package/package.json +17 -17
  30. package/templates/AGENTS.md +1 -0
  31. package/templates/agent-docs/_index.md +1 -0
  32. package/templates/agent-docs/_manifest.json +1 -1
  33. package/templates/agent-docs/cli.md +266 -16
  34. package/templates/agent-docs/data.md +143 -1
  35. package/templates/agent-docs/database/misc.md +32 -0
  36. package/templates/agent-docs/database/querying.md +14 -1
  37. package/templates/agent-docs/internationalization.md +3 -8
  38. package/templates/agent-docs/reference.md +35 -0
  39. package/templates/agent-docs/scheduling.md +35 -1
  40. package/templates/agent-docs/security.md +48 -0
  41. package/templates/agent-docs/templates/apibackends.md +90 -17
  42. package/templates/agent-docs/whats-new.md +177 -0
  43. package/templates/agent-docs/workflows.md +31 -0
  44. package/templates/apps/api-ai/package.json +7 -7
  45. package/templates/apps/api-auth/package.json +8 -8
  46. package/templates/apps/api-backend/package.json +7 -7
  47. package/templates/apps/api-backend-deactivation/package.json +7 -7
  48. package/templates/apps/api-backend-mail/package.json +8 -8
  49. package/templates/apps/api-backend-mariadb/package.json +9 -9
  50. package/templates/apps/api-backend-storage/package.json +8 -8
  51. package/templates/apps/api-data-advanced/package.json +8 -8
  52. package/templates/apps/api-durable/package.json +8 -8
  53. package/templates/apps/api-feature-flags/package.json +9 -9
  54. package/templates/apps/api-governance/package.json +8 -8
  55. package/templates/apps/api-kv/package.json +8 -8
  56. package/templates/apps/api-moderation/package.json +8 -8
  57. package/templates/apps/api-observability/package.json +8 -8
  58. package/templates/apps/api-ratelimit/package.json +8 -8
  59. package/templates/apps/api-rbac/package.json +8 -8
  60. package/templates/apps/api-rest/package.json +7 -7
  61. package/templates/apps/api-saas/package.json +11 -11
  62. package/templates/apps/api-search/package.json +8 -8
  63. package/templates/apps/api-versioning/package.json +8 -8
  64. package/templates/apps/api-webhooks/README.md +3 -1
  65. package/templates/apps/api-webhooks/package.json +8 -8
  66. package/templates/apps/changelog/package.json +6 -6
  67. package/templates/apps/edge-functions/package.json +2 -2
  68. package/templates/apps/frontend-admin/package.json +8 -8
  69. package/templates/apps/frontend-app/package.json +8 -8
  70. package/templates/apps/frontend-blank/package.json +7 -7
  71. package/templates/apps/frontend-contact/package.json +7 -7
  72. package/templates/apps/frontend-dashboard/package.json +7 -7
  73. package/templates/apps/frontend-docs/package.json +7 -7
  74. package/templates/apps/frontend-i18n/package.json +6 -6
  75. package/templates/apps/frontend-landing/package.json +7 -7
  76. package/templates/apps/frontend-spa/package.json +7 -7
  77. package/templates/apps/frontend-ssr/package.json +7 -7
  78. package/templates/apps/frontend-ssr-api/package.json +8 -8
  79. package/templates/apps/frontend-static-blog/package.json +6 -6
  80. package/dist/apiBuild-BmPdmhgi.js +0 -2
  81. package/dist/dev-D_PGP9Kx.js +0 -2
  82. package/dist/inspectMetrics-SRtv8KDy.js +0 -4247
  83. package/dist/serveCommand-p6e5Ahgx.js +0 -1070
@@ -0,0 +1,862 @@
1
+ import { G as e, a as t, i as n, m as r, n as i, r as a, t as o } from "./seedRunner-DZmhoqNt.js";
2
+ import { a as s, n as c, t as l } from "./cliArgs-qdZSElM3.js";
3
+ import { basename as u, join as d, relative as f, resolve as p } from "node:path";
4
+ import { pathToFileURL as m } from "node:url";
5
+ import { Effect as h } from "effect";
6
+ import { createLogger as ee } from "@voltro/logger";
7
+ import { promises as g, writeSync as te } from "node:fs";
8
+ import { InMemoryDataStore as ne } from "@voltro/runtime";
9
+ import { SqlClient as _ } from "@effect/sql";
10
+ import { actorsTable as v, frameworkTables as y, isMigrationDefinition as b, registerCoreTables as re } from "@voltro/database";
11
+ import { createHash as ie } from "node:crypto";
12
+ import { applyPlan as x, applySchema as S, declaredSnapshot as ae, fingerprintSchema as C, ignoreTablesFromEnv as w, introspectSchema as oe, planMigrations as T, rollbackFileBasedMigration as E, runFileBasedMigrations as D, shortFingerprint as O, squashMigrationPlans as se } from "@voltro/database/sql";
13
+ //#region src/cliError.ts
14
+ var k = class extends Error {
15
+ isCliError = !0;
16
+ code;
17
+ constructor(e, t = 1) {
18
+ super(e), this.name = "CliError", this.code = t;
19
+ }
20
+ }, ce = (e) => typeof e == "object" && !!e && e.isCliError === !0, A = /\.migration\.ts$/, j = async (e, t) => {
21
+ let n = [];
22
+ for (let r of e) {
23
+ let e;
24
+ try {
25
+ e = await import(m(r).href);
26
+ } catch (e) {
27
+ t.warn("migration: failed to import", { file: r }, e);
28
+ continue;
29
+ }
30
+ let i = (e.default && b(e.default) ? e.default : void 0) ?? Object.values(e).find(b);
31
+ if (!i) {
32
+ t.warn("migration: no defineMigration export", { file: r });
33
+ continue;
34
+ }
35
+ i.id !== u(r).replace(/\.migration\.ts$/, "") && t.warn("migration: id does not match filename — id wins for ordering, but rename the file for clarity", {
36
+ file: r,
37
+ idInFile: i.id,
38
+ idFromFile: u(r).replace(/\.migration\.ts$/, "")
39
+ });
40
+ let a = await g.readFile(r, "utf8").catch(() => ""), o = ie("sha256").update(a).digest("hex").slice(0, 16);
41
+ n.push({
42
+ file: r,
43
+ definition: i,
44
+ hash: o
45
+ });
46
+ }
47
+ return [...n].sort((e, t) => e.definition.id < t.definition.id ? -1 : 1);
48
+ }, M = (e) => {
49
+ let t = (process.env.DB_DIALECT ?? "postgres").toLowerCase(), n = t === "memory" ? "postgres" : t;
50
+ return h.runPromise(S(y, n).pipe(h.provide(e)));
51
+ }, N = (e) => h.runPromise(h.gen(function* () {
52
+ let e = yield* (yield* _.SqlClient)`SELECT id, status, hash, "appliedAt" FROM "_voltro_migrations"`.pipe(h.orElseSucceed(() => []));
53
+ return new Map(e.map((e) => [e.id, e]));
54
+ }).pipe(h.provide(e))), P = (e, t) => h.runPromise(h.gen(function* () {
55
+ yield* (yield* _.SqlClient)`
56
+ INSERT INTO "_voltro_migrations"
57
+ (id, name, hash, status, "appliedAt", "durationMs", "errorMessage", "errorStack")
58
+ VALUES
59
+ (${t.id}, ${t.name}, ${t.hash}, ${t.status}, ${t.appliedAt}, ${t.durationMs}, ${t.errorMessage}, ${t.errorStack})
60
+ ON CONFLICT (id) DO UPDATE SET
61
+ name = EXCLUDED.name,
62
+ hash = EXCLUDED.hash,
63
+ status = EXCLUDED.status,
64
+ "appliedAt" = EXCLUDED."appliedAt",
65
+ "durationMs" = EXCLUDED."durationMs",
66
+ "errorMessage"= EXCLUDED."errorMessage",
67
+ "errorStack" = EXCLUDED."errorStack",
68
+ "updatedAt" = now()
69
+ `;
70
+ }).pipe(h.provide(e))), le = async (e, t, n) => {
71
+ if (e.length === 0) return {
72
+ applied: [],
73
+ failed: null,
74
+ skipped: []
75
+ };
76
+ await M(t);
77
+ let r = await N(t), i = [], a = [], o = null;
78
+ for (let s of e) {
79
+ let e = r.get(s.definition.id);
80
+ if (e && e.status === "applied") {
81
+ e.hash !== s.hash && n.warn("migration: applied migration has a different hash on disk", {
82
+ id: s.definition.id,
83
+ appliedHash: e.hash,
84
+ fileHash: s.hash
85
+ }), a.push({
86
+ migrationId: s.definition.id,
87
+ status: "skipped",
88
+ durationMs: 0,
89
+ steps: []
90
+ });
91
+ continue;
92
+ }
93
+ n.info("migration: applying", {
94
+ id: s.definition.id,
95
+ name: s.definition.name
96
+ });
97
+ let c = Date.now(), l = [];
98
+ await P(t, {
99
+ id: s.definition.id,
100
+ name: s.definition.name,
101
+ hash: s.hash,
102
+ status: "running",
103
+ appliedAt: null,
104
+ durationMs: null,
105
+ errorMessage: null,
106
+ errorStack: null
107
+ });
108
+ let u = !1;
109
+ for (let e of s.definition.steps) {
110
+ let n = Date.now();
111
+ try {
112
+ await h.runPromise(e.apply.pipe(h.provide(t))), l.push({
113
+ stepName: e.name,
114
+ status: "applied",
115
+ durationMs: Date.now() - n
116
+ });
117
+ } catch (r) {
118
+ let a = r, d = {
119
+ stepName: e.name,
120
+ status: "failed",
121
+ durationMs: Date.now() - n,
122
+ error: {
123
+ message: a.message,
124
+ ...a.stack ? { stack: a.stack } : {}
125
+ }
126
+ };
127
+ l.push(d), u = !0;
128
+ let f = {
129
+ migrationId: s.definition.id,
130
+ status: "failed",
131
+ durationMs: Date.now() - c,
132
+ steps: l,
133
+ error: {
134
+ stepName: e.name,
135
+ message: a.message,
136
+ ...a.stack ? { stack: a.stack } : {}
137
+ }
138
+ };
139
+ o = f, i.push(f), await P(t, {
140
+ id: s.definition.id,
141
+ name: s.definition.name,
142
+ hash: s.hash,
143
+ status: "failed",
144
+ appliedAt: null,
145
+ durationMs: Date.now() - c,
146
+ errorMessage: a.message,
147
+ errorStack: a.stack ?? null
148
+ });
149
+ break;
150
+ }
151
+ }
152
+ if (u) break;
153
+ i.push({
154
+ migrationId: s.definition.id,
155
+ status: "applied",
156
+ durationMs: Date.now() - c,
157
+ steps: l
158
+ }), await P(t, {
159
+ id: s.definition.id,
160
+ name: s.definition.name,
161
+ hash: s.hash,
162
+ status: "applied",
163
+ appliedAt: /* @__PURE__ */ new Date(),
164
+ durationMs: Date.now() - c,
165
+ errorMessage: null,
166
+ errorStack: null
167
+ }), n.info("migration: applied", {
168
+ id: s.definition.id,
169
+ durationMs: Date.now() - c,
170
+ stepCount: l.length
171
+ });
172
+ }
173
+ return {
174
+ applied: i.filter((e) => e.status === "applied"),
175
+ failed: o,
176
+ skipped: a
177
+ };
178
+ }, ue = async (e, t, n, r = {}) => {
179
+ await M(t);
180
+ let i = await N(t), a = e.filter((e) => i.get(e.definition.id)?.status === "applied");
181
+ if (a.length === 0) return n.info("migration: nothing to roll back"), {
182
+ applied: [],
183
+ failed: null,
184
+ skipped: []
185
+ };
186
+ let o = [];
187
+ for (let e = a.length - 1; e >= 0; e--) {
188
+ let t = a[e];
189
+ if (o.push(t), !r.to && o.length === 1 || r.to && t.definition.id === r.to) break;
190
+ }
191
+ let s = [], c = null;
192
+ for (let e of o) {
193
+ n.info("migration: rolling back", { id: e.definition.id });
194
+ let r = Date.now(), i = [], a = !1;
195
+ for (let n = e.definition.steps.length - 1; n >= 0; n--) {
196
+ let o = e.definition.steps[n], s = Date.now();
197
+ try {
198
+ await h.runPromise(o.undo.pipe(h.provide(t))), i.push({
199
+ stepName: o.name,
200
+ status: "applied",
201
+ durationMs: Date.now() - s
202
+ });
203
+ } catch (t) {
204
+ let n = t;
205
+ i.push({
206
+ stepName: o.name,
207
+ status: "failed",
208
+ durationMs: Date.now() - s,
209
+ error: {
210
+ message: n.message,
211
+ ...n.stack ? { stack: n.stack } : {}
212
+ }
213
+ }), a = !0, c = {
214
+ migrationId: e.definition.id,
215
+ status: "failed",
216
+ durationMs: Date.now() - r,
217
+ steps: i,
218
+ error: {
219
+ stepName: o.name,
220
+ message: n.message,
221
+ ...n.stack ? { stack: n.stack } : {}
222
+ }
223
+ };
224
+ break;
225
+ }
226
+ }
227
+ if (a) break;
228
+ s.push({
229
+ migrationId: e.definition.id,
230
+ status: "applied",
231
+ durationMs: Date.now() - r,
232
+ steps: i
233
+ }), await P(t, {
234
+ id: e.definition.id,
235
+ name: e.definition.name,
236
+ hash: e.hash,
237
+ status: "reverted",
238
+ appliedAt: null,
239
+ durationMs: Date.now() - r,
240
+ errorMessage: null,
241
+ errorStack: null
242
+ }), n.info("migration: rolled back", { id: e.definition.id });
243
+ }
244
+ return {
245
+ applied: s,
246
+ failed: c,
247
+ skipped: []
248
+ };
249
+ }, F = async (e, t) => {
250
+ await M(t);
251
+ let n = await N(t);
252
+ return e.map((e) => {
253
+ let t = n.get(e.definition.id);
254
+ return t ? {
255
+ id: e.definition.id,
256
+ name: e.definition.name,
257
+ status: t.status === "running" ? "failed" : t.status,
258
+ appliedAt: t.appliedAt,
259
+ hashMatches: t.hash === e.hash
260
+ } : {
261
+ id: e.definition.id,
262
+ name: e.definition.name,
263
+ status: "pending",
264
+ appliedAt: null,
265
+ hashMatches: !0
266
+ };
267
+ });
268
+ }, I = (e) => {
269
+ if (!e.failed) return "";
270
+ let t = e.failed, n = [
271
+ `✗ Migration failed: ${t.migrationId} / step '${t.error?.stepName ?? "?"}'`,
272
+ ` Reason: ${t.error?.message ?? "unknown"}`,
273
+ "",
274
+ "Next steps:",
275
+ " voltro db status # full migration timeline",
276
+ " voltro db rollback # revert to last good state",
277
+ " voltro db migrate # retry after fixing the cause",
278
+ ""
279
+ ];
280
+ return e.applied.length > 0 && n.unshift("", `(${e.applied.length} migration(s) applied successfully before the failure.)`), n.join("\n");
281
+ }, L = ee({ scope: "voltro:db" }), de = /* @__PURE__ */ new Set([
282
+ "node_modules",
283
+ "dist",
284
+ "build",
285
+ ".turbo",
286
+ ".vite",
287
+ ".next",
288
+ ".git"
289
+ ]), R = async (e, t) => {
290
+ let n = [], r = async (e) => {
291
+ let i = await g.readdir(e, { withFileTypes: !0 }).catch(() => []);
292
+ for (let a of i) {
293
+ if (de.has(a.name)) continue;
294
+ let i = d(e, a.name);
295
+ a.isDirectory() ? await r(i) : a.isFile() && t.test(a.name) && n.push(i);
296
+ }
297
+ };
298
+ return await r(e), n;
299
+ }, z = (e) => p(e.find((e) => !e.startsWith("-")) ?? process.cwd()), B = () => {
300
+ 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;
301
+ return a ? {
302
+ url: a,
303
+ ...n,
304
+ ...i
305
+ } : {
306
+ host: process.env.DB_HOST ?? process.env.PG_HOST ?? "localhost",
307
+ port: Number(process.env.DB_PORT ?? process.env.PG_PORT ?? 5432),
308
+ username: process.env.DB_USER ?? process.env.PG_USER ?? "app",
309
+ password: process.env.DB_PASSWORD ?? process.env.PG_PASSWORD ?? "app",
310
+ database: process.env.DB_DATABASE ?? process.env.PG_DATABASE ?? "app",
311
+ ...n,
312
+ ...i
313
+ };
314
+ }, V = (e) => {
315
+ let t = e;
316
+ if (typeof t != "object" || !t) return !1;
317
+ if (t.code === "ERR_OUT_OF_RANGE" || t.cause?.code === "ERR_OUT_OF_RANGE") return !0;
318
+ let n = `${t.message ?? ""} ${t.cause?.message ?? ""} ${t.stack ?? ""}`;
319
+ return /ERR_OUT_OF_RANGE|out of range/i.test(n) && /pg-protocol|parseDataRow|buffer-reader|BufferReader/i.test(n);
320
+ }, 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 = () => {
321
+ let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
322
+ if (e === "memory") throw new k("voltro db: subcommands require a real dialect, not DB_DIALECT=memory.");
323
+ return e;
324
+ }, W = async () => (await e(U())).makeSqlLayer(B()), fe = async (e) => {
325
+ let t = z(e), n = e.includes("--dry-run"), r = await j(await R(t, A), L);
326
+ if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
327
+ if (n) {
328
+ let e = /* @__PURE__ */ new Set();
329
+ try {
330
+ let t = await F(r, await W());
331
+ e = new Set(t.filter((e) => e.status === "applied").map((e) => e.id));
332
+ } catch {
333
+ L.warn("migrate --dry-run: could not read the migration ledger — showing ALL as pending");
334
+ }
335
+ let t = r.filter((t) => !e.has(t.definition.id));
336
+ if (t.length === 0) return L.info("migrate --dry-run: nothing pending — every migration is applied"), 0;
337
+ L.info(`migrate --dry-run: ${t.length} pending (nothing applied)`);
338
+ for (let e of t) console.log(` pending ${e.definition.id}`);
339
+ return 0;
340
+ }
341
+ let i = await le(r, await W(), L);
342
+ return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("migrate complete", {
343
+ applied: i.applied.length,
344
+ skipped: i.skipped.length
345
+ }), 0);
346
+ }, pe = async (e) => {
347
+ let t = z(e), n = c(e, "--to"), r = await j(await R(t, A), L);
348
+ if (r.length === 0) return L.info("no migrations found", { root: t }), 0;
349
+ let i = await ue(r, await W(), L, n ? { to: n } : {});
350
+ return i.failed ? (process.stderr.write(I(i) + "\n"), 1) : (L.info("rollback complete", { reverted: i.applied.length }), 0);
351
+ }, me = async (e) => {
352
+ let t = await j(await R(z(e), A), L);
353
+ if (t.length === 0) return console.log("no migrations found"), 0;
354
+ let n = await F(t, await W());
355
+ for (let e of n) {
356
+ let t = e.status === "applied" ? "✓" : e.status === "failed" ? "✗" : e.status === "reverted" ? "↩" : "·", n = e.hashMatches ? "" : " (hash drift!)", r = e.appliedAt ? ` @ ${e.appliedAt.toISOString()}` : "";
357
+ console.log(` ${t} ${e.id} [${e.status}] ${e.name}${r}${n}`);
358
+ }
359
+ return 0;
360
+ }, he = async (t) => {
361
+ let n = (c(t, "--store") ?? process.env.DB_DIALECT ?? "postgres").toLowerCase();
362
+ if (n === "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."), {
363
+ store: new ne(),
364
+ dispose: async () => {},
365
+ dialectId: "memory"
366
+ };
367
+ let r = await e(n);
368
+ L.info(`voltro db seed: target store=${r.id} — rows will persist`);
369
+ let i = await r.makeStore({
370
+ sqlLayer: r.makeSqlLayer(B()),
371
+ changeStrategy: "inline"
372
+ }), a = i;
373
+ return {
374
+ store: i,
375
+ dispose: async () => {
376
+ typeof a.close == "function" && await a.close(5e3);
377
+ },
378
+ dialectId: r.id
379
+ };
380
+ }, ge = async (e) => {
381
+ let t = z(e), r = c(e, "--id"), s = c(e, "--lifecycle"), l = await i(await R(t, o), L);
382
+ if (l.length === 0) return L.info("no seeds found", { root: t }), 0;
383
+ let { store: u, dispose: d, dialectId: f } = await he(e);
384
+ try {
385
+ return r ? +((await n(l, r, u, L)).status === "failed") : +((await a((s ? l.filter((e) => e.definition.lifecycle === s) : l.filter((e) => e.definition.lifecycle === "boot")).map((e) => ({
386
+ ...e,
387
+ definition: {
388
+ ...e.definition,
389
+ lifecycle: "boot"
390
+ }
391
+ })), u, L, { force: !0 })).filter((e) => e.status === "failed").length > 0);
392
+ } finally {
393
+ await d().catch((e) => {
394
+ L.warn("voltro db seed: store dispose failed", { error: String(e) });
395
+ });
396
+ }
397
+ }, _e = (e) => typeof e == "object" && !!e && "tableName" in e && "fields" in e && typeof e.tableName == "string", G = async (e) => {
398
+ let t = await R(e, r), n = [];
399
+ for (let r of t) {
400
+ let t;
401
+ try {
402
+ t = await import(m(r).href);
403
+ } catch (t) {
404
+ L.warn("db: failed to import schema file", {
405
+ file: f(e, r),
406
+ error: String(t)
407
+ });
408
+ continue;
409
+ }
410
+ for (let e of Object.values(t)) _e(e) && n.push(e);
411
+ }
412
+ return n;
413
+ }, K = (e) => {
414
+ let t = new Map(e.map((e) => [e.tableName, e]));
415
+ t.has("actors") || t.set("actors", v);
416
+ let n = t.get("actors"), r = t.get("tenants");
417
+ return re({
418
+ actors: () => n,
419
+ ...r === void 0 ? {} : { tenants: () => r }
420
+ }), [...t.values()];
421
+ }, q = (e) => {
422
+ let t = /* @__PURE__ */ new Map();
423
+ for (let n of e) {
424
+ let e = n.tableName;
425
+ e !== void 0 && !t.has(e) && t.set(e, n);
426
+ }
427
+ return [...t.values()];
428
+ }, J = async (e, t) => h.runPromise(t.pipe(h.provide(e))), Y = async (e) => {
429
+ try {
430
+ return await J(e, _.SqlClient.pipe(h.flatMap(oe)));
431
+ } catch (e) {
432
+ let t = String(e?.message ?? e);
433
+ if (V(e) || /connection error/i.test(t)) {
434
+ let n = /* @__PURE__ */ Error(`${H}\n\nOriginal error: ${t}`);
435
+ throw n.cause = e, n;
436
+ }
437
+ throw e;
438
+ }
439
+ }, ve = {
440
+ safe: "\x1B[32m✓\x1B[0m",
441
+ "needs-default": "\x1B[32m⊕\x1B[0m",
442
+ "needs-backfill": "\x1B[33m⊕\x1B[0m",
443
+ "needs-rename-annotation": "\x1B[33m⟲\x1B[0m",
444
+ lossy: "\x1B[31m✗\x1B[0m",
445
+ "online-required": "\x1B[36m◷\x1B[0m",
446
+ "multi-step": "\x1B[35m⇉\x1B[0m"
447
+ }, ye = (e) => {
448
+ let t = e.op, n;
449
+ switch (t.kind) {
450
+ case "create-table":
451
+ n = `CREATE TABLE ${t.table} (${t.columns.length} cols)`;
452
+ break;
453
+ case "drop-table":
454
+ n = `DROP TABLE ${t.table}`;
455
+ break;
456
+ case "add-column":
457
+ n = `ALTER TABLE ${t.table} ADD COLUMN ${t.column.name} ${t.column.type}${t.column.nullable ? "" : " NOT NULL"}`;
458
+ break;
459
+ case "drop-column":
460
+ n = `ALTER TABLE ${t.table} DROP COLUMN ${t.column}`;
461
+ break;
462
+ case "rename-column":
463
+ n = `ALTER TABLE ${t.table} RENAME COLUMN ${t.from} TO ${t.to}`;
464
+ break;
465
+ case "alter-column-nullability":
466
+ n = `ALTER TABLE ${t.table} ALTER COLUMN ${t.column} ${t.toNullable ? "DROP NOT NULL" : "SET NOT NULL"}`;
467
+ break;
468
+ case "alter-column-type":
469
+ n = `ALTER TABLE ${t.table} ALTER COLUMN ${t.column} TYPE ${t.to}`;
470
+ break;
471
+ case "alter-column-default":
472
+ n = `ALTER TABLE ${t.table} ALTER COLUMN ${t.column} ${t.hasDefault ? `SET DEFAULT ${JSON.stringify(t.defaultValue)}` : "DROP DEFAULT"}`;
473
+ break;
474
+ case "add-index":
475
+ n = `CREATE INDEX ${t.index.name} ON ${t.table} (${t.index.columns.join(", ")})`;
476
+ break;
477
+ case "drop-index":
478
+ n = `DROP INDEX ${t.index}`;
479
+ break;
480
+ case "add-unique":
481
+ n = `ALTER TABLE ${t.table} ADD UNIQUE (${t.column})`;
482
+ break;
483
+ case "drop-unique":
484
+ n = `ALTER TABLE ${t.table} DROP UNIQUE (${t.column})`;
485
+ break;
486
+ case "add-unique-composite":
487
+ n = `ALTER TABLE ${t.table} ADD CONSTRAINT ${t.name} UNIQUE (${t.fields.join(", ")})`;
488
+ break;
489
+ case "drop-unique-composite":
490
+ n = `ALTER TABLE ${t.table} DROP CONSTRAINT ${t.name}`;
491
+ break;
492
+ case "add-foreign-key":
493
+ n = `ALTER TABLE ${t.table} ADD FK ${t.column} → ${t.targetTable}.${t.targetColumn}`;
494
+ break;
495
+ case "drop-foreign-key":
496
+ n = `ALTER TABLE ${t.table} DROP FK ${t.column}`;
497
+ break;
498
+ case "add-check":
499
+ n = `ALTER TABLE ${t.table} ADD CHECK (${t.column} IN (${t.values.join(", ")}))`;
500
+ break;
501
+ case "drop-check":
502
+ n = `ALTER TABLE ${t.table} DROP CHECK ${t.column}`;
503
+ break;
504
+ }
505
+ let r = ve[e.classification], i = e.reason ? ` # ${e.reason}` : "", a = e.blocked ? `\n ! ${e.blocked.fix}` : "";
506
+ return ` ${r} ${n}${i}${a}`;
507
+ }, X = (e) => {
508
+ let t = e.summary, n = e.operations.length;
509
+ if (console.log(`\nschema diff: ${n} operation${n === 1 ? "" : "s"}, ${t.blocked} blocked\n`), n === 0) {
510
+ console.log(" (schema is up to date)\n");
511
+ return;
512
+ }
513
+ for (let t of e.operations) console.log(ye(t));
514
+ 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`);
515
+ }, Z = (e) => {
516
+ let t = c(e, "--online-after") ?? process.env.VOLTRO_ONLINE_THRESHOLD;
517
+ if (t === void 0) return;
518
+ let n = Number(t);
519
+ if (!Number.isInteger(n) || n < 0) throw Error(`--online-after / VOLTRO_ONLINE_THRESHOLD must be a non-negative integer, got "${t}"`);
520
+ return n;
521
+ }, be = async (e) => {
522
+ let n = Z(e), r = z(e), i = await G(r);
523
+ if (i.length === 0) return L.warn("db plan: no schema files found", {
524
+ root: r,
525
+ hint: "looked for *.entity.ts / *.schema.ts / schema.ts"
526
+ }), 1;
527
+ let a = await t({ root: r }), o = K(q([
528
+ ...i,
529
+ ...y,
530
+ ...a
531
+ ])), s = c(e, "--against");
532
+ if (s) {
533
+ let t = s, r = c(e, "--token") ?? process.env.VOLTRO_INSPECT_TOKEN, i = { accept: "application/json" };
534
+ r && (i.authorization = `Bearer ${r}`);
535
+ try {
536
+ let e = t.endsWith("/_voltro/inspect/migrations") ? t : `${t.replace(/\/+$/, "")}/_voltro/inspect/migrations`, r = await fetch(e, { headers: i });
537
+ if (!r.ok) return L.error(`db plan --against: remote returned ${r.status}`, { url: e }), 1;
538
+ let a = (await r.json()).drift?.liveSnapshot;
539
+ if (!a) return L.error("db plan --against: remote inspect payload missing drift.liveSnapshot", { url: e }), 1;
540
+ let s = T({
541
+ declared: o,
542
+ live: a,
543
+ dialect: U(),
544
+ ...w(),
545
+ ...n === void 0 ? {} : { onlineAfter: n }
546
+ });
547
+ return console.log(`\n[--against ${e}] — diff vs remote live schema:\n`), X(s), s.summary.blocked > 0 ? 2 : 0;
548
+ } catch (e) {
549
+ return L.error(`db plan --against: ${e.message}`), 1;
550
+ }
551
+ }
552
+ let l = T({
553
+ declared: o,
554
+ live: await Y(await W()),
555
+ dialect: U(),
556
+ ...w(),
557
+ ...n === void 0 ? {} : { onlineAfter: n }
558
+ });
559
+ return e.includes("--json") ? (te(1, `${JSON.stringify(l, null, 2)}\n`), l.summary.blocked > 0 ? 2 : 0) : (X(l), l.summary.blocked > 0 ? 2 : 0);
560
+ }, xe = async (e, n, r) => {
561
+ let i;
562
+ try {
563
+ let t = await g.readFile(p(e), "utf8");
564
+ i = JSON.parse(t);
565
+ } catch (t) {
566
+ return L.error(`db apply --plan: cannot read/parse plan file '${e}': ${t.message}`), 1;
567
+ }
568
+ if (!Array.isArray(i?.operations) || typeof i?.fromFingerprint != "string" || typeof i?.toFingerprint != "string") return L.error(`db apply --plan: '${e}' is not a valid plan file (expected the output of \`voltro db plan --json\`).`), 1;
569
+ let a = await G(r);
570
+ if (a.length === 0) return L.warn("db apply --plan: no schema files found", { root: r }), 1;
571
+ let o = await t({ root: r }), s = K(q([
572
+ ...a,
573
+ ...y,
574
+ ...o
575
+ ])), l = await W();
576
+ await J(l, S(y, U()));
577
+ let u = T({
578
+ declared: s,
579
+ live: await Y(l),
580
+ dialect: U(),
581
+ ...w()
582
+ });
583
+ if (u.fromFingerprint !== i.fromFingerprint) return L.error("db apply --plan: refusing — the live schema has drifted since this plan was generated."), console.error(` plan.from=${i.fromFingerprint.slice(0, 16)} live=${u.fromFingerprint.slice(0, 16)}`), console.error(" Regenerate against the current live schema: `voltro db plan --json > plan.json`"), 2;
584
+ if (u.toFingerprint !== i.toFingerprint) return L.error("db apply --plan: refusing — the declared schema differs from what this plan targets."), console.error(` plan.to=${i.toFingerprint.slice(0, 16)} declared=${u.toFingerprint.slice(0, 16)}`), console.error(" The schema files changed since the plan was generated. Regenerate the plan."), 2;
585
+ if (u.summary.blocked > 0) return X(u), L.error(`db apply --plan: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), 2;
586
+ if (u.operations.length === 0) return L.info("db apply --plan: schema is up to date — nothing to apply"), 0;
587
+ X(u);
588
+ let d = (process.env.NODE_ENV ?? "dev").toLowerCase(), f = c(n, "--note"), m = await J(l, _.SqlClient.pipe(h.flatMap((e) => x(e, u, {
589
+ declared: s,
590
+ appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:plan",
591
+ environment: d === "production" ? "prod" : d === "staging" ? "staging" : "dev",
592
+ source: "file",
593
+ ...f ? { notes: f } : {}
594
+ }))));
595
+ return L.info(`db apply --plan: applied ${u.operations.length} op(s) in ${m.durationMs}ms`, {
596
+ id: m.id,
597
+ fingerprint: m.fingerprint.slice(0, 16)
598
+ }), 0;
599
+ }, Se = async (e) => {
600
+ let n = z(e), r = Z(e), i = c(e, "--plan");
601
+ if (i) return xe(i, e, n);
602
+ 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;
603
+ let a = await G(n);
604
+ if (a.length === 0) return L.warn("db apply: no schema files found", { root: n }), 1;
605
+ let o = await t({ root: n }), s = K(q([
606
+ ...a,
607
+ ...y,
608
+ ...o
609
+ ])), l = await W();
610
+ await J(l, S(y, U()));
611
+ let u = T({
612
+ declared: s,
613
+ live: await Y(l),
614
+ dialect: U(),
615
+ ...w(),
616
+ ...r === void 0 ? {} : { onlineAfter: r }
617
+ });
618
+ if (X(u), u.summary.blocked > 0) return L.error(`db apply: refusing — ${u.summary.blocked} blocked operation(s). See the fix hints above.`), 2;
619
+ if (u.operations.length === 0) return L.info("db apply: schema is up to date — nothing to apply"), 0;
620
+ let d = c(e, "--note"), f = await J(l, _.SqlClient.pipe(h.flatMap((e) => x(e, u, {
621
+ declared: s,
622
+ appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user",
623
+ environment: (process.env.NODE_ENV ?? "dev").toLowerCase() === "staging" ? "staging" : "dev",
624
+ source: "auto-diff",
625
+ ...d ? { notes: d } : {}
626
+ }))));
627
+ return L.info(`db apply: applied ${u.operations.length} op(s) in ${f.durationMs}ms`, {
628
+ id: f.id,
629
+ fingerprint: f.fingerprint.slice(0, 16)
630
+ }), 0;
631
+ }, Ce = async (e) => {
632
+ let t = Math.max(1, Number(c(e, "--limit") ?? "20")), n = await J(await W(), _.SqlClient.pipe(h.flatMap((e) => e`
633
+ SELECT ${e("id")}, ${e("fingerprint")}, ${e("operations")}::text AS ${e("operations")},
634
+ ${e("appliedBy")}, ${e("environment")}, ${e("source")},
635
+ ${e("durationMs")}, ${e("notes")}, ${e("appliedAt")}::text AS ${e("appliedAt")}
636
+ FROM ${e("_voltro_migration_plans")}
637
+ ORDER BY ${e("appliedAt")} DESC
638
+ LIMIT ${t}
639
+ `)));
640
+ if (n.length === 0) return L.info("db plans: no planner-based migrations applied yet"), console.log("Run `voltro db apply` after a schema change to start a plan history."), 0;
641
+ console.log(`\n${n.length} plan${n.length === 1 ? "" : "s"} (newest first):\n`);
642
+ for (let e of n) {
643
+ let t = 0;
644
+ try {
645
+ t = JSON.parse(e.operations).length;
646
+ } catch {}
647
+ console.log(` ${e.id} fp=${O(e.fingerprint)} env=${e.environment} src=${e.source} ${t} op(s) ${e.durationMs}ms ${e.appliedAt} by=${e.appliedBy}`), e.notes && console.log(` [2mnote: ${e.notes}[0m`);
648
+ }
649
+ return console.log(), 0;
650
+ }, we = async (e) => {
651
+ let t = e[0];
652
+ 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;
653
+ let n = await W(), r = await J(n, _.SqlClient.pipe(h.flatMap((e) => e`SELECT ${e("id")}, ${e("operations")}::text AS ${e("operations")}
654
+ FROM ${e("_voltro_migration_plans")} WHERE ${e("id")} = ${t}`)));
655
+ if (r.length === 0) return console.error(`restore-snapshot: plan '${t}' not found`), 1;
656
+ let i;
657
+ try {
658
+ i = JSON.parse(r[0].operations);
659
+ } catch {
660
+ return console.error(`restore-snapshot: plan '${t}' has corrupted operations JSON`), 1;
661
+ }
662
+ let a = [];
663
+ for (let e of i) e.op?.kind === "drop-column" && e.op.table && e.op.column && a.push({
664
+ table: e.op.table,
665
+ column: e.op.column
666
+ });
667
+ let o = [];
668
+ for (let e of i) e.op?.kind === "drop-table" && e.op.table && o.push({ table: e.op.table });
669
+ if (a.length === 0 && o.length === 0) return console.log(`restore-snapshot: plan '${t}' has no drop-column / drop-table ops to restore`), 0;
670
+ let s = 0;
671
+ for (let e of a) {
672
+ let t = await J(n, _.SqlClient.pipe(h.flatMap((t) => t`
673
+ SELECT column_name FROM information_schema.columns
674
+ WHERE table_schema = 'public'
675
+ AND table_name = ${e.table}
676
+ AND column_name LIKE ${e.column + "__dropped_%"}
677
+ ORDER BY column_name DESC LIMIT 1
678
+ `)));
679
+ if (t.length === 0) {
680
+ console.warn(`restore-snapshot: no snapshot found for ${e.table}.${e.column} — was the plan applied with VOLTRO_SOFT_DROP=1?`);
681
+ continue;
682
+ }
683
+ let r = t[0].column_name;
684
+ await J(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})`);
685
+ }
686
+ let c = 0;
687
+ for (let e of o) {
688
+ let t = await J(n, _.SqlClient.pipe(h.flatMap((t) => t`
689
+ SELECT table_name FROM information_schema.tables
690
+ WHERE table_schema = 'public'
691
+ AND table_name LIKE ${e.table + "__dropped_%"}
692
+ ORDER BY table_name DESC LIMIT 1
693
+ `)));
694
+ if (t.length === 0) {
695
+ console.warn(`restore-snapshot: no snapshot found for table ${e.table} — was the plan applied with VOLTRO_SOFT_DROP=1?`);
696
+ continue;
697
+ }
698
+ let r = t[0].table_name;
699
+ await J(n, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${r}" RENAME TO "${e.table}"`)))), c++, console.log(` ↩ restored table ${e.table} (from ${r})`);
700
+ }
701
+ return console.log(`\n✓ restored ${s} column(s) + ${c} table(s) from plan '${t}'\n`), 0;
702
+ }, Te = async (e) => {
703
+ let t = c(e, "--before");
704
+ 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;
705
+ let n = t.replace(/[^0-9]/g, "");
706
+ if (n.length < 8) return console.error(`gc-snapshots: --before must be at least a full date (YYYY-MM-DD), got "${t}"`), 2;
707
+ let r = (n + "00000000000000").slice(0, 14), i = e.includes("--dry-run"), a = await W(), o = await J(a, _.SqlClient.pipe(h.flatMap((e) => e`
708
+ SELECT table_name, column_name FROM information_schema.columns
709
+ WHERE table_schema = 'public' AND column_name LIKE ${"%__dropped_%"}
710
+ `))), s = await J(a, _.SqlClient.pipe(h.flatMap((e) => e`
711
+ SELECT table_name FROM information_schema.tables
712
+ WHERE table_schema = 'public' AND table_name LIKE ${"%__dropped_%"}
713
+ `))), l = (e) => {
714
+ let t = /__dropped_(\d{14})$/.exec(e);
715
+ return t ? t[1] : null;
716
+ }, u = 0;
717
+ for (let e of o) {
718
+ let t = l(e.column_name);
719
+ t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop column" : "dropping column"} ${e.table_name}.${e.column_name}`), i || await J(a, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`ALTER TABLE "${e.table_name}" DROP COLUMN "${e.column_name}"`)))), u++);
720
+ }
721
+ for (let e of s) {
722
+ let t = l(e.table_name);
723
+ t === null || t >= r || (console.log(` ${i ? "[dry-run] would drop table" : "dropping table"} ${e.table_name}`), i || await J(a, _.SqlClient.pipe(h.flatMap((t) => t.unsafe(`DROP TABLE "${e.table_name}"`)))), u++);
724
+ }
725
+ return console.log(`\n${i ? `[dry-run] ${u} snapshot(s) would be dropped` : `✓ dropped ${u} snapshot(s)`} (older than ${r}).\n`), 0;
726
+ }, Q = async (e) => {
727
+ let n = c(e, "--before");
728
+ if (!n) return console.error("voltro db squash --before <iso-date> [--note \"<text>\"]"), 2;
729
+ let r = n;
730
+ if (Number.isNaN(new Date(r).getTime())) return console.error(`voltro db squash: --before '${r}' is not a valid ISO date`), 2;
731
+ let i = c(e, "--note"), a = z(e), o = await G(a);
732
+ if (o.length === 0) return L.warn("db squash: no schema files found", { root: a }), 1;
733
+ let s = await t({ root: a }), l = C(ae(K(q([
734
+ ...o,
735
+ ...y,
736
+ ...s
737
+ ])))), u = await W();
738
+ try {
739
+ let e = await J(u, se({
740
+ before: r,
741
+ fingerprint: l,
742
+ appliedBy: process.env.USER ?? "cli",
743
+ environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
744
+ ...i === void 0 ? {} : { note: i }
745
+ }));
746
+ return console.log(`\n✓ squashed ${e.squashedCount} migration plan(s)\n snapshot id: ${e.snapshotId}\n fingerprint: ${O(e.snapshotFingerprint)}\n`), 0;
747
+ } catch (e) {
748
+ return console.error(`squash refused: ${e.message}`), 1;
749
+ }
750
+ }, Ee = async (e) => {
751
+ let t = await W(), n = await J(t, _.SqlClient.pipe(h.flatMap((e) => e`
752
+ SELECT ${e("id")}, ${e("fingerprint")}, ${e("appliedAt")}::text AS ${e("appliedAt")}
753
+ FROM ${e("_voltro_migration_plans")}
754
+ ORDER BY ${e("appliedAt")} DESC
755
+ LIMIT 1
756
+ `))), r = C({ tables: (await Y(t)).tables.filter((e) => !e.name.startsWith("_voltro_")) });
757
+ if (n.length === 0) return L.info("db drift: no _voltro_migration_plans rows yet"), console.log(`live user-schema fingerprint: ${O(r)}`), console.log("No baseline to compare against. Run `voltro db apply` to lock in the current state."), 0;
758
+ let i = n[0];
759
+ return i.fingerprint === r ? (L.info(`db drift: live matches last applied (${O(r)})`), 0) : (L.error("db drift: live schema DIVERGED from last applied state"), console.log(), console.log(` last applied: ${O(i.fingerprint)} at ${i.appliedAt} (${i.id})`), console.log(` live now: ${O(r)}`), console.log(), console.log("Probable causes:"), console.log(" - manual DDL ran out-of-band (psql session, another tool)"), console.log(" - someone applied a plan but the row never made it into _voltro_migration_plans"), console.log(), console.log("To reconcile, run `voltro db plan` to see what your code expects vs the live DB."), 4);
760
+ }, De = async (e) => {
761
+ let t = z(e), n = await W(), r = (process.env.NODE_ENV ?? "").toLowerCase() === "production" ? "prod" : (process.env.NODE_ENV ?? "").toLowerCase() === "staging" ? "staging" : "dev";
762
+ await J(n, S(y, U()));
763
+ let i = await J(n, _.SqlClient.pipe(h.flatMap((e) => D(e, {
764
+ projectRoot: t,
765
+ env: r,
766
+ appliedBy: process.env.USER ? `cli:${process.env.USER}` : "cli:user"
767
+ }))));
768
+ return i.applied.length === 0 ? (L.info(`db files: no pending file-based migrations (${i.skipped.length} already applied)`), 0) : (L.info(`db files: applied ${i.applied.length} migration(s)`, {
769
+ applied: i.applied.map((e) => `${e.id} (${e.durationMs}ms)`),
770
+ skipped: i.skipped.length
771
+ }), 0);
772
+ }, Oe = async (e) => {
773
+ let t = z(e), n = e.find((e) => !e.startsWith("--"));
774
+ if (!n) return L.error("voltro db rollback-file <migration-id>"), 2;
775
+ 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;
776
+ let r = await J(await W(), _.SqlClient.pipe(h.flatMap((e) => E(e, {
777
+ projectRoot: t,
778
+ id: n
779
+ }))));
780
+ return L.info(`db rollback-file: rolled back ${r.id} in ${r.durationMs}ms`), 0;
781
+ }, ke = () => {
782
+ let e = /* @__PURE__ */ new Date();
783
+ return [
784
+ e.getUTCFullYear(),
785
+ String(e.getUTCMonth() + 1).padStart(2, "0"),
786
+ String(e.getUTCDate()).padStart(2, "0"),
787
+ String(e.getUTCHours()).padStart(2, "0"),
788
+ String(e.getUTCMinutes()).padStart(2, "0"),
789
+ String(e.getUTCSeconds()).padStart(2, "0")
790
+ ].join("");
791
+ }, Ae = (e) => e.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "migration", je = (e, t) => `import { defineMigration, SqlClient } from '@voltro/database'
792
+ import { Effect } from 'effect'
793
+
794
+ // A hand-written migration. Reach for this when the DECLARATIVE differ can't
795
+ // express the change — a data backfill, a value re-encode, a table rename that
796
+ // must preserve rows. For plain schema shape changes (add a column/table/index)
797
+ // you usually DON'T need this: edit the entity + run \`voltro db apply\`, which
798
+ // diffs your declared schema against the live DB automatically.
799
+ //
800
+ // \`voltro db migrate\` applies every pending *.migration.ts in timestamp order
801
+ // and records each in \`_voltro_migrations\`; \`voltro db rollback\` runs \`undo\`.
802
+ export default defineMigration({
803
+ id: '${e}',
804
+ name: '${t}',
805
+ steps: ({ step }) => [
806
+ step('${t}', {
807
+ // Forward migration. Runs inside a transaction; the SqlClient is provided.
808
+ apply: Effect.gen(function* () {
809
+ const sql = yield* SqlClient.SqlClient
810
+ // TODO: your change, e.g.
811
+ // yield* sql\`ALTER TABLE widgets ADD COLUMN archived boolean NOT NULL DEFAULT false\`
812
+ }),
813
+ // The inverse — what \`voltro db rollback\` runs to undo the step. Keep it
814
+ // an exact reverse so a rollback restores the prior state.
815
+ undo: Effect.gen(function* () {
816
+ const sql = yield* SqlClient.SqlClient
817
+ // TODO: reverse the apply above, e.g.
818
+ // yield* sql\`ALTER TABLE widgets DROP COLUMN archived\`
819
+ }),
820
+ }),
821
+ ],
822
+ })
823
+ `, $ = {
824
+ plan: be,
825
+ apply: Se,
826
+ plans: Ce,
827
+ drift: Ee,
828
+ files: De,
829
+ "rollback-file": Oe,
830
+ migrate: fe,
831
+ rollback: pe,
832
+ status: me,
833
+ seed: ge,
834
+ generate: async (e) => {
835
+ let { value: t, rest: n } = s(e, "--root"), { value: r, rest: i } = s(n, "--name"), a = t ? p(t) : process.cwd(), o = Ae(r ?? l(i) ?? "migration"), c = `${ke()}_${o}`, u = d(a, "migrations"), m = d(u, `${c}.migration.ts`);
836
+ return await g.mkdir(u, { recursive: !0 }), await g.writeFile(m, je(c, o), "utf8"), L.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;
837
+ },
838
+ squash: Q,
839
+ "restore-snapshot": we,
840
+ "gc-snapshots": Te
841
+ }, Me = async (e) => {
842
+ let [t, ...n] = e;
843
+ if (!t) {
844
+ console.error("voltro db <subcommand> [args]"), console.error("subcommands:");
845
+ for (let e of Object.keys($)) console.error(` ${e}`);
846
+ return 1;
847
+ }
848
+ let r = $[t];
849
+ if (!r) return console.error(`voltro db: unknown subcommand '${t}'`), 2;
850
+ (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)");
851
+ let i = (e) => {
852
+ V(e) ? L.error(H) : console.error(e), process.exit(1);
853
+ };
854
+ process.on("uncaughtException", i);
855
+ try {
856
+ return await r(n);
857
+ } finally {
858
+ process.off("uncaughtException", i);
859
+ }
860
+ };
861
+ //#endregion
862
+ export { V as a, ce as c, K as i, B as n, Me as o, G as r, k as s, H as t };