@voltro/cli 0.50.0 → 0.51.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 (111) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/dist/{apiBuild-CFYXo06U.js → apiBuild-CPDHXF72.js} +2 -2
  3. package/dist/apiBuild-GDKuGOMV.js +2 -0
  4. package/dist/appGraph-CFmFKHb2.js +2 -0
  5. package/dist/appGraph-yRuKjOba.js +111 -0
  6. package/dist/bin.js +1 -1
  7. package/dist/{build-CnSH-zru.js → build-DETLZAFt.js} +271 -240
  8. package/dist/{checkCommand-OL41BMIb.js → checkCommand-CWcnDArJ.js} +1 -1
  9. package/dist/{checkCommand-Do3SQbLx.js → checkCommand-DNuPiWMc.js} +56 -46
  10. package/dist/{codegenCommand-BJzaRz6q.js → codegenCommand-BNBHcNNj.js} +2 -2
  11. package/dist/{codemodRunner-De3k0pRd.js → codemodRunner-BDVixlSw.js} +278 -136
  12. package/dist/{commands-By23Ljny.js → commands-B1OiS9bX.js} +24 -23
  13. package/dist/{dashboardCommand-DN1rAABE.js → dashboardCommand-C-vvPY1B.js} +5 -5
  14. package/dist/{dataCommand-B93TUBIx.js → dataCommand-C1GxXW5q.js} +458 -384
  15. package/dist/dbCommand-C27HIsGE.js +2 -0
  16. package/dist/{dbCommand-Bf7Cy0mn.js → dbCommand-If4Y1xQ-.js} +1 -1
  17. package/dist/{dev-BWwE0QjK.js → dev-CK522MV5.js} +1 -1
  18. package/dist/{dev-1yu42ubu.js → dev-kdAg9Q7l.js} +1246 -1246
  19. package/dist/doctorCommand-BK4l18eG.js +2 -0
  20. package/dist/{doctorCommand-y-EbP8st.js → doctorCommand-nKmeW78u.js} +42 -26
  21. package/dist/{doctorSourceRelations-Dd48LPvO.js → doctorSourceRelations-c5mlN5HI.js} +66 -34
  22. package/dist/{dormancyCommand-Dc7KRNCG.js → dormancyCommand-CY3wa_SW.js} +1 -1
  23. package/dist/{embeddingsCommand-kPs132bB.js → embeddingsCommand-BDLIgje_.js} +1 -1
  24. package/dist/{envCommand-E12zSt51.js → envCommand-C6V_xVlT.js} +13 -13
  25. package/dist/{evolveCommand-B3nagPVc.js → evolveCommand-D3c4DSfN.js} +2 -2
  26. package/dist/{generateCommand-CcyvH2ve.js → generateCommand-DTNPGjxo.js} +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/{infoCommand-Cq0Yz98u.js → infoCommand-BnRFEF1o.js} +1 -1
  29. package/dist/{inspect-B7U7Cl_Z.js → inspect-CtL_xTbu.js} +1 -0
  30. package/dist/inspect-CuGDYES0.js +2 -0
  31. package/dist/manifestBuild-CPjhvM62.js +2 -0
  32. package/dist/{manifestBuild-BK42hu0k.js → manifestBuild-CuU1VrSm.js} +2 -2
  33. package/dist/{migrate-D3MK9BpK.js → migrate-DehuBakM.js} +1 -1
  34. package/dist/{runtimeTrace-BpLUcrfy.js → runtimeTrace-ZsBU7Tkx.js} +1 -1
  35. package/dist/{sdkgen-dl4-WKF6.js → sdkgen-O4XqWOjM.js} +1 -1
  36. package/dist/serveCommand-BRnPCxVd.js +2 -0
  37. package/dist/{serveCommand-8CTzQCY0.js → serveCommand-DdiYNBBu.js} +4 -4
  38. package/dist/serveEntry.js +1 -1
  39. package/dist/{start-7HcSIKEp.js → start-BLNmWkLa.js} +331 -327
  40. package/dist/{start-DvlYDyAr.js → start-Dzicuyw8.js} +1 -1
  41. package/dist/startEntry.js +1 -1
  42. package/dist/{updateCommand-i8JbrLAA.js → updateCommand-Bs322Q78.js} +1 -1
  43. package/dist/updateCommand-eXB35SEv.js +2 -0
  44. package/dist/{webDev-B4IEMB0n.js → webDev-B-ubQEMX.js} +283 -224
  45. package/dist/webDev-DposiF3j.js +2 -0
  46. package/dist/{webhooksCommand-B0bdyuQ4.js → webhooksCommand-FLYY9IXh.js} +1 -1
  47. package/package.json +41 -17
  48. package/templates/AGENTS.core.md +15 -5
  49. package/templates/AGENTS.md +16 -6
  50. package/templates/agent-docs/_index.md +1 -1
  51. package/templates/agent-docs/cli.md +92 -3
  52. package/templates/agent-docs/data.md +36 -0
  53. package/templates/agent-docs/internationalization.md +131 -13
  54. package/templates/agent-docs/whats-new.md +73 -27
  55. package/templates/apps/api-ai/package.json +7 -7
  56. package/templates/apps/api-auth/package.json +8 -8
  57. package/templates/apps/api-backend/package.json +7 -7
  58. package/templates/apps/api-backend-deactivation/package.json +7 -7
  59. package/templates/apps/api-backend-mail/package.json +8 -8
  60. package/templates/apps/api-backend-mariadb/package.json +9 -9
  61. package/templates/apps/api-backend-sqlite/package.json +8 -8
  62. package/templates/apps/api-backend-storage/package.json +8 -8
  63. package/templates/apps/api-cms/package.json +10 -10
  64. package/templates/apps/api-collab/package.json +8 -8
  65. package/templates/apps/api-data-advanced/package.json +8 -8
  66. package/templates/apps/api-durable/package.json +8 -8
  67. package/templates/apps/api-feature-flags/package.json +9 -9
  68. package/templates/apps/api-governance/package.json +8 -8
  69. package/templates/apps/api-kv/package.json +8 -8
  70. package/templates/apps/api-moderation/package.json +8 -8
  71. package/templates/apps/api-observability/package.json +8 -8
  72. package/templates/apps/api-ratelimit/package.json +8 -8
  73. package/templates/apps/api-rbac/package.json +8 -8
  74. package/templates/apps/api-rest/package.json +7 -7
  75. package/templates/apps/api-saas/package.json +11 -11
  76. package/templates/apps/api-saas-starter/package.json +10 -10
  77. package/templates/apps/api-search/package.json +8 -8
  78. package/templates/apps/api-status/package.json +8 -8
  79. package/templates/apps/api-versioning/package.json +8 -8
  80. package/templates/apps/api-webhooks/package.json +9 -9
  81. package/templates/apps/changelog/package.json +6 -6
  82. package/templates/apps/edge-functions/package.json +2 -2
  83. package/templates/apps/frontend-admin/package.json +8 -8
  84. package/templates/apps/frontend-app/package.json +9 -9
  85. package/templates/apps/frontend-auth/package.json +8 -8
  86. package/templates/apps/frontend-blank/package.json +7 -7
  87. package/templates/apps/frontend-cms/package.json +9 -9
  88. package/templates/apps/frontend-collab/package.json +10 -10
  89. package/templates/apps/frontend-contact/package.json +7 -7
  90. package/templates/apps/frontend-dashboard/package.json +7 -7
  91. package/templates/apps/frontend-docs/package.json +7 -7
  92. package/templates/apps/frontend-i18n/package.json +6 -6
  93. package/templates/apps/frontend-landing/package.json +7 -7
  94. package/templates/apps/frontend-portal/package.json +8 -8
  95. package/templates/apps/frontend-saas/package.json +8 -8
  96. package/templates/apps/frontend-spa/package.json +7 -7
  97. package/templates/apps/frontend-ssr/package.json +7 -7
  98. package/templates/apps/frontend-ssr-api/package.json +8 -8
  99. package/templates/apps/frontend-static-blog/package.json +6 -6
  100. package/templates/apps/frontend-status/package.json +8 -8
  101. package/templates/apps/mobile-app/package.json +4 -4
  102. package/dist/apiBuild-BWo8KI6n.js +0 -2
  103. package/dist/appGraph-KGDPTuTy.js +0 -110
  104. package/dist/appGraph-zuMGKVYX.js +0 -2
  105. package/dist/dbCommand-Bnl7MSGC.js +0 -2
  106. package/dist/doctorCommand-BmovAwb7.js +0 -2
  107. package/dist/inspect-DUze25t0.js +0 -2
  108. package/dist/manifestBuild-j0n109tt.js +0 -2
  109. package/dist/serveCommand-C_WI4TTG.js +0 -2
  110. package/dist/updateCommand-D91-TZ7E.js +0 -2
  111. package/dist/webDev-B79ji8M5.js +0 -2
@@ -1,20 +1,20 @@
1
1
  import { i as e, t } from "./subcommandNames-DpYs3DXr.js";
2
2
  import { t as n } from "./cliError-BmdYnghb.js";
3
- import { c as r, i, o as a, z as o } from "./dev-1yu42ubu.js";
3
+ import { c as r, i, o as a, z as o } from "./dev-kdAg9Q7l.js";
4
4
  import { n as s, t as c } from "./cliArgs-D4p8n7EE.js";
5
5
  import { r as l } from "./dialectDriver-czCHYpeH.js";
6
6
  import { i as u, n as d, r as f, t as p } from "./dataProfile-Du3ztYxv.js";
7
7
  import { o as m, t as h } from "./inspectFetch-EMuhTG_9.js";
8
- import { r as g } from "./dbCommand-Bf7Cy0mn.js";
8
+ import { r as g } from "./dbCommand-If4Y1xQ-.js";
9
9
  import { resolve as _ } from "node:path";
10
10
  import { createLogger as v } from "@voltro/logger";
11
- import { DATA_TRANSFERS_TABLE as y, REPLACE_IN_PROGRESS_TABLE as b, _voltroDataTransfersTable as x, _voltroReplaceInProgressTable as S, connectionIdentity as C, derivedRowId as ee, ensureTableRegistered as te, normaliseTarget as ne, sameDatabase as re, streamTable as ie } from "@voltro/database";
12
- import { Cause as w, Effect as T, Exit as ae, Option as oe, Stream as se } from "effect";
13
- import { SqlClient as ce } from "@effect/sql";
14
- import { declaredSnapshot as le, fingerprintSchema as ue, introspectSchema as de } from "@voltro/database/sql";
15
- import { STORAGE_REFS_TABLE as fe, resolveStorageProvider as E } from "@voltro/plugin-storage";
16
- import { createHash as pe } from "node:crypto";
17
- import { ASSETS_DIR as D, ASSET_INDEX_FILE as O, BundleError as me, asImportError as he, assessLiveImport as ge, backupArtifactName as k, backupCommand as _e, captureAssetsToCas as ve, classificationFromSnapshot as ye, decryptStream as A, encryptStream as be, packBundle as xe, peekBundle as j, previewMasking as Se, restoreAssetsFromCas as Ce, restoreCommand as we, runExport as Te, runImport as Ee, runNativeStep as M, storageAssetSink as De, storageAssetSource as Oe, storageBlobSink as ke, storageBlobSource as Ae, unpackBundle as je, unpackBundleToDir as Me } from "@voltro/data-transfer";
11
+ import { DATA_TRANSFERS_TABLE as y, REPLACE_IN_PROGRESS_TABLE as b, _voltroDataTransfersTable as x, _voltroReplaceInProgressTable as S, connectionIdentity as C, derivedRowId as w, ensureTableRegistered as T, normaliseTarget as ee, sameDatabase as te, streamTable as ne } from "@voltro/database";
12
+ import { Cause as E, Effect as D, Exit as re, Option as ie, Stream as ae } from "effect";
13
+ import { SqlClient as oe } from "@effect/sql";
14
+ import { declaredSnapshot as se, fingerprintSchema as ce, introspectSchema as le } from "@voltro/database/sql";
15
+ import { STORAGE_REFS_TABLE as ue, appStorageProvider as de, configuredStorageProvider as O } from "@voltro/plugin-storage";
16
+ import { createHash as fe } from "node:crypto";
17
+ import { ASSETS_DIR as k, ASSET_INDEX_FILE as A, BundleError as pe, asImportError as me, assessLiveImport as he, backupArtifactName as j, backupCommand as ge, captureAssetsToCas as _e, classificationFromSnapshot as ve, decryptStream as M, encryptStream as ye, packBundle as be, peekBundle as N, previewMasking as xe, restoreAssetsFromCas as Se, restoreCommand as Ce, runExport as we, runImport as Te, runNativeStep as Ee, storageAssetSink as De, storageAssetSource as Oe, storageBlobSink as ke, storageBlobSource as Ae, unpackBundle as je, unpackBundleToDir as Me } from "@voltro/data-transfer";
18
18
  import { request as Ne } from "node:http";
19
19
  import { request as Pe } from "node:https";
20
20
  //#region src/longRequest.ts
@@ -26,7 +26,7 @@ var Fe = /* @__PURE__ */ new Set([
26
26
  "ENOTFOUND",
27
27
  "ECONNRESET",
28
28
  "EAI_AGAIN"
29
- ]), N = class extends Error {
29
+ ]), P = class extends Error {
30
30
  url;
31
31
  cause;
32
32
  constructor(e, t) {
@@ -39,13 +39,13 @@ var Fe = /* @__PURE__ */ new Set([
39
39
  })();
40
40
  super(`the instance at ${n} is not answering (${t.code ?? "connect failed"}).`), this.url = e, this.cause = t, this.name = "InstanceUnreachable";
41
41
  }
42
- }, P = class extends Error {
42
+ }, F = class extends Error {
43
43
  elapsedMs;
44
44
  url;
45
45
  constructor(e, t) {
46
46
  super(`no response after ${Math.round(e / 1e3)}s`), this.elapsedMs = e, this.url = t, this.name = "LongRequestTimeout";
47
47
  }
48
- }, F = async (e, t, n, r) => {
48
+ }, I = async (e, t, n, r) => {
49
49
  let i = new URL(e), a = i.protocol === "https:" ? Pe : Ne, o = Date.now();
50
50
  return await new Promise((s, c) => {
51
51
  let l = a({
@@ -64,8 +64,8 @@ var Fe = /* @__PURE__ */ new Set([
64
64
  })), e.on("error", c);
65
65
  });
66
66
  if (r?.timeoutMs !== void 0 && r.timeoutMs > 0 && l.setTimeout(r.timeoutMs, () => {
67
- l.destroy(new P(Date.now() - o, e));
68
- }), l.on("error", (t) => c(Fe.has(t.code ?? "") ? new N(e, t) : t)), typeof t == "string" || Buffer.isBuffer(t)) {
67
+ l.destroy(new F(Date.now() - o, e));
68
+ }), l.on("error", (t) => c(Fe.has(t.code ?? "") ? new P(e, t) : t)), typeof t == "string" || Buffer.isBuffer(t)) {
69
69
  l.end(t);
70
70
  return;
71
71
  }
@@ -78,7 +78,7 @@ var Fe = /* @__PURE__ */ new Set([
78
78
  }
79
79
  })();
80
80
  });
81
- }, I = (e) => `${e.message}\n That address came from --api-url (or VOLTRO_API_URL) — it is the running instance, not the database.\n Nothing was sent, so nothing was imported. Check the instance is up and reachable from here\n (a VPN, an ingress or a stopped deployment are the usual three), e.g. curl -sS ${e.url}/_voltro/inspect/app`, Ie = (e, t) => `the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. This is a client-side deadline, not a failure on the server: the upload itself finished long before, and the response arrives only when the import does. The instance is very probably STILL IMPORTING and will finish.\n` + (t === "replace" ? " Do NOT simply re-run: --mode replace empties the target first, so a second run started while the first is mid-flight would empty it under the first one. Check the instance's log for the import's completion, and check the target's row counts, before deciding." : ` Re-running the same bundle is safe once the first run has finished (--mode ${t} is idempotent), but check the instance's log first — the work may already be done.`) + "\n To keep a deadline anyway, pass --timeout <seconds>; the default is to wait.", Le = 16777216, Re = async function* (e, t) {
81
+ }, L = (e) => `${e.message}\n That address came from --api-url (or VOLTRO_API_URL) — it is the running instance, not the database.\n Nothing was sent, so nothing was imported. Check the instance is up and reachable from here\n (a VPN, an ingress or a stopped deployment are the usual three), e.g. curl -sS ${e.url}/_voltro/inspect/app`, Ie = (e, t) => `the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. This is a client-side deadline, not a failure on the server: the upload itself finished long before, and the response arrives only when the import does. The instance is very probably STILL IMPORTING and will finish.\n` + (t === "replace" ? " Do NOT simply re-run: --mode replace empties the target first, so a second run started while the first is mid-flight would empty it under the first one. Check the instance's log for the import's completion, and check the target's row counts, before deciding." : ` Re-running the same bundle is safe once the first run has finished (--mode ${t} is idempotent), but check the instance's log first — the work may already be done.`) + "\n To keep a deadline anyway, pass --timeout <seconds>; the default is to wait.", Le = 16777216, Re = async function* (e, t) {
82
82
  let n = [], r = 0, i = 0, a = function* (e) {
83
83
  let t = Buffer.concat(n);
84
84
  n = [], r = 0;
@@ -100,7 +100,7 @@ var Fe = /* @__PURE__ */ new Set([
100
100
  i += a.byteLength, yield s;
101
101
  }
102
102
  yield* a(!0);
103
- }, ze = (e) => pe("sha256").update(Buffer.from(e)).digest("hex"), Be = async (e) => {
103
+ }, ze = (e) => fe("sha256").update(Buffer.from(e)).digest("hex"), Be = async (e) => {
104
104
  let t = 0, n = 0, r = {
105
105
  status: 0,
106
106
  body: {}
@@ -131,7 +131,7 @@ var Fe = /* @__PURE__ */ new Set([
131
131
  bytes: t,
132
132
  resumedFrom: e.alreadyHeld
133
133
  };
134
- }, L = "voltro-backup-stamp.json", Ve = (e) => ({
134
+ }, R = "voltro-backup-stamp.json", Ve = (e) => ({
135
135
  stampVersion: 1,
136
136
  dialect: e.dialect,
137
137
  schemaFingerprint: e.schemaFingerprint,
@@ -150,9 +150,15 @@ var Fe = /* @__PURE__ */ new Set([
150
150
  if (n.stampVersion !== 1 || typeof n.dialect != "string" || typeof n.voltroVersion != "string" || typeof n.createdAt != "string") return null;
151
151
  let r = n.schemaFingerprint;
152
152
  if (r !== null && typeof r != "string") return null;
153
- let i = n.assets, a = typeof i == "object" && i && typeof i.count == "number" && typeof i.totalBytes == "number" ? {
153
+ let i = n.assets, a = (e) => {
154
+ let t = i?.[e];
155
+ return typeof t == "number" ? t : void 0;
156
+ }, o = typeof i == "object" && i && typeof i.count == "number" && typeof i.totalBytes == "number" ? {
154
157
  count: i.count,
155
- totalBytes: i.totalBytes
158
+ totalBytes: i.totalBytes,
159
+ ...a("references") === void 0 ? {} : { references: a("references") },
160
+ ...a("objects") === void 0 ? {} : { objects: a("objects") },
161
+ ...a("objectBytes") === void 0 ? {} : { objectBytes: a("objectBytes") }
156
162
  } : null;
157
163
  return {
158
164
  stampVersion: 1,
@@ -160,85 +166,121 @@ var Fe = /* @__PURE__ */ new Set([
160
166
  schemaFingerprint: r,
161
167
  voltroVersion: n.voltroVersion,
162
168
  createdAt: n.createdAt,
163
- assets: a
169
+ assets: o
164
170
  };
165
171
  }, Ue = (e) => {
166
- let { stamp: t, currentDialect: n, currentFingerprint: r } = e;
172
+ let { stamp: t, currentDialect: n, targetFingerprint: r } = e;
167
173
  if (t === null) return {
168
- warn: `no ${L} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
174
+ warn: `no ${R} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
169
175
  info: []
170
176
  };
171
177
  let i = [`backup produced by @voltro/cli ${t.voltroVersion} at ${t.createdAt}`, `schema fingerprint: ${t.schemaFingerprint ?? "<none recorded>"}`];
172
178
  return t.dialect === n ? t.schemaFingerprint !== null && r !== null && t.schemaFingerprint !== r ? {
173
- warn: `schema skew — the backup's schema (${t.schemaFingerprint.slice(0, 12)}) differs from what this code declares (${r.slice(0, 12)}). Run \`voltro db apply\` after the restore, or the app may boot against a schema it doesn't understand.`,
179
+ warn: `schema skew — the backup's schema (${t.schemaFingerprint.slice(0, 12)}) differs from the schema this TARGET database currently has (${r.slice(0, 12)}). This compares the backup against the target's present state, not against what your code declares. The restore replaces the target's schema with the backup's; run \`voltro db apply\` afterwards to bring it to what the code declares.`,
174
180
  info: i
175
181
  } : { info: i } : {
176
182
  refuse: `dialect mismatch — backup is a ${t.dialect} dump, but DB_DIALECT is ${n}. A cross-dialect restore is never valid.`,
177
183
  info: i
178
184
  };
179
185
  }, We = async (e, t) => {
180
- let n = ee("dimp", `${t.direction}-${t.artifact}-${Date.now()}-${Math.random()}`);
186
+ let n = w("dimp", `${t.direction}-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
187
+ id: n,
188
+ direction: t.direction,
189
+ startedAt: /* @__PURE__ */ new Date(),
190
+ finishedAt: null,
191
+ failure: null,
192
+ mode: `native ${t.dialect}${t.assets ? " + assets" : ""}`,
193
+ via: "cli",
194
+ bundle: t.artifact,
195
+ sourceFingerprint: null,
196
+ tablesWritten: 0,
197
+ rowsWritten: 0,
198
+ staged: null
199
+ };
181
200
  try {
182
- return te(x), await e.insert(y, {
183
- id: n,
184
- direction: t.direction,
185
- startedAt: /* @__PURE__ */ new Date(),
186
- finishedAt: null,
187
- failure: null,
188
- mode: `native ${t.dialect}${t.assets ? " + assets" : ""}`,
189
- via: "cli",
190
- bundle: t.artifact,
191
- sourceFingerprint: null,
192
- tablesWritten: 0,
193
- rowsWritten: 0,
194
- staged: null
195
- }), {
201
+ return T(x), await e.insert(y, r), {
196
202
  runId: n,
197
- recorded: !0
203
+ recorded: !0,
204
+ opened: r
198
205
  };
199
206
  } catch {
200
207
  return {
201
208
  runId: n,
202
- recorded: !1
209
+ recorded: !1,
210
+ opened: r
203
211
  };
204
212
  }
205
- }, R = async (e, t, n) => {
206
- t.recorded && await e.update(y, t.runId, {
213
+ }, z = async (e, t, n) => {
214
+ if (!t.recorded) return;
215
+ let r = {
207
216
  finishedAt: /* @__PURE__ */ new Date(),
208
217
  failure: n.failure ?? null,
209
218
  ...n.assets === void 0 ? {} : { rowsWritten: n.assets }
210
- }).then(() => void 0, () => void 0);
219
+ };
220
+ if (await e.update(y, t.runId, r).then(() => void 0, () => void 0), !await e.query({
221
+ table: y,
222
+ predicate: void 0,
223
+ order: [],
224
+ take: void 0,
225
+ skip: void 0,
226
+ projection: void 0
227
+ }).then((e) => e.some((e) => String(e.id ?? "") === t.runId), () => !0)) try {
228
+ T(x), await e.insert(y, {
229
+ ...t.opened,
230
+ ...r
231
+ });
232
+ } catch {}
211
233
  }, Ge = async (e, t) => {
212
- let n = ee("repl", `restore-${t.artifact}-${Date.now()}-${Math.random()}`);
234
+ let n = w("repl", `restore-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
235
+ id: n,
236
+ startedAt: /* @__PURE__ */ new Date(),
237
+ kind: "restore",
238
+ tables: null,
239
+ capture: t.artifact,
240
+ via: "cli"
241
+ };
213
242
  try {
214
- te(S), await e.insert(b, {
215
- id: n,
216
- startedAt: /* @__PURE__ */ new Date(),
217
- kind: "restore",
218
- tables: null,
219
- capture: t.artifact,
220
- via: "cli"
221
- });
243
+ T(S), await e.insert(b, r);
222
244
  } catch {
223
245
  return;
224
246
  }
225
- return async () => {
226
- await e.deleteMany(b, { where: {
227
- column: "id",
228
- op: "eq",
229
- value: n
230
- } }).then(() => void 0, () => void 0);
247
+ let i = async () => (await e.query({
248
+ table: b,
249
+ predicate: void 0,
250
+ order: [],
251
+ take: void 0,
252
+ skip: void 0,
253
+ projection: void 0
254
+ })).some((e) => String(e.id ?? "") === n);
255
+ return {
256
+ reassert: async () => {
257
+ try {
258
+ if (await i()) return "held";
259
+ } catch {}
260
+ try {
261
+ return T(S), await e.insert(b, r), "rewritten";
262
+ } catch {
263
+ return "lost";
264
+ }
265
+ },
266
+ clear: async () => {
267
+ await e.deleteMany(b, { where: {
268
+ column: "id",
269
+ op: "eq",
270
+ value: n
271
+ } }).then(() => void 0, () => void 0);
272
+ }
231
273
  };
232
274
  }, Ke = async (e) => {
233
- let t = await T.runPromiseExit(e);
234
- if (ae.isSuccess(t)) return;
235
- let n = w.failureOption(t.cause);
236
- if (oe.isSome(n)) {
275
+ let t = await D.runPromiseExit(e);
276
+ if (re.isSuccess(t)) return;
277
+ let n = E.failureOption(t.cause);
278
+ if (ie.isSome(n)) {
237
279
  let e = n.value;
238
280
  return String(e?.message ?? e);
239
281
  }
240
- return w.pretty(t.cause);
241
- }, z = (e) => {
282
+ return E.pretty(t.cause);
283
+ }, B = (e) => {
242
284
  try {
243
285
  let t = new URL(e), n = t.pathname.replace(/^\//, "");
244
286
  return `${t.hostname.toLowerCase()}:${t.port || "(default)"}/${n}`;
@@ -251,7 +293,7 @@ var Fe = /* @__PURE__ */ new Set([
251
293
  ok: !1,
252
294
  error: "no drill target — pass `--drill-url <url>` or set `DRILL_DB_URL`. The drill restores into a THROWAWAY database (never the live one), so it needs its own connection."
253
295
  };
254
- let n = z(t);
296
+ let n = B(t);
255
297
  return e.liveKeys.some((e) => e === n) ? {
256
298
  ok: !1,
257
299
  error: `drill target (${n}) is the LIVE database — a restore drill must target a throwaway DB, never production. It would --clean and overwrite it.`
@@ -274,7 +316,7 @@ var Fe = /* @__PURE__ */ new Set([
274
316
  pass: !0,
275
317
  lines: [`PASS — restored ${r} table(s); schema fingerprint matches the backup stamp (${n.slice(0, 12)}).`]
276
318
  };
277
- }, B = v({ scope: "voltro:data" }), Ye = async () => {
319
+ }, V = v({ scope: "voltro:data" }), Ye = async () => {
278
320
  try {
279
321
  let { createRequire: e } = await import("node:module"), { join: t } = await import("node:path"), { readFileSync: n } = await import("node:fs"), r = e(t(process.cwd(), "package.json")).resolve("@voltro/cli/package.json");
280
322
  return JSON.parse(n(r, "utf8")).version ?? "unknown";
@@ -283,14 +325,14 @@ var Fe = /* @__PURE__ */ new Set([
283
325
  }
284
326
  }, Xe = async (e) => {
285
327
  try {
286
- return ue(await tt(e));
328
+ return ce(await tt(e));
287
329
  } catch {
288
330
  return null;
289
331
  }
290
- }, V = (e) => {
332
+ }, H = (e) => {
291
333
  if (e.event !== "done") return;
292
334
  let t = e.total === void 0 ? "" : ` / ${e.total}`;
293
- B.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
335
+ V.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
294
336
  }, Ze = async (e) => {
295
337
  let t = (e) => e?.meta?.databaseId;
296
338
  if (e) {
@@ -312,20 +354,20 @@ var Fe = /* @__PURE__ */ new Set([
312
354
  }
313
355
  return null;
314
356
  }, Qe = async (e) => {
315
- let t = e.includes("--allow-live"), n = t ? null : await Ze(H(e, "--api-url")), r = C({
316
- dialect: W(),
357
+ let t = e.includes("--allow-live"), n = t ? null : await Ze(U(e, "--api-url")), r = C({
358
+ dialect: G(),
317
359
  ...a()
318
- }), i = ne({
319
- dialect: W(),
360
+ }), i = ee({
361
+ dialect: G(),
320
362
  ...a()
321
- }), { refuse: o, message: s } = ge({
363
+ }), { refuse: o, message: s } = he({
322
364
  liveInstance: n?.label ?? null,
323
365
  allowLive: t,
324
- sameTarget: re(r, n?.databaseId),
366
+ sameTarget: te(r, n?.databaseId),
325
367
  ...i === void 0 ? {} : { targetLabel: `${i.host}:${i.port}/${i.database}` }
326
368
  });
327
- return !o || (B.error(s ?? "refusing: live instance detected"), !1);
328
- }, H = s, $e = [
369
+ return !o || (V.error(s ?? "refusing: live instance detected"), !1);
370
+ }, U = s, $e = [
329
371
  "--api-url",
330
372
  "--bundle-key",
331
373
  "--compression",
@@ -345,50 +387,50 @@ var Fe = /* @__PURE__ */ new Set([
345
387
  "--timeout",
346
388
  "--tenant",
347
389
  "--token"
348
- ], U = (e) => c(e, $e), W = () => {
390
+ ], W = (e) => c(e, $e), G = () => {
349
391
  let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
350
392
  if (e === "memory") throw new n("voltro data: requires a real dialect, not DB_DIALECT=memory.");
351
393
  return e;
352
394
  }, et = async (e, t) => {
353
395
  let n = (await l(e)).makeSqlLayer(t);
354
- return T.runPromise(ce.SqlClient.pipe(T.flatMap(de), T.provide(n)));
396
+ return D.runPromise(oe.SqlClient.pipe(D.flatMap(le), D.provide(n)));
355
397
  }, tt = (e) => et(e, a()), nt = () => {
356
398
  let e = a(), t = [];
357
- return e.url && t.push(z(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
358
- }, G = async () => {
399
+ return e.url && t.push(B(e.url)), (e.host || e.database) && t.push(`${(e.host ?? "localhost").toLowerCase()}:${e.port ?? "(default)"}/${e.database ?? ""}`), t;
400
+ }, K = async () => {
359
401
  let e = process.cwd(), t = await r(e);
360
402
  if (!t) throw new n("voltro data: no app.config.ts found in the current directory.");
361
- let a = await i(t, void 0), o = W();
403
+ let a = await i(t, void 0), o = G();
362
404
  return {
363
405
  store: a,
364
406
  snapshot: await tt(o),
365
407
  dialect: o
366
408
  };
367
- }, K = (e) => ie(e, {
368
- table: fe,
409
+ }, rt = (e) => ne(e, {
410
+ table: ue,
369
411
  chunkSize: 500
370
- }).pipe(se.map((e) => ({
412
+ }).pipe(ae.map((e) => ({
371
413
  key: String(e.key),
372
414
  contentType: String(e.contentType ?? "application/octet-stream"),
373
415
  size: Number(e.size ?? 0)
374
- }))), q = (e) => !!e && e.endsWith(".vbundle"), J = (e) => H(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, rt = (e) => e.includes("--encrypt") || H(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, it = async (e, t) => {
416
+ }))), q = (e) => !!e && e.endsWith(".vbundle"), J = (e) => U(e, "--passphrase") ?? process.env.VOLTRO_BUNDLE_KEY, it = (e) => e.includes("--encrypt") || U(e, "--passphrase") !== void 0 || process.env.VOLTRO_BUNDLE_KEY !== void 0, at = async (e, t) => {
375
417
  let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
376
418
  await r(i.from(e), n(t));
377
419
  }, Y = (e) => ({ async *[Symbol.asyncIterator]() {
378
420
  let { createReadStream: t } = await import("node:fs");
379
421
  for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
380
- } }), at = async (e, t) => {
422
+ } }), ot = async (e, t) => {
381
423
  if (t !== void 0 || e === void 0) return null;
382
424
  try {
383
- let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a = q(e) ? (await j(Y(i), ["manifest.json"])).get("manifest.json") : await r.readFile(n(i, "manifest.json"));
425
+ let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a = q(e) ? (await N(Y(i), ["manifest.json"])).get("manifest.json") : await r.readFile(n(i, "manifest.json"));
384
426
  if (a === void 0) return null;
385
427
  let o = (JSON.parse(Buffer.from(a).toString("utf8")).tables ?? []).map((e) => e.name).filter((e) => typeof e == "string");
386
428
  return o.length > 0 ? o : null;
387
429
  } catch {
388
430
  return null;
389
431
  }
390
- }, ot = (e) => {
391
- let t = H(e, "--max-request-seconds") ?? process.env.VOLTRO_MAX_REQUEST_SECONDS;
432
+ }, st = (e) => {
433
+ let t = U(e, "--max-request-seconds") ?? process.env.VOLTRO_MAX_REQUEST_SECONDS;
392
434
  if (t === void 0) return { pollMs: 2e3 };
393
435
  let r = Number(t);
394
436
  if (!Number.isFinite(r) || r <= 0) throw new n(`--max-request-seconds expects a positive number of seconds, got '${t}'`);
@@ -397,7 +439,7 @@ var Fe = /* @__PURE__ */ new Set([
397
439
  perRequest: { timeoutMs: i },
398
440
  pollMs: Math.max(500, Math.min(2e3, Math.floor(i / 2)))
399
441
  };
400
- }, st = async (e, t, n) => {
442
+ }, ct = async (e, t, n) => {
401
443
  let r = await fetch(`${e.replace(/\/+$/, "")}/_voltro/admin/transfers?limit=50`, { headers: { authorization: `Bearer ${t}` } }).catch(() => null);
402
444
  if (r === null || r.status !== 200) return null;
403
445
  let i = ((await r.json().catch(() => null))?.runs ?? []).find((e) => e.id === n);
@@ -406,7 +448,7 @@ var Fe = /* @__PURE__ */ new Set([
406
448
  startedAt: new Date(String(i.startedAt)),
407
449
  finishedAt: i.finishedAt === null || i.finishedAt === void 0 ? null : new Date(String(i.finishedAt))
408
450
  };
409
- }, ct = async (e) => {
451
+ }, lt = async (e) => {
410
452
  let t = e.apiUrl.replace(/\/+$/, ""), n = [], r = 0, i;
411
453
  for (;;) {
412
454
  let a = `${t}/_voltro/admin/export/download?runId=${encodeURIComponent(e.runId)}&offset=${r}`, o = new AbortController(), s = e.perRequest === void 0 ? void 0 : setTimeout(() => {
@@ -428,12 +470,12 @@ var Fe = /* @__PURE__ */ new Set([
428
470
  e.log.info(` downloading — ${r}/${i ?? "?"} bytes`);
429
471
  }
430
472
  return new Uint8Array(Buffer.concat(n));
431
- }, X = "transfers", lt = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), ut = (e, t) => {
432
- let r = H(e, "--tenant"), i = H(e, "--tables"), a = H(e, "--exclude");
473
+ }, ut = "transfers", dt = (e) => (e ?? "").split(",").map((e) => e.trim()).filter(Boolean), ft = (e, t) => {
474
+ let r = U(e, "--tenant"), i = U(e, "--tables"), a = U(e, "--exclude");
433
475
  if (a !== void 0) {
434
476
  if (i !== void 0) throw new n("voltro data export: --exclude and --tables are two ways to say the same thing; use one.");
435
477
  if (r !== void 0) throw new n("voltro data export: --exclude does not apply to --tenant, whose table set is derived from the tenant closure.");
436
- let e = lt(a), o = new Set(t), s = e.filter((e) => !o.has(e));
478
+ let e = dt(a), o = new Set(t), s = e.filter((e) => !o.has(e));
437
479
  if (s.length > 0) throw new n(`voltro data export: --exclude names ${s.length} table(s) that do not exist: ${s.join(", ")}. An exclusion that excludes nothing would leave the run looking like it worked.`);
438
480
  return {
439
481
  kind: "all",
@@ -445,15 +487,15 @@ var Fe = /* @__PURE__ */ new Set([
445
487
  tenantId: r
446
488
  } : i ? {
447
489
  kind: "tables",
448
- tables: lt(i)
490
+ tables: dt(i)
449
491
  } : { kind: "all" };
450
- }, dt = async () => {
492
+ }, pt = async () => {
451
493
  let e = await g(process.cwd());
452
- return ye(le(e));
453
- }, ft = "voltro/tenant", pt = async () => {
454
- let e = (await g(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === ft)).map((e) => e.tableName);
494
+ return ve(se(e));
495
+ }, mt = "voltro/tenant", ht = async () => {
496
+ let e = (await g(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === mt)).map((e) => e.tableName);
455
497
  return e.length > 0 ? e : void 0;
456
- }, mt = (e) => {
498
+ }, gt = (e) => {
457
499
  for (let t of e.tables) if (t.samples.length !== 0) {
458
500
  console.log(`\n ${t.table}`);
459
501
  for (let e of t.samples) console.log(` ${e.column} [${e.action}] ${JSON.stringify(e.before)} → ${JSON.stringify(e.after)}`);
@@ -466,8 +508,8 @@ var Fe = /* @__PURE__ */ new Set([
466
508
  console.log("\n ✗ unclassified (would BLOCK a real export — add .sensitive()/.safe() or a policy override):");
467
509
  for (let t of e.unclassified) console.log(` ${t}`);
468
510
  } else console.log("\n ✓ every exported column is classified — safe to run.");
469
- }, ht = async (e) => {
470
- let { resolve: t, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = H(e.args, "--chunk-size"), o = a === void 0 ? Le : Math.round(Number(a) * 1024 * 1024);
511
+ }, _t = async (e) => {
512
+ let { resolve: t, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = U(e.args, "--chunk-size"), o = a === void 0 ? Le : Math.round(Number(a) * 1024 * 1024);
471
513
  if (!Number.isFinite(o) || o <= 0) throw new n(`--chunk-size expects a positive number of megabytes, got '${String(a)}'`);
472
514
  let s = e.stream[Symbol.asyncIterator](), c = [], l = 0, u = !1;
473
515
  for (; l <= o;) {
@@ -482,11 +524,11 @@ var Fe = /* @__PURE__ */ new Set([
482
524
  kind: "small",
483
525
  body: Buffer.concat(c)
484
526
  };
485
- let d = t(process.cwd(), e.bundleDir), f = ze(q(e.bundleDir) ? await i.readFile(d) : await i.readFile(r(d, "manifest.json"))), p = `up-${f.slice(0, 32)}`, m = await F(e.url, Buffer.alloc(0), {
527
+ let d = t(process.cwd(), e.bundleDir), f = ze(q(e.bundleDir) ? await i.readFile(d) : await i.readFile(r(d, "manifest.json"))), p = `up-${f.slice(0, 32)}`, m = await I(e.url, Buffer.alloc(0), {
486
528
  ...e.headers,
487
529
  "x-import-upload-id": p,
488
530
  "x-import-probe": "1"
489
- }, Q(e.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
531
+ }, Z(e.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
490
532
  h > 0 && e.log.info("api import: resuming a chunked upload the instance already holds", { bytes: h });
491
533
  let g = (async function* () {
492
534
  for (let e of c) yield e;
@@ -509,7 +551,7 @@ var Fe = /* @__PURE__ */ new Set([
509
551
  e.log.info("api import: upload done — the instance is importing now. This request stays open until it finishes, which on a full bundle is minutes, and nothing more is printed until then. It is not a hang; the instance logs the import itself.");
510
552
  },
511
553
  target: { send: async (t, n) => {
512
- let r = await F(e.url, t, n, Q(e.args)), i = {};
554
+ let r = await I(e.url, t, n, Z(e.args)), i = {};
513
555
  try {
514
556
  i = JSON.parse(r.body.toString("utf8") || "{}");
515
557
  } catch {}
@@ -531,83 +573,83 @@ var Fe = /* @__PURE__ */ new Set([
531
573
  body: Buffer.from(JSON.stringify(v.body))
532
574
  }
533
575
  };
534
- }, gt = (e) => `--mode replace with --no-atomic against a ${e ? "RUNNING instance" : "live database"}: the target is empty between the delete and the load, and anything the application writes in that window can collide with a row from the bundle — or survive the import as a row the bundle never carried. Drop --no-atomic to run the whole load in one transaction (concurrent writers then wait instead of racing), or stop the traffic for the duration.`, Z = (e, t) => e.includes("--no-atomic") ? !1 : e.includes("--atomic") || t === "replace", _t = (e, t, n) => {
576
+ }, vt = (e) => `--mode replace with --no-atomic against a ${e ? "RUNNING instance" : "live database"}: the target is empty between the delete and the load, and anything the application writes in that window can collide with a row from the bundle — or survive the import as a row the bundle never carried. Drop --no-atomic to run the whole load in one transaction (concurrent writers then wait instead of racing), or stop the traffic for the duration.`, X = (e, t) => e.includes("--no-atomic") ? !1 : e.includes("--atomic") || t === "replace", yt = (e, t, n) => {
535
577
  if (t !== "replace" || e.includes("--no-rollback")) return;
536
- let r = H(e, "--rollback-dir");
578
+ let r = U(e, "--rollback-dir");
537
579
  return r === void 0 ? o(n) : _(r);
538
- }, vt = (e, t, n) => {
539
- let r = _t(e, t, n);
580
+ }, bt = (e, t, n) => {
581
+ let r = yt(e, t, n);
540
582
  return {
541
583
  ...r === void 0 ? {} : { rollbackDir: r },
542
584
  recordInterruption: !0,
543
585
  via: "direct"
544
586
  };
545
- }, Q = (e) => {
546
- let t = H(e, "--timeout");
587
+ }, Z = (e) => {
588
+ let t = U(e, "--timeout");
547
589
  if (t === void 0) return {};
548
590
  let r = Number(t);
549
591
  if (!Number.isFinite(r) || r <= 0) throw new n(`--timeout expects a positive number of seconds, got '${t}'`);
550
592
  return { timeoutMs: Math.round(r * 1e3) };
551
- }, yt = async (e) => {
552
- let t = H(e, "--target") === "api", n = H(e, "--bundle-key"), r = U(e);
553
- if (!r && !(t && n)) return B.error("usage: voltro data export <outDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--profile <name>] [--tenant <id>] [--tables a,b] [--exclude a,b] [--bundle-key <storage-key>] [--assets] [--compression zstd|gzip|none] [--consistency live|snapshot] [--dry-run]"), 1;
593
+ }, xt = async (e) => {
594
+ let t = U(e, "--target") === "api", n = U(e, "--bundle-key"), r = W(e);
595
+ if (!r && !(t && n)) return V.error("usage: voltro data export <outDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--profile <name>] [--tenant <id>] [--tables a,b] [--exclude a,b] [--bundle-key <storage-key>] [--assets] [--compression zstd|gzip|none] [--consistency live|snapshot] [--dry-run]"), 1;
554
596
  if (t) {
555
- let t = H(e, "--api-url");
556
- if (!t) return B.error("--target api requires --api-url <url> (the running instance)"), 1;
557
- let i = H(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
558
- if (!i) return B.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
559
- let a = H(e, "--tables"), o = H(e, "--exclude");
560
- if (o !== void 0 && a !== void 0) return B.error("voltro data export: --exclude and --tables are two ways to say the same thing; use one."), 1;
597
+ let t = U(e, "--api-url");
598
+ if (!t) return V.error("--target api requires --api-url <url> (the running instance)"), 1;
599
+ let i = U(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
600
+ if (!i) return V.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
601
+ let a = U(e, "--tables"), o = U(e, "--exclude");
602
+ if (o !== void 0 && a !== void 0) return V.error("voltro data export: --exclude and --tables are two ways to say the same thing; use one."), 1;
561
603
  let s = {
562
- ...H(e, "--profile") ? { profile: H(e, "--profile") } : {},
563
- ...H(e, "--tenant") ? { tenant: H(e, "--tenant") } : {},
604
+ ...U(e, "--profile") ? { profile: U(e, "--profile") } : {},
605
+ ...U(e, "--tenant") ? { tenant: U(e, "--tenant") } : {},
564
606
  ...a ? { tables: a.split(",").map((e) => e.trim()).filter(Boolean) } : {},
565
607
  ...o ? { exclude: o.split(",").map((e) => e.trim()).filter(Boolean) } : {},
566
- ...H(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
567
- ...H(e, "--compression") ? { compression: H(e, "--compression") } : {},
608
+ ...U(e, "--consistency") === "snapshot" ? { consistency: "snapshot" } : {},
609
+ ...U(e, "--compression") ? { compression: U(e, "--compression") } : {},
568
610
  ...e.includes("--assets") ? { assets: !0 } : {},
569
611
  ...n ? { bundleKey: n } : {}
570
- }, c = ot(e), l;
612
+ }, c = st(e), l;
571
613
  try {
572
- let n = await F(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(s), {
614
+ let n = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(s), {
573
615
  "content-type": "application/json",
574
616
  authorization: `Bearer ${i}`
575
- }, c.perRequest ?? Q(e));
576
- if (n.status < 200 || n.status >= 300) return B.error(`api export failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
617
+ }, c.perRequest ?? Z(e));
618
+ if (n.status < 200 || n.status >= 300) return V.error(`api export failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
577
619
  l = JSON.parse(n.body.toString("utf8"));
578
620
  } catch (e) {
579
- if (e instanceof P) return B.error(`api export: the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. That is a client-side deadline. Nothing was written locally; re-run, or raise --timeout.`), 1;
580
- if (e instanceof N) return B.error(`api export: ${I(e)}`), 1;
621
+ if (e instanceof F) return V.error(`api export: the instance did not answer within ${Math.round(e.elapsedMs / 1e3)}s. That is a client-side deadline. Nothing was written locally; re-run, or raise --timeout.`), 1;
622
+ if (e instanceof P) return V.error(`api export: ${L(e)}`), 1;
581
623
  throw e;
582
624
  }
583
- if (e.includes("--detach")) return B.info(`api export: started as ${l.runId}. NOT waiting for it.\n The outcome is not known yet — read it with: voltro data ${X} --target api --api-url ${t}`), 0;
584
- if (l.recorded === !1) return B.warn(`api export: started as ${l.runId}, but this instance could not record the run — so its outcome\n cannot be read back. Run \`voltro db apply\` against it so the next one is watchable.`), 0;
625
+ if (e.includes("--detach")) return V.info(`api export: started as ${l.runId}. NOT waiting for it.\n The outcome is not known yet — read it with: voltro data ${ut} --target api --api-url ${t}`), 0;
626
+ if (l.recorded === !1) return V.warn(`api export: started as ${l.runId}, but this instance could not record the run — so its outcome\n cannot be read back. Run \`voltro db apply\` against it so the next one is watchable.`), 0;
585
627
  let p = await d({
586
- read: () => st(t, i, l.runId),
628
+ read: () => ct(t, i, l.runId),
587
629
  onTick: (e) => {
588
- B.info(f(e));
630
+ V.info(f(e));
589
631
  },
590
632
  intervalMs: c.pollMs,
591
633
  ...c.total === void 0 ? {} : { timeoutMs: c.total }
592
634
  }), m = u(p);
593
- if (m !== 0) return B.error(m === 1 ? `api export FAILED (${l.runId}): ${String(p?.failure ?? "no reason recorded")}` : `api export: no outcome visible for ${l.runId}. The run is not cancelled; it is simply not readable from here.`), m;
594
- if (n) return B.info("api export complete (pushed to storage)", {
635
+ if (m !== 0) return V.error(m === 1 ? `api export FAILED (${l.runId}): ${String(p?.failure ?? "no reason recorded")}` : `api export: no outcome visible for ${l.runId}. The run is not cancelled; it is simply not readable from here.`), m;
636
+ if (n) return V.info("api export complete (pushed to storage)", {
595
637
  via: t,
596
638
  runId: l.runId,
597
639
  bundleKey: n,
598
640
  tables: p?.tablesWritten ?? 0,
599
641
  rows: p?.rowsWritten ?? 0
600
642
  }), 0;
601
- let { resolve: h } = await import("node:path"), g = await ct({
643
+ let { resolve: h } = await import("node:path"), g = await lt({
602
644
  apiUrl: t,
603
645
  secret: i,
604
646
  runId: l.runId,
605
- log: B,
647
+ log: V,
606
648
  ...c.perRequest ? { perRequest: c.perRequest } : {}
607
649
  });
608
650
  return g === null ? 1 : (await Me((async function* () {
609
651
  yield g;
610
- })(), h(process.cwd(), r)), B.info("api export complete (downloaded from the running instance)", {
652
+ })(), h(process.cwd(), r)), V.info("api export complete (downloaded from the running instance)", {
611
653
  outDir: r,
612
654
  runId: l.runId,
613
655
  tables: p?.tablesWritten ?? 0,
@@ -615,11 +657,13 @@ var Fe = /* @__PURE__ */ new Set([
615
657
  bytes: g.byteLength
616
658
  }), 0);
617
659
  }
618
- if (!r) return B.error("a <outDir> is required for a direct export"), 1;
619
- let i = H(e, "--profile"), a = i ? await p(i, { allowPath: !0 }) : {}, { store: o, snapshot: s, dialect: c } = await G(), l = e.includes("--assets") || a.assets === !0, m = l ? E({}) : void 0, h = H(e, "--compression") ?? a.compression, g = H(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", _ = a.masking, v = a.subset, y = a.scope ?? ut(e, s.tables.map((e) => e.name)), b = _ ? await dt() : void 0, x = y.kind === "tenant" ? await pt() : void 0, S = x ? { tenantTables: x } : {};
660
+ if (!r) return V.error("a <outDir> is required for a direct export"), 1;
661
+ let i = U(e, "--profile"), a = i ? await p(i, { allowPath: !0 }) : {}, { store: o, snapshot: s, dialect: c } = await K(), l = e.includes("--assets") || a.assets === !0, m = l ? O() : void 0;
662
+ if (l && !m) return V.error("--assets: this app has no storage provider configured, so there are no blobs to export.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the flag."), 1;
663
+ let h = U(e, "--compression") ?? a.compression, g = U(e, "--consistency") === "snapshot" ? "snapshot" : a.consistency ?? "live", _ = a.masking, v = a.subset, y = a.scope ?? ft(e, s.tables.map((e) => e.name)), b = _ ? await pt() : void 0, x = y.kind === "tenant" ? await ht() : void 0, S = x ? { tenantTables: x } : {};
620
664
  if (e.includes("--dry-run")) {
621
665
  let e = y.kind === "tables" && y.tables ? [...y.tables] : s.tables.map((e) => e.name);
622
- if (B.info("export DRY RUN — nothing was written", {
666
+ if (V.info("export DRY RUN — nothing was written", {
623
667
  outDir: r,
624
668
  tables: e.length,
625
669
  scope: y.kind,
@@ -627,8 +671,8 @@ var Fe = /* @__PURE__ */ new Set([
627
671
  compression: h ?? "zstd",
628
672
  assets: l,
629
673
  masking: _ ? "profile policy applies" : "none"
630
- }), B.info(`would export: ${e.join(", ")}`), !_) return 0;
631
- let t = await T.runPromise(Se({
674
+ }), V.info(`would export: ${e.join(", ")}`), !_) return 0;
675
+ let t = await D.runPromise(xe({
632
676
  store: o,
633
677
  snapshot: s,
634
678
  masking: _,
@@ -638,12 +682,12 @@ var Fe = /* @__PURE__ */ new Set([
638
682
  ...S
639
683
  }
640
684
  }));
641
- return mt(t), +(t.unclassified.length > 0);
685
+ return gt(t), +(t.unclassified.length > 0);
642
686
  }
643
687
  if (q(r)) {
644
688
  let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: i, join: a } = await import("node:path"), u = await n.mkdtemp(a(t.tmpdir(), "voltro-export-"));
645
689
  try {
646
- let t = await T.runPromise(Te({
690
+ let t = await D.runPromise(we({
647
691
  store: o,
648
692
  snapshot: s,
649
693
  dialect: c,
@@ -656,17 +700,17 @@ var Fe = /* @__PURE__ */ new Set([
656
700
  ...h ? { compression: h } : {},
657
701
  ..._ ? { masking: _ } : {},
658
702
  ...b ? { classification: b } : {},
659
- onProgress: V
660
- })), n = xe({
703
+ onProgress: H
704
+ })), n = be({
661
705
  dir: u,
662
- ...m ? { blobs: Ae(m, () => K(o)) } : {}
663
- }), a = rt(e);
706
+ ...m ? { blobs: Ae(m, () => rt(o)) } : {}
707
+ }), a = it(e);
664
708
  if (a) {
665
709
  let t = J(e);
666
- if (!t) return B.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
667
- n = be(n, t);
710
+ if (!t) return V.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
711
+ n = ye(n, t);
668
712
  }
669
- return await it(n, i(process.cwd(), r)), B.info("export complete (single file)", {
713
+ return await at(n, i(process.cwd(), r)), V.info("export complete (single file)", {
670
714
  file: r,
671
715
  encrypted: a,
672
716
  assets: l,
@@ -680,7 +724,7 @@ var Fe = /* @__PURE__ */ new Set([
680
724
  });
681
725
  }
682
726
  }
683
- let C = await T.runPromise(Te({
727
+ let C = await D.runPromise(we({
684
728
  store: o,
685
729
  snapshot: s,
686
730
  dialect: c,
@@ -693,10 +737,10 @@ var Fe = /* @__PURE__ */ new Set([
693
737
  ...h ? { compression: h } : {},
694
738
  ..._ ? { masking: _ } : {},
695
739
  ...b ? { classification: b } : {},
696
- ...m ? { assets: Oe(m, () => K(o)) } : {},
697
- onProgress: V
740
+ ...m ? { assets: Oe(m, () => rt(o)) } : {},
741
+ onProgress: H
698
742
  }));
699
- return B.info("export complete", {
743
+ return V.info("export complete", {
700
744
  outDir: r,
701
745
  tables: C.tables.length,
702
746
  rows: C.tables.reduce((e, t) => e + t.rowCount, 0),
@@ -704,13 +748,13 @@ var Fe = /* @__PURE__ */ new Set([
704
748
  compression: C.compression,
705
749
  ...C.masking ? { maskedColumns: C.masking.transformed.length } : {}
706
750
  }), 0;
707
- }, bt = () => "dry run checked: schema fit, cross-dialect portability, mode legality, the table selection. It did NOT read per-table checksums (those stream during the load) and cannot see conflicts that depend on the target's current rows.", xt = (e) => {
708
- let t = H(e, "--tables");
751
+ }, St = () => "dry run checked: schema fit, cross-dialect portability, mode legality, the table selection. It did NOT read per-table checksums (those stream during the load) and cannot see conflicts that depend on the target's current rows.", Ct = (e) => {
752
+ let t = U(e, "--tables");
709
753
  return {
710
754
  ...t ? { tables: t.split(",").map((e) => e.trim()).filter(Boolean) } : {},
711
755
  ...e.includes("--dry-run") ? { dryRun: !0 } : {}
712
756
  };
713
- }, St = (e) => ({
757
+ }, wt = (e) => ({
714
758
  tables: e.tablesWritten,
715
759
  rows: e.rowsWritten,
716
760
  carried: e.manifest.tables.reduce((e, t) => e + t.rowCount, 0),
@@ -718,28 +762,28 @@ var Fe = /* @__PURE__ */ new Set([
718
762
  skippedTables: e.tablesSkipped,
719
763
  skippedRows: e.rowsSkipped
720
764
  } : {}
721
- }), Ct = (e, t) => {
765
+ }), Tt = (e, t) => {
722
766
  if (e.includes("--dry-run")) {
723
- B.info("import DRY RUN — nothing was written", t), B.info(bt());
767
+ V.info("import DRY RUN — nothing was written", t), V.info(St());
724
768
  return;
725
769
  }
726
770
  if (t.rows === 0 && typeof t.skippedRows == "number" && t.skippedRows !== 0) {
727
- B.info("import complete — 0 rows written, every table was already applied by an earlier run of this bundle directory", t);
771
+ V.info("import complete — 0 rows written, every table was already applied by an earlier run of this bundle directory", t);
728
772
  return;
729
773
  }
730
- B.info("import complete", t);
731
- }, wt = Symbol("refused"), Tt = (e) => {
732
- let t = he(e) ?? e;
774
+ V.info("import complete", t);
775
+ }, Et = Symbol("refused"), Q = (e) => {
776
+ let t = me(e) ?? e;
733
777
  if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return !1;
734
778
  let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
735
- B.error(`import refused ${i} row(s), of which ${a} are the actual failures — the rest could not land because a row they reference did not.`);
779
+ V.error(`import refused ${i} row(s), of which ${a} are the actual failures — the rest could not land because a row they reference did not.`);
736
780
  let o = Array.isArray(t.byTable) ? t.byTable : [];
737
- o.length > 1 && B.error(` by table: ${o.map((e) => `${e.table} ${e.refused}`).join(", ")}`);
738
- for (let e of r) B.error(` ${e.table} ${e.id} ${e.reason}`);
739
- n.length < i && B.error(` (the list above is capped at ${n.length} of ${i} — the counts by table are complete)`);
781
+ o.length > 1 && V.error(` by table: ${o.map((e) => `${e.table} ${e.refused}`).join(", ")}`);
782
+ for (let e of r) V.error(` ${e.table} ${e.id} ${e.reason}`);
783
+ n.length < i && V.error(` (the list above is capped at ${n.length} of ${i} — the counts by table are complete)`);
740
784
  let s = i - a;
741
- return s > 0 && B.error(` … and ${s} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`), !0;
742
- }, Et = (e) => {
785
+ return s > 0 && V.error(` … and ${s} row(s) behind them. Fix the ${a} above and re-run; they resolve with their parents.`), !0;
786
+ }, Dt = (e) => {
743
787
  let t = typeof e == "string" ? e : e.toString("utf8"), n = t;
744
788
  try {
745
789
  let e = JSON.parse(t), r = e.error ?? e.message;
@@ -779,22 +823,22 @@ var Fe = /* @__PURE__ */ new Set([
779
823
  return;
780
824
  }
781
825
  }
782
- }, Dt = async (e) => {
783
- let t = H(e, "--target") === "api", n = H(e, "--bundle-key"), r = U(e);
784
- if (!r && !(t && n)) return B.error("usage: voltro data import <bundleDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--bundle-key <storage-key>] [--tables a,b] [--mode upsert|append|replace] [--on-conflict skip|fail] [--atomic] [--assets] [--no-verify] [--force] [--allow-live] [--dry-run] (--force: import despite schema drift AND cross-dialect warnings; --dry-run: report what would move, write nothing)"), 1;
785
- let i = H(e, "--mode") ?? "upsert", o = H(e, "--on-conflict") ?? "skip";
826
+ }, Ot = async (e) => {
827
+ let t = U(e, "--target") === "api", n = U(e, "--bundle-key"), r = W(e);
828
+ if (!r && !(t && n)) return V.error("usage: voltro data import <bundleDir> [--target direct|api] [--api-url <url>] [--token <secret>] [--timeout <seconds>] [--bundle-key <storage-key>] [--tables a,b] [--mode upsert|append|replace] [--on-conflict skip|fail] [--atomic] [--assets] [--no-verify] [--force] [--allow-live] [--dry-run] (--force: import despite schema drift AND cross-dialect warnings; --dry-run: report what would move, write nothing)"), 1;
829
+ let i = U(e, "--mode") ?? "upsert", o = U(e, "--on-conflict") ?? "skip";
786
830
  if (t) {
787
- let t = H(e, "--api-url");
788
- if (!t) return B.error("--target api requires --api-url <url> (the running instance)"), 1;
789
- let a = H(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
790
- if (!a) return B.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
791
- let s = H(e, "--tables"), c = H(e, "--rollback-key");
792
- i === "replace" && c === void 0 && B.warn("api replace: no rollback capture — this run empties the target first and nothing was asked to be kept.\n Pass --rollback-key <storage-key> and the INSTANCE stores the target's current rows in its own\n object storage before deleting them, which is the one place that survives the pod going away.");
831
+ let t = U(e, "--api-url");
832
+ if (!t) return V.error("--target api requires --api-url <url> (the running instance)"), 1;
833
+ let a = U(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
834
+ if (!a) return V.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET) matching the instance secret"), 1;
835
+ let s = U(e, "--tables"), c = U(e, "--rollback-key");
836
+ i === "replace" && c === void 0 && V.warn("api replace: no rollback capture — this run empties the target first and nothing was asked to be kept.\n Pass --rollback-key <storage-key> and the INSTANCE stores the target's current rows in its own\n object storage before deleting them, which is the one place that survives the pod going away.");
793
837
  let l = {
794
838
  authorization: `Bearer ${a}`,
795
839
  "x-import-mode": i,
796
840
  "x-import-on-conflict": o,
797
- ...Z(e, i) ? { "x-import-atomic": "1" } : {},
841
+ ...X(e, i) ? { "x-import-atomic": "1" } : {},
798
842
  ...e.includes("--no-atomic") ? { "x-import-atomic": "0" } : {},
799
843
  ...c === void 0 ? {} : { "x-import-rollback-key": c },
800
844
  ...e.includes("--force") ? { "x-import-force": "1" } : {},
@@ -804,64 +848,64 @@ var Fe = /* @__PURE__ */ new Set([
804
848
  if (n) l["content-type"] = "application/json", m = JSON.stringify({ bundleKey: n });
805
849
  else if (q(r)) {
806
850
  let t = Y(p(process.cwd(), r)), n = J(e);
807
- n && (t = A(t, n)), l["content-type"] = "application/octet-stream", m = t;
808
- } else await (await import("node:fs/promises")).access(p(process.cwd(), r, "assets", "index.ndjson")).then(() => !0, () => !1) && B.warn("this directory bundle has assets; --target api uploads DATA ONLY. Pack a single-file `.vbundle` (voltro data export <name>.vbundle --assets) to transfer assets over the API."), l["content-type"] = "application/octet-stream", m = xe({ dir: p(process.cwd(), r) });
851
+ n && (t = M(t, n)), l["content-type"] = "application/octet-stream", m = t;
852
+ } else await (await import("node:fs/promises")).access(p(process.cwd(), r, "assets", "index.ndjson")).then(() => !0, () => !1) && V.warn("this directory bundle has assets; --target api uploads DATA ONLY. Pack a single-file `.vbundle` (voltro data export <name>.vbundle --assets) to transfer assets over the API."), l["content-type"] = "application/octet-stream", m = be({ dir: p(process.cwd(), r) });
809
853
  if (i === "replace") {
810
- let i = await at(r, n);
854
+ let i = await ot(r, n);
811
855
  {
812
- let r = i === null ? { bundleKey: n } : { tables: i }, a = await F(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, JSON.stringify(r), {
856
+ let r = i === null ? { bundleKey: n } : { tables: i }, a = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, JSON.stringify(r), {
813
857
  ...l,
814
858
  "content-type": "application/json",
815
859
  "x-import-preflight": "1"
816
- }, Q(e)).catch(() => null), o = a !== null && a.status === 200 ? JSON.parse(a.body.toString("utf8")) : null;
817
- o === null ? B.warn("api replace: the instance did not answer the preflight (an older build does not have it). The run still works; the decision will only be visible in the pod log.") : o.staged === !0 ? B.info(`api replace: the instance WILL stage — ${o.stagedTables ?? 0} table(s) load into copies and the\n target keeps its rows until one short swap at the end. Interrupting the load leaves the target intact.`) : B.warn(`api replace: the instance will NOT stage — it empties the target first and refills it, so an
860
+ }, Z(e)).catch(() => null), o = a !== null && a.status === 200 ? JSON.parse(a.body.toString("utf8")) : null;
861
+ o === null ? V.warn("api replace: the instance did not answer the preflight (an older build does not have it). The run still works; the decision will only be visible in the pod log.") : o.staged === !0 ? V.info(`api replace: the instance WILL stage — ${o.stagedTables ?? 0} table(s) load into copies and the\n target keeps its rows until one short swap at the end. Interrupting the load leaves the target intact.`) : V.warn(`api replace: the instance will NOT stage — it empties the target first and refills it, so an
818
862
  interrupted run leaves the target empty.\n Reason: ${o.reason ?? "not stated"}`);
819
863
  }
820
864
  }
821
- i === "replace" && !Z(e, i) && B.warn(gt(!0));
822
- let h = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, g = ot(e), _;
865
+ i === "replace" && !X(e, i) && V.warn(vt(!0));
866
+ let h = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, g = st(e), _;
823
867
  try {
824
- let t = typeof m == "string" ? null : await ht({
868
+ let t = typeof m == "string" ? null : await _t({
825
869
  stream: m,
826
870
  url: h,
827
871
  headers: l,
828
872
  bundleDir: r,
829
873
  args: e,
830
- log: B
831
- }), n = t === null ? await F(h, m, l, g.perRequest ?? Q(e)) : t.kind === "sent" ? t.res : await F(h, t.body, l, g.perRequest ?? Q(e));
832
- if (n.status < 200 || n.status >= 300) return Tt(Et(n.body)) || B.error(`api import failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
874
+ log: V
875
+ }), n = t === null ? await I(h, m, l, g.perRequest ?? Z(e)) : t.kind === "sent" ? t.res : await I(h, t.body, l, g.perRequest ?? Z(e));
876
+ if (n.status < 200 || n.status >= 300) return Q(Dt(n.body)) || V.error(`api import failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
833
877
  _ = JSON.parse(n.body.toString("utf8"));
834
878
  } catch (e) {
835
- if (e instanceof P) return B.error(`api import: ${Ie(e, i)}`), 1;
836
- if (e instanceof N) return B.error(`api import: ${I(e)}`), 1;
879
+ if (e instanceof F) return V.error(`api import: ${Ie(e, i)}`), 1;
880
+ if (e instanceof P) return V.error(`api import: ${L(e)}`), 1;
837
881
  throw e;
838
882
  }
839
- let v = await F(`${t.replace(/\/+$/, "")}/_voltro/admin/import/start`, JSON.stringify(_.bundleKey === void 0 ? { uploadId: _.uploadId } : { bundleKey: _.bundleKey }), {
883
+ let v = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/import/start`, JSON.stringify(_.bundleKey === void 0 ? { uploadId: _.uploadId } : { bundleKey: _.bundleKey }), {
840
884
  ...l,
841
885
  "content-type": "application/json"
842
- }, g.perRequest ?? Q(e)).catch((e) => {
843
- if (e instanceof N) return B.error(`api import: ${I(e)}`), null;
886
+ }, g.perRequest ?? Z(e)).catch((e) => {
887
+ if (e instanceof P) return V.error(`api import: ${L(e)}`), null;
844
888
  throw e;
845
889
  });
846
890
  if (v === null) return 1;
847
- if (v.status < 200 || v.status >= 300) return Tt(Et(v.body)) || B.error(`api import refused: HTTP ${v.status} — ${v.body.toString("utf8")}`), 1;
891
+ if (v.status < 200 || v.status >= 300) return Q(Dt(v.body)) || V.error(`api import refused: HTTP ${v.status} — ${v.body.toString("utf8")}`), 1;
848
892
  let y = JSON.parse(v.body.toString("utf8"));
849
- if (y.alreadyStarted === !0 && B.info(`api import: this bundle is already being imported as ${y.runId} — watching that run instead of starting a second one`), e.includes("--detach")) return B.info(`api import: started as ${y.runId}. NOT waiting for it.\n The outcome is not known yet — read it with: voltro data ${X} --target api --api-url ${t}`), 0;
850
- if (y.recorded === !1) return B.warn(`api import: started as ${y.runId}, but this instance could not record the run — so its outcome\n cannot be read back. Run \`voltro db apply\` against it so the next import is watchable.`), 0;
893
+ if (y.alreadyStarted === !0 && V.info(`api import: this bundle is already being imported as ${y.runId} — watching that run instead of starting a second one`), e.includes("--detach")) return V.info(`api import: started as ${y.runId}. NOT waiting for it.\n The outcome is not known yet — read it with: voltro data ${ut} --target api --api-url ${t}`), 0;
894
+ if (y.recorded === !1) return V.warn(`api import: started as ${y.runId}, but this instance could not record the run — so its outcome\n cannot be read back. Run \`voltro db apply\` against it so the next import is watchable.`), 0;
851
895
  let b = await d({
852
- read: () => st(t, a, y.runId),
896
+ read: () => ct(t, a, y.runId),
853
897
  onTick: (e) => {
854
- B.info(f(e));
898
+ V.info(f(e));
855
899
  },
856
900
  intervalMs: g.pollMs,
857
901
  ...g.total === void 0 ? {} : { timeoutMs: g.total }
858
902
  }), x = u(b);
859
- return x === 2 ? (B.warn(b === null ? `api import: started as ${y.runId}, but its record could not be READ back from this instance.\n That is a channel problem, not a slow import — an older instance without the history route, a
903
+ return x === 2 ? (V.warn(b === null ? `api import: started as ${y.runId}, but its record could not be READ back from this instance.\n That is a channel problem, not a slow import — an older instance without the history route, a
860
904
  proxy dropping the GET, or a target with no history table. The run itself is unaffected and
861
- is NOT cancelled; its outcome is simply not visible from here.` : `api import: still running after the time allowed. The run is NOT cancelled — it is decoupled from\n this client by design. Watch it with: voltro data ${X} --target api --api-url ${t}`), 2) : x === 1 ? (B.error(`api import FAILED (${y.runId}): ${String(b?.failure ?? "no reason recorded")}`), 1) : e.includes("--dry-run") ? (B.info("api import DRY RUN — nothing was written", {
905
+ is NOT cancelled; its outcome is simply not visible from here.` : `api import: still running after the time allowed. The run is NOT cancelled — it is decoupled from\n this client by design. Watch it with: voltro data ${ut} --target api --api-url ${t}`), 2) : x === 1 ? (V.error(`api import FAILED (${y.runId}): ${String(b?.failure ?? "no reason recorded")}`), 1) : e.includes("--dry-run") ? (V.info("api import DRY RUN — nothing was written", {
862
906
  via: t,
863
907
  runId: y.runId
864
- }), B.info(bt()), 0) : (B.info("api import complete (imported by the running instance)", {
908
+ }), V.info(St()), 0) : (V.info("api import complete (imported by the running instance)", {
865
909
  via: t,
866
910
  runId: y.runId,
867
911
  tables: b?.tablesWritten ?? 0,
@@ -869,74 +913,76 @@ var Fe = /* @__PURE__ */ new Set([
869
913
  ...b?.staged !== null && b?.staged !== void 0 ? { staged: b.staged } : {}
870
914
  }), 0);
871
915
  }
872
- if (!r) return B.error("a <bundleDir> is required for a direct import"), 1;
916
+ if (!r) return V.error("a <bundleDir> is required for a direct import"), 1;
873
917
  if (!await Qe(e)) return 1;
874
- i === "replace" && e.includes("--allow-live") && !Z(e, i) && B.warn(gt(!1));
875
- let { store: s, snapshot: c } = await G();
876
- c.tables.some((e) => e.name === y) || B.warn(`this target has no \`${y}\` table, so this import will not be recorded.\n The import itself is unaffected. \`voltro db apply\` creates it.`);
877
- let l = e.includes("--force"), p = (e) => B.warn(e), m = e.includes("--assets") ? E({}) : void 0, h = W() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(a()) : void 0;
918
+ i === "replace" && e.includes("--allow-live") && !X(e, i) && V.warn(vt(!1));
919
+ let { store: s, snapshot: c } = await K();
920
+ c.tables.some((e) => e.name === y) || V.warn(`this target has no \`${y}\` table, so this import will not be recorded.\n The import itself is unaffected. \`voltro db apply\` creates it.`);
921
+ let l = e.includes("--force"), p = (e) => V.warn(e), m = e.includes("--assets"), h = m ? O() : void 0;
922
+ if (m && !h) return V.error("--assets: this app has no storage provider configured, so there is nowhere to put the blobs.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the flag."), 1;
923
+ let g = G() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(a()) : void 0;
878
924
  if (q(r)) {
879
925
  let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: a, join: u } = await import("node:path"), d = await n.mkdtemp(u(t.tmpdir(), "voltro-import-"));
880
926
  try {
881
927
  let t = Y(a(process.cwd(), r)), n = J(e);
882
- return n && (t = A(t, n)), await je(t, {
928
+ return n && (t = M(t, n)), await je(t, {
883
929
  dir: d,
884
- blobs: ke(m ?? E({}))
885
- }), Ct(e, {
930
+ blobs: ke(h ?? de())
931
+ }), Tt(e, {
886
932
  file: r,
887
- ...St(await T.runPromise(Ee({
933
+ ...wt(await D.runPromise(Te({
888
934
  store: s,
889
935
  bundleDir: d,
890
936
  mode: i,
891
937
  onConflict: o,
892
- atomic: Z(e, i),
893
- ...vt(e, i, r),
938
+ atomic: X(e, i),
939
+ ...bt(e, i, r),
894
940
  verify: !e.includes("--no-verify"),
895
- targetDialect: W(),
941
+ targetDialect: G(),
896
942
  allowIncompatible: l,
897
943
  targetSnapshot: c,
898
944
  force: l,
899
945
  onWarn: p,
900
- onProgress: V,
946
+ onProgress: H,
901
947
  assetsAlreadyRestored: !0,
902
- ...xt(e),
903
- ...h ? { copyLoader: h } : {}
948
+ ...Ct(e),
949
+ ...g ? { copyLoader: g } : {}
904
950
  })))
905
951
  }), 0;
906
952
  } finally {
907
- await h?.close(), await n.rm(d, {
953
+ await g?.close(), await n.rm(d, {
908
954
  recursive: !0,
909
955
  force: !0
910
956
  });
911
957
  }
912
958
  }
913
- let g = await T.runPromise(Ee({
914
- ...vt(e, i, r),
959
+ let _ = await D.runPromise(Te({
960
+ ...bt(e, i, r),
915
961
  store: s,
916
962
  bundleDir: r,
917
963
  mode: i,
918
964
  onConflict: o,
919
- atomic: Z(e, i),
965
+ atomic: X(e, i),
920
966
  verify: !e.includes("--no-verify"),
921
- targetDialect: W(),
967
+ targetDialect: G(),
922
968
  allowIncompatible: l,
923
969
  targetSnapshot: c,
924
970
  force: l,
925
971
  onWarn: p,
926
- onProgress: V,
927
- ...xt(e),
928
- ...m ? { assets: De(m) } : {},
929
- ...h ? { copyLoader: h } : {}
972
+ onProgress: H,
973
+ ...Ct(e),
974
+ ...h ? { assets: De(h) } : {},
975
+ ...g ? { copyLoader: g } : {}
930
976
  })).catch((e) => {
931
- if (Tt(e)) return wt;
977
+ if (Q(e)) return Et;
932
978
  throw e;
933
- }).finally(() => h?.close());
934
- return typeof g == "symbol" ? 1 : (Ct(e, {
979
+ }).finally(() => g?.close());
980
+ return typeof _ == "symbol" ? 1 : (Tt(e, {
935
981
  bundleDir: r,
936
- ...St(g),
937
- assets: g.manifest.assets.count
982
+ ...wt(_),
983
+ assets: _.manifest.assets.count
938
984
  }), 0);
939
- }, Ot = () => {
985
+ }, kt = () => {
940
986
  let e = a();
941
987
  return {
942
988
  ...e.url ? { url: e.url } : {},
@@ -946,33 +992,42 @@ var Fe = /* @__PURE__ */ new Set([
946
992
  ...e.password ? { password: e.password } : {},
947
993
  ...e.database ? { database: e.database } : {}
948
994
  };
949
- }, kt = async (e) => {
950
- let t = U(e);
951
- if (!t) return B.error("usage: voltro data backup <outDir> [--assets] (native pg_dump/mariadb-dump/… + consistent artifact)"), 1;
952
- let n = W(), r = e.includes("--assets"), { mkdir: i, writeFile: a } = await import("node:fs/promises"), { join: o } = await import("node:path"), s = r ? E({}) : void 0;
953
- if (r && !s) return B.error("--assets: this app has no storage provider configured, so there are no blobs to capture.\n Configure one in app.config.ts, or drop the flag to take the rows-only dump."), 1;
995
+ }, At = async (e) => {
996
+ let t = W(e);
997
+ if (!t) return V.error("usage: voltro data backup <outDir> [--assets] (native pg_dump/mariadb-dump/… + consistent artifact)"), 1;
998
+ let n = G(), r = e.includes("--assets"), { mkdir: i, writeFile: a } = await import("node:fs/promises"), { join: o } = await import("node:path"), s = await Mt(), c = r ? O() : void 0;
999
+ if (r && !c) return V.error("--assets: this app has no storage provider configured, so there are no blobs to capture.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the\n flag to take the rows-only dump."), 1;
954
1000
  await i(t, { recursive: !0 });
955
- let c = o(t, k(n)), l = await At(), u = l ? await We(l.store, {
1001
+ let l = o(t, j(n)), u = Pt(s), d = Ft(s), f = u ? await We(u, {
956
1002
  direction: "backup",
957
1003
  dialect: n,
958
- artifact: c,
1004
+ artifact: l,
959
1005
  assets: r
960
- }) : void 0, d = await Ke(M(_e(n, Ot(), c)));
961
- if (d !== void 0) return l && u && await R(l.store, u, { failure: d }), B.error(`native backup failed: ${d}`), 1;
962
- let f = 0, p = 0;
963
- if (s && l) {
964
- let e = o(t, D);
1006
+ }) : void 0, p = await Ke(Ee(ge(n, kt(), l)));
1007
+ if (p !== void 0) return u && f && await z(u, f, { failure: p }), V.error(`native backup failed: ${p}`), 1;
1008
+ let m = await Xe(n), h = await Ye(), g = (/* @__PURE__ */ new Date()).toISOString(), _ = async (e) => {
1009
+ let r = Ve({
1010
+ dialect: n,
1011
+ schemaFingerprint: m,
1012
+ voltroVersion: h,
1013
+ createdAt: g,
1014
+ assets: e
1015
+ });
1016
+ return await a(o(t, R), `${JSON.stringify(r, null, 2)}\n`, "utf8"), r;
1017
+ }, v = 0, y = 0, b = [], x = 0, S = 0, C = 0;
1018
+ if (c && u) {
1019
+ let e = o(t, k);
965
1020
  await i(e, { recursive: !0 });
966
- let n = o(t, O), r = l.store, a = await T.runPromiseExit(ve({
967
- source: Oe(s, () => K(r)),
1021
+ let n = o(t, A), r = u, a = await D.runPromiseExit(_e({
1022
+ source: Oe(c, () => rt(r)),
968
1023
  assetsDir: e,
969
1024
  progress: {
970
1025
  done: () => !1,
971
- mark: (e, t) => T.tryPromise({
1026
+ mark: (e, t) => D.tryPromise({
972
1027
  try: async () => {
973
1028
  await (await import("node:fs/promises")).appendFile(n, `${JSON.stringify(t)}\n`);
974
1029
  },
975
- catch: (e) => new me({
1030
+ catch: (e) => new pe({
976
1031
  reason: `asset index append failed: ${String(e)}`,
977
1032
  path: n
978
1033
  })
@@ -980,121 +1035,139 @@ var Fe = /* @__PURE__ */ new Set([
980
1035
  }
981
1036
  }));
982
1037
  if (a._tag === "Failure") {
983
- let e = w.pretty(a.cause);
984
- return u && await R(l.store, u, { failure: `assets: ${e}` }), B.error(`the rows were dumped to ${c}, but the asset capture failed: ${e}\n This directory holds a ROWS-ONLY backup. Re-run with --assets once the storage
1038
+ let e = E.pretty(a.cause);
1039
+ return f && await z(u, f, { failure: `assets: ${e}` }), await _(null).catch(() => void 0), V.error(`the rows were dumped to ${l}, but the asset capture failed: ${e}\n This directory holds a ROWS-ONLY backup. Re-run with --assets once the storage
985
1040
  backend is reachable, or restore without --assets and accept the dangling refs.`), 1;
986
1041
  }
987
- f = a.value.count, p = a.value.totalBytes;
1042
+ v = a.value.count, y = a.value.totalBytes, b = a.value.missing, x = a.value.objects, S = a.value.objectBytes, C = a.value.references, b.length > 0 && V.warn(`${b.length} of ${b.length + v} blob reference(s) point at objects the storage provider does not have; they are NOT in this backup and no restore can bring them back.
1043
+ e.g. ${b.slice(0, 3).map((e) => `'${e}'`).join(", ")}` + (b.length > 3 ? `, and ${b.length - 3} more` : ""));
988
1044
  }
989
- let m = Ve({
990
- dialect: n,
991
- schemaFingerprint: await Xe(n),
992
- voltroVersion: await Ye(),
993
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
994
- assets: r ? {
995
- count: f,
996
- totalBytes: p
997
- } : null
998
- });
999
- return await a(o(t, L), `${JSON.stringify(m, null, 2)}\n`, "utf8"), l && u && await R(l.store, u, { assets: f }), B.info("native backup complete", {
1045
+ let w = await _(r ? {
1046
+ count: v,
1047
+ totalBytes: y,
1048
+ references: C,
1049
+ objects: x,
1050
+ objectBytes: S
1051
+ } : null);
1052
+ return u && f && await z(u, f, { assets: v }), V.info("native backup complete", {
1000
1053
  dialect: n,
1001
- artifact: c,
1002
- stamp: L,
1003
- schemaFingerprint: m.schemaFingerprint ?? "<none>",
1004
- assets: r ? `${f} blob(s), ${p} byte(s) under ${D}/` : "NOT included — re-run with --assets, or use `voltro data export --assets`",
1005
- recorded: u?.recorded === !0 ? "voltro data transfers" : "NOT recorded (this target keeps no transfer history)"
1054
+ artifact: l,
1055
+ stamp: R,
1056
+ schemaFingerprint: w.schemaFingerprint ?? "<none>",
1057
+ assets: r ? `${v} key(s) → ${x} object(s), ${S} byte(s) under ${k}/ (from ${C} reference(s))` + (b.length > 0 ? ` — ${b.length} reference(s) had no object and are NOT in here` : "") : "NOT included — re-run with --assets, or use `voltro data export --assets`",
1058
+ recorded: jt(f, d)
1006
1059
  }), 0;
1007
- }, At = async () => {
1060
+ }, jt = (e, t) => e?.recorded === !0 ? "voltro data transfers" : t === void 0 ? "NOT recorded (this target keeps no transfer history)" : `NOT recorded — bookkeeping was unavailable HERE, not at the target: ${t}`, Mt = async () => {
1061
+ let e;
1008
1062
  try {
1009
- let e = await r(process.cwd());
1010
- return e ? { store: await i(e, void 0) } : void 0;
1011
- } catch {
1012
- return;
1063
+ e = await r(process.cwd());
1064
+ } catch (e) {
1065
+ return {
1066
+ unavailable: "store-unopenable",
1067
+ reason: `app.config.ts could not be loaded: ${Nt(e)}`
1068
+ };
1069
+ }
1070
+ if (!e) return { unavailable: "no-app-config" };
1071
+ try {
1072
+ return { store: await i(e, void 0) };
1073
+ } catch (e) {
1074
+ return {
1075
+ unavailable: "store-unopenable",
1076
+ reason: Nt(e)
1077
+ };
1013
1078
  }
1014
- }, jt = async (e, t) => {
1015
- let n = W(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, k(n)), o = qe({
1016
- flagUrl: H(t, "--drill-url"),
1079
+ }, Nt = (e) => {
1080
+ let t = e?.message, n = typeof t == "string" && t.trim() !== "" ? t : String(e);
1081
+ return n.split("\n")[0] ?? n;
1082
+ }, Pt = (e) => "store" in e ? e.store : void 0, Ft = (e) => "store" in e ? void 0 : e.unavailable === "no-app-config" ? "no app.config.ts was found from this directory, so there is no store to record through" : e.reason, It = async (e, t) => {
1083
+ let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, j(n)), o = qe({
1084
+ flagUrl: U(t, "--drill-url"),
1017
1085
  envUrl: process.env.DRILL_DB_URL,
1018
1086
  liveKeys: nt()
1019
1087
  });
1020
- if (!o.ok) return B.error(`restore --drill: ${o.error}`), 1;
1021
- let s = await i(r(e, L), "utf8").then(He, () => null);
1022
- if (s && s.dialect !== n) return B.error(`restore --drill: backup is a ${s.dialect} dump but DB_DIALECT is ${n} — cross-dialect restore is never valid.`), 1;
1023
- B.info("restore drill: restoring into throwaway DB", {
1088
+ if (!o.ok) return V.error(`restore --drill: ${o.error}`), 1;
1089
+ let s = await i(r(e, R), "utf8").then(He, () => null);
1090
+ if (s && s.dialect !== n) return V.error(`restore --drill: backup is a ${s.dialect} dump but DB_DIALECT is ${n} — cross-dialect restore is never valid.`), 1;
1091
+ V.info("restore drill: restoring into throwaway DB", {
1024
1092
  dialect: n,
1025
- target: z(o.url)
1026
- }), await T.runPromise(M(we(n, { url: o.url }, a)));
1093
+ target: B(o.url)
1094
+ }), await D.runPromise(Ee(Ce(n, { url: o.url }, a)));
1027
1095
  let c = null, l = 0;
1028
1096
  try {
1029
1097
  let e = await et(n, { url: o.url });
1030
- c = ue(e), l = e.tables.length;
1098
+ c = ce(e), l = e.tables.length;
1031
1099
  } catch (e) {
1032
- return B.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
1100
+ return V.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
1033
1101
  }
1034
1102
  let u = Je({
1035
1103
  stampFingerprint: s?.schemaFingerprint ?? null,
1036
1104
  restoredFingerprint: c,
1037
1105
  restoredTableCount: l
1038
1106
  });
1039
- for (let e of u.lines) u.pass ? B.info(`restore drill: ${e}`) : B.error(`restore drill: ${e}`);
1107
+ for (let e of u.lines) u.pass ? V.info(`restore drill: ${e}`) : V.error(`restore drill: ${e}`);
1040
1108
  return +!u.pass;
1041
- }, Mt = async (e) => {
1042
- let t = U(e);
1043
- if (!t) return B.error("usage: voltro data restore <backupDir> [--assets] [--allow-live] [--api-url <url>] [--force] [--drill [--drill-url <url>]] (native pg_restore/mariadb/…)"), 1;
1044
- if (e.includes("--drill")) return jt(t, e);
1109
+ }, Lt = async (e) => {
1110
+ let t = W(e);
1111
+ if (!t) return V.error("usage: voltro data restore <backupDir> [--assets] [--allow-live] [--api-url <url>] [--force] [--no-marker] [--drill [--drill-url <url>]] (native pg_restore/mariadb/…)"), 1;
1112
+ if (e.includes("--drill")) return It(t, e);
1045
1113
  if (!await Qe(e)) return 1;
1046
- let n = W(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, k(n)), o = e.includes("--assets"), s = Ue({
1047
- stamp: await i(r(t, L), "utf8").then(He, () => null),
1114
+ let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, j(n)), o = e.includes("--assets"), s = Ue({
1115
+ stamp: await i(r(t, R), "utf8").then(He, () => null),
1048
1116
  currentDialect: n,
1049
- currentFingerprint: await Xe(n)
1117
+ targetFingerprint: await Xe(n)
1050
1118
  });
1051
- for (let e of s.info) B.info(`backup stamp: ${e}`);
1052
- if (s.warn && B.warn(s.warn), s.refuse) {
1053
- if (!e.includes("--force")) return B.error(`restore refused: ${s.refuse} (pass --force to override)`), 1;
1054
- B.warn(`restore proceeding despite: ${s.refuse} (--force)`);
1119
+ for (let e of s.info) V.info(`backup stamp: ${e}`);
1120
+ if (s.warn && V.warn(s.warn), s.refuse) {
1121
+ if (!e.includes("--force")) return V.error(`restore refused: ${s.refuse} (pass --force to override)`), 1;
1122
+ V.warn(`restore proceeding despite: ${s.refuse} (--force)`);
1055
1123
  }
1056
- let c = r(t, O), l = await i(c, "utf8").then(() => !0, () => !1);
1057
- if (o && !l) return B.error(`--assets: this backup carries no assets — there is no ${O} in ${t}.\n It was taken without --assets, so its rows will reference blobs this restore cannot
1124
+ let c = r(t, A), l = await i(c, "utf8").then(() => !0, () => !1);
1125
+ if (o && !l) return V.error(`--assets: this backup carries no assets — there is no ${A} in ${t}.\n It was taken without --assets, so its rows will reference blobs this restore cannot
1058
1126
  put back. Restore from a backup taken with --assets, or drop the flag to accept that.`), 1;
1059
- !o && l && B.warn("this backup CARRIES assets and --assets was not passed: the rows will be restored and the blobs will not. Pass --assets to restore both.");
1060
- let u = o ? E({}) : void 0;
1061
- if (o && !u) return B.error("--assets: this app has no storage provider configured, so there is nowhere to put the blobs."), 1;
1062
- let d = await At(), f = d ? await Ge(d.store, { artifact: a }) : void 0;
1063
- if (d && f === void 0) return B.error(`refusing to restore: could not write the in-progress marker.
1064
- It is one row in \`${b}\`, written before the first destructive\n statement so a killed restore refuses the next boot rather than serving a half-loaded
1065
- database. Run \`voltro db apply\` so the table exists, then retry.`), 1;
1066
- let p = d ? await We(d.store, {
1127
+ !o && l && V.warn("this backup CARRIES assets and --assets was not passed: the rows will be restored and the blobs will not. Pass --assets to restore both.");
1128
+ let u = await Mt(), d = o ? O() : void 0;
1129
+ if (o && !d) return V.error("--assets: this app has no storage provider configured, so there is nowhere to put the blobs.\n Install `storagePlugin(...)` in app.config.ts or set STORAGE_PROVIDER, or drop the flag."), 1;
1130
+ let f = Ft(u), p = e.includes("--no-marker"), m = Pt(u), h = m ? await Ge(m, { artifact: a }) : void 0, g = f === void 0 ? h === void 0 ? `the marker row could not be written to \`${b}\` (run \`voltro db apply\` so the table exists)` : void 0 : `bookkeeping is unavailable: ${f}`;
1131
+ if (g !== void 0 && !p) return V.error(`refusing to restore: the in-progress marker cannot be written.
1132
+ reason: ${g}\n The marker is one row in \`${b}\`, written before the first\n destructive statement so a killed restore refuses the next boot instead of serving a
1133
+ half-loaded database. Without it this restore has no guard at all.
1134
+ Fix the reason above, or pass --no-marker to restore WITHOUT that protection,
1135
+ deliberately.`), 1;
1136
+ g !== void 0 && V.warn(`--no-marker: restoring with NO in-progress marker. If this run does not finish, nothing
1137
+ will stop the next boot from serving a half-loaded database.
1138
+ ${g}`);
1139
+ let _ = m ? await We(m, {
1067
1140
  direction: "restore",
1068
1141
  dialect: n,
1069
1142
  artifact: a,
1070
1143
  assets: o
1071
- }) : void 0, m = await Ke(M(we(n, Ot(), a)));
1072
- if (m !== void 0) return d && p && await R(d.store, p, { failure: m }), B.error(`native restore failed: ${m}\n This database is now in an unknown state and the next boot will REFUSE, by design.
1073
- Re-run the restore to completion, or clear the marker deliberately with
1074
- \`voltro data clear-replace-marker --yes\` once you have decided the state is correct.`), 1;
1075
- let h = 0;
1076
- if (u) {
1077
- let e = await T.runPromiseExit(Ce({
1078
- sink: De(u),
1079
- assetsDir: r(t, D),
1144
+ }) : void 0, v = await Ke(Ee(Ce(n, kt(), a))), y = h === void 0 ? "lost" : await h.reassert();
1145
+ if (y === "rewritten" ? V.warn(`the restore artefact removed the in-progress marker (it carries \`${b}\`) it has been written back.\n Backups taken from this version on exclude that table, so this only affects older
1146
+ and hand-made dumps.`) : y === "lost" && V.error(`the restore artefact removed the in-progress marker and it could NOT be written back.\n \`${b}\` is unwritable, so if this restore does not finish,\n nothing will stop the next boot from serving a half-loaded database. Check the
1147
+ result of this command before you start the app.`), v !== void 0) return m && _ && await z(m, _, { failure: v }), V.error(`native restore failed: ${v}\n` + (y === "lost" ? " This database is now in an unknown state and NOTHING WILL STOP THE NEXT BOOT — see above.\n" : " This database is now in an unknown state and the next boot will REFUSE, by design.\n") + " Re-run the restore to completion, or clear the marker deliberately with\n `voltro data clear-replace-marker --yes` once you have decided the state is correct."), 1;
1148
+ let x = 0, S = 0;
1149
+ if (d) {
1150
+ let e = await D.runPromiseExit(Se({
1151
+ sink: De(d),
1152
+ assetsDir: r(t, k),
1080
1153
  indexPath: c
1081
1154
  }));
1082
1155
  if (e._tag === "Failure") {
1083
- let t = w.pretty(e.cause);
1084
- return d && p && await R(d.store, p, { failure: `assets: ${t}` }), B.error(`the rows were restored, but the asset restore failed: ${t}\n The in-progress marker is still set and the next boot will refuse. Re-run
1156
+ let t = E.pretty(e.cause);
1157
+ return m && _ && await z(m, _, { failure: `assets: ${t}` }), V.error(`the rows were restored, but the asset restore failed: ${t}\n The in-progress marker is still set and the next boot will refuse. Re-run
1085
1158
  \`voltro data restore <dir> --assets\` once storage is reachable.`), 1;
1086
1159
  }
1087
- h = e.value;
1160
+ x = e.value.count, S = e.value.objects;
1088
1161
  }
1089
- return f && await f(), d && p && await R(d.store, p, { assets: h }), B.info("native restore complete", {
1162
+ return h && await h.clear(), m && _ && await z(m, _, { assets: x }), V.info("native restore complete", {
1090
1163
  dialect: n,
1091
1164
  artifact: a,
1092
- assets: o ? `${h} blob(s) restored` : "not restored (no --assets)",
1093
- recorded: p?.recorded === !0 ? "voltro data transfers" : "NOT recorded (this target keeps no transfer history)"
1165
+ assets: o ? `${x} key(s) restored from ${S} object(s)` : "not restored (no --assets)",
1166
+ recorded: jt(_, f)
1094
1167
  }), 0;
1095
- }, Nt = async (e) => {
1096
- let t = U(e);
1097
- if (!t) return B.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
1168
+ }, Rt = async (e) => {
1169
+ let t = W(e);
1170
+ if (!t) return V.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
1098
1171
  let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
1099
1172
  if ((await i.stat(a).catch(() => void 0))?.isDirectory()) {
1100
1173
  let n = r(a, "manifest.json"), o = r(a, "README.md");
@@ -1103,24 +1176,24 @@ var Fe = /* @__PURE__ */ new Set([
1103
1176
  if (e !== void 0) return console.log(e), 0;
1104
1177
  }
1105
1178
  let s = await i.readFile(n, "utf8").catch(() => void 0);
1106
- return s === void 0 ? (B.error(`'${t}' is a directory but holds no manifest.json — is it an unpacked bundle? (a bundle exported with --target api is always unpacked into a directory, even when the path ends in .vbundle)`), 1) : (console.log(s), 0);
1179
+ return s === void 0 ? (V.error(`'${t}' is a directory but holds no manifest.json — is it an unpacked bundle? (a bundle exported with --target api is always unpacked into a directory, even when the path ends in .vbundle)`), 1) : (console.log(s), 0);
1107
1180
  }
1108
1181
  let o = J(e), s = () => {
1109
1182
  let e = Y(a);
1110
- return o ? A(e, o) : e;
1183
+ return o ? M(e, o) : e;
1111
1184
  };
1112
1185
  if (e.includes("--json")) {
1113
- let e = (await j(s(), ["manifest.json"])).get("manifest.json");
1114
- return e ? (console.log(e.toString("utf8")), 0) : (B.error("no manifest.json found in the bundle"), 1);
1186
+ let e = (await N(s(), ["manifest.json"])).get("manifest.json");
1187
+ return e ? (console.log(e.toString("utf8")), 0) : (V.error("no manifest.json found in the bundle"), 1);
1115
1188
  }
1116
- let c = (await j(s(), ["README.md"])).get("README.md");
1189
+ let c = (await N(s(), ["README.md"])).get("README.md");
1117
1190
  if (c) return console.log(c.toString("utf8")), 0;
1118
- let l = (await j(s(), ["manifest.json"])).get("manifest.json");
1119
- return l ? (console.log(l.toString("utf8")), 0) : (B.error("bundle has no README.md or manifest.json"), 1);
1120
- }, Pt = async (e) => {
1121
- let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-CvmiAM9K.js"), { store: r } = await G(), i = await n(r);
1122
- if (i === void 0) return B.info("no interrupted replace is recorded — nothing to clear"), 0;
1123
- if (!e.includes("--yes")) return B.error(`${i}\n\n Re-run with --yes to clear it.`), 1;
1191
+ let l = (await N(s(), ["manifest.json"])).get("manifest.json");
1192
+ return l ? (console.log(l.toString("utf8")), 0) : (V.error("bundle has no README.md or manifest.json"), 1);
1193
+ }, zt = async (e) => {
1194
+ let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-CvmiAM9K.js"), { store: r } = await K(), i = await n(r);
1195
+ if (i === void 0) return V.info("no interrupted replace is recorded — nothing to clear"), 0;
1196
+ if (!e.includes("--yes")) return V.error(`${i}\n\n Re-run with --yes to clear it.`), 1;
1124
1197
  let a = await r.query({
1125
1198
  table: t,
1126
1199
  predicate: void 0,
@@ -1130,30 +1203,30 @@ var Fe = /* @__PURE__ */ new Set([
1130
1203
  projection: void 0
1131
1204
  });
1132
1205
  for (let e of a) await r.delete(t, String(e.id));
1133
- return B.info(`cleared ${a.length} interrupted-replace marker(s) — the next boot will start`), 0;
1134
- }, Ft = async (e) => {
1135
- let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await G(), a = W();
1136
- if (a !== "postgres" && a !== "mysql" && a !== "mariadb" && a !== "sqlite" && a !== "mssql") return B.error(`this store has no staging tables to clear (dialect: ${a})`), 1;
1206
+ return V.info(`cleared ${a.length} interrupted-replace marker(s) — the next boot will start`), 0;
1207
+ }, Bt = async (e) => {
1208
+ let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await K(), a = G();
1209
+ if (a !== "postgres" && a !== "mysql" && a !== "mariadb" && a !== "sqlite" && a !== "mssql") return V.error(`this store has no staging tables to clear (dialect: ${a})`), 1;
1137
1210
  let { introspectSchema: o } = await import("@voltro/database/sql"), s = (await i.run((await import("@effect/sql")).SqlClient.SqlClient.pipe((await import("effect")).Effect.flatMap(o)))).tables.map((e) => e.name).filter((e) => e.startsWith(t));
1138
- return s.length === 0 ? (B.info("no staging tables are present — nothing to clear"), 0) : e.includes("--yes") ? (await r(i, s.map((e) => n(e.slice(t.length), a))), B.info(`dropped ${s.length} staging table(s)`), 0) : (B.error(`${s.length} staging table(s) from an earlier \`--mode replace\`:\n` + s.map((e) => ` ${e}`).join("\n") + "\n\n Each holds a copy of what a run was loading. Nothing else removes them.\n Make sure no import is running right now — this cannot tell a leftover from one in flight.\n Re-run with --yes to drop them."), 1);
1139
- }, It = async (e) => {
1140
- let { describeTransferRun: t, DATA_TRANSFERS_TABLE: n } = await import("@voltro/database"), r = Number(H(e, "--limit") ?? "20"), i = Number.isFinite(r) ? Math.max(1, Math.min(200, Math.trunc(r))) : 20;
1141
- if (H(e, "--target") === "api") {
1142
- let t = H(e, "--api-url");
1143
- if (!t) return B.error("--target api requires --api-url <url> (the running instance)"), 1;
1144
- let n = H(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
1145
- if (!n) return B.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET)"), 1;
1211
+ return s.length === 0 ? (V.info("no staging tables are present — nothing to clear"), 0) : e.includes("--yes") ? (await r(i, s.map((e) => n(e.slice(t.length), a))), V.info(`dropped ${s.length} staging table(s)`), 0) : (V.error(`${s.length} staging table(s) from an earlier \`--mode replace\`:\n` + s.map((e) => ` ${e}`).join("\n") + "\n\n Each holds a copy of what a run was loading. Nothing else removes them.\n Make sure no import is running right now — this cannot tell a leftover from one in flight.\n Re-run with --yes to drop them."), 1);
1212
+ }, Vt = async (e) => {
1213
+ let { describeTransferRun: t, DATA_TRANSFERS_TABLE: n } = await import("@voltro/database"), r = Number(U(e, "--limit") ?? "20"), i = Number.isFinite(r) ? Math.max(1, Math.min(200, Math.trunc(r))) : 20;
1214
+ if (U(e, "--target") === "api") {
1215
+ let t = U(e, "--api-url");
1216
+ if (!t) return V.error("--target api requires --api-url <url> (the running instance)"), 1;
1217
+ let n = U(e, "--token") ?? process.env.VOLTRO_DATA_TRANSFER_SECRET;
1218
+ if (!n) return V.error("--target api requires --token <secret> (or VOLTRO_DATA_TRANSFER_SECRET)"), 1;
1146
1219
  let r = await fetch(`${t.replace(/\/+$/, "")}/_voltro/admin/transfers?limit=${i}`, { headers: { authorization: `Bearer ${n}` } }), a = await r.text();
1147
- if (r.status !== 200) return B.error(`api transfers: ${r.status} ${a}`), 1;
1220
+ if (r.status !== 200) return V.error(`api transfers: ${r.status} ${a}`), 1;
1148
1221
  let o = JSON.parse(a);
1149
1222
  if (e.includes("--json")) return console.log(a), 0;
1150
1223
  let s = o.runs ?? [];
1151
- if (s.length === 0) return B.info("no data transfers recorded on this instance"), 0;
1152
- (o.running ?? 0) > 0 && B.info(`${o.running} transfer(s) IN FLIGHT — re-run this to watch the counters move`);
1153
- for (let e of s) B.info(` ${e.line ?? ""}`);
1224
+ if (s.length === 0) return V.info("no data transfers recorded on this instance"), 0;
1225
+ (o.running ?? 0) > 0 && V.info(`${o.running} transfer(s) IN FLIGHT — re-run this to watch the counters move`);
1226
+ for (let e of s) V.info(` ${e.line ?? ""}`);
1154
1227
  return 0;
1155
1228
  }
1156
- let { store: a } = await G(), o;
1229
+ let { store: a } = await K(), o;
1157
1230
  try {
1158
1231
  o = await a.query({
1159
1232
  table: n,
@@ -1167,23 +1240,23 @@ var Fe = /* @__PURE__ */ new Set([
1167
1240
  take: i
1168
1241
  });
1169
1242
  } catch {
1170
- return B.error(`this target has no \`${n}\` table, so no transfer has been recorded here.\n Run \`voltro db apply\`; the transfers themselves are unaffected by its absence.`), 1;
1243
+ return V.error(`this target has no \`${n}\` table, so no transfer has been recorded here.\n Run \`voltro db apply\`; the transfers themselves are unaffected by its absence.`), 1;
1171
1244
  }
1172
- if (o.length === 0) return B.info("no data transfers recorded against this target"), 0;
1245
+ if (o.length === 0) return V.info("no data transfers recorded against this target"), 0;
1173
1246
  for (let e of o) {
1174
1247
  let n = e;
1175
- B.info(` ${t({
1248
+ V.info(` ${t({
1176
1249
  ...e,
1177
1250
  startedAt: new Date(n.startedAt),
1178
1251
  finishedAt: n.finishedAt === null ? null : new Date(n.finishedAt)
1179
1252
  })}`);
1180
1253
  }
1181
1254
  return 0;
1182
- }, Lt = async (e) => {
1183
- let t = U(e), n = U(e.filter((e) => e !== t));
1184
- if (!t || !n) return B.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
1255
+ }, Ht = async (e) => {
1256
+ let t = W(e), n = W(e.filter((e) => e !== t));
1257
+ if (!t || !n) return V.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
1185
1258
  let { resolve: r } = await import("node:path"), i = Y(r(process.cwd(), t)), a = J(e);
1186
- return a && (i = A(i, a)), await Me(i, r(process.cwd(), n)), B.info("unpack complete", {
1259
+ return a && (i = M(i, a)), await Me(i, r(process.cwd(), n)), V.info("unpack complete", {
1187
1260
  file: t,
1188
1261
  dir: n
1189
1262
  }), 0;
@@ -1263,17 +1336,18 @@ var Fe = /* @__PURE__ */ new Set([
1263
1336
  "--assets",
1264
1337
  "--allow-live",
1265
1338
  "--force",
1339
+ "--no-marker",
1266
1340
  "--drill",
1267
1341
  "--drill-url",
1268
1342
  "--api-url"
1269
1343
  ] }
1270
- }, Rt = (e) => e.split("=")[0], zt = (e, t) => {
1344
+ }, Ut = (e) => e.split("=")[0], Wt = (e, t) => {
1271
1345
  let n = $[e];
1272
1346
  if (!n) return;
1273
- let r = H(t, "--target") === "api" ? "api" : "direct", i = /* @__PURE__ */ new Set([...n.common, ...r === "api" ? n.apiOnly ?? [] : n.directOnly ?? []]), a = new Set(r === "api" ? n.directOnly ?? [] : n.apiOnly ?? []);
1347
+ let r = U(t, "--target") === "api" ? "api" : "direct", i = /* @__PURE__ */ new Set([...n.common, ...r === "api" ? n.apiOnly ?? [] : n.directOnly ?? []]), a = new Set(r === "api" ? n.directOnly ?? [] : n.apiOnly ?? []);
1274
1348
  for (let n of t) {
1275
1349
  if (!n.startsWith("--")) continue;
1276
- let t = Rt(n);
1350
+ let t = Ut(n);
1277
1351
  if (i.has(t)) continue;
1278
1352
  if (a.has(t)) return `voltro data ${e}: ${t} is not read with --target ${r} (it is a --target ${r === "api" ? "direct" : "api"} flag). Remove it, or change --target.`;
1279
1353
  let o = Object.entries($).filter(([n, r]) => n !== e && [
@@ -1283,24 +1357,24 @@ var Fe = /* @__PURE__ */ new Set([
1283
1357
  ].includes(t)).map(([e]) => e);
1284
1358
  return o.length > 0 ? `voltro data ${e}: ${t} is not a \`${e}\` flag (it belongs to \`voltro data ${o.join("` / `")}\`). Remove it.` : `voltro data ${e}: unknown flag ${t}. Run \`voltro data --help\` for the flags this command reads.`;
1285
1359
  }
1286
- }, Bt = {
1287
- export: yt,
1288
- import: Dt,
1289
- transfers: It,
1290
- inspect: Nt,
1291
- unpack: Lt,
1292
- backup: kt,
1293
- restore: Mt,
1294
- "clear-replace-marker": Pt,
1295
- "clear-staging": Ft
1296
- }, Vt = async (n) => {
1360
+ }, Gt = {
1361
+ export: xt,
1362
+ import: Ot,
1363
+ transfers: Vt,
1364
+ inspect: Rt,
1365
+ unpack: Ht,
1366
+ backup: At,
1367
+ restore: Lt,
1368
+ "clear-replace-marker": zt,
1369
+ "clear-staging": Bt
1370
+ }, Kt = async (n) => {
1297
1371
  let [r, ...i] = n;
1298
1372
  if (r !== void 0 && $[r] !== void 0) {
1299
- let e = zt(r, i);
1300
- if (e) return B.error(e), 2;
1373
+ let e = Wt(r, i);
1374
+ if (e) return V.error(e), 2;
1301
1375
  }
1302
- let a = r === void 0 ? void 0 : Bt[r];
1303
- return a === void 0 ? (B.error(`usage: voltro data ${e(t)} … (logical: export/import; native: backup/restore; \`imports\` reads the history, incl. a run in flight)`), r ? 2 : 1) : a(i);
1376
+ let a = r === void 0 ? void 0 : Gt[r];
1377
+ return a === void 0 ? (V.error(`usage: voltro data ${e(t)} … (logical: export/import; native: backup/restore; \`imports\` reads the history, incl. a run in flight)`), r ? 2 : 1) : a(i);
1304
1378
  };
1305
1379
  //#endregion
1306
- export { Vt as runDataCommand };
1380
+ export { Kt as runDataCommand };