@voltro/cli 0.54.0 → 0.56.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 (173) hide show
  1. package/CHANGELOG.md +639 -2
  2. package/bin/voltro.mjs +24 -0
  3. package/dist/{apiBuild-DTWp0S_q.js → apiBuild-Bdaetr37.js} +119 -88
  4. package/dist/apiBuild-Vw1figjO.js +2 -0
  5. package/dist/bin.js +1 -1
  6. package/dist/{build-D4ygSbnV.js → build-CI36wL4R.js} +339 -314
  7. package/dist/buildReport-52gHKgfO.js +64 -0
  8. package/dist/{checkCommand-Dg1G7Gwd.js → checkCommand-CAwFXrxA.js} +6 -6
  9. package/dist/{checkCommand-L7DTlpIF.js → checkCommand-D0QV_zM_.js} +1 -1
  10. package/dist/{clusterCmd-DrVFCzSj.js → clusterCmd-CdLB1GkT.js} +1 -1
  11. package/dist/{codegen-DSLM8Su9.js → codegen-Bth5lUTU.js} +76 -64
  12. package/dist/codegen-DbH7NbCR.js +2 -0
  13. package/dist/{codegenCommand-CG_Vx4lc.js → codegenCommand-CidbQzbv.js} +15 -14
  14. package/dist/{codemodRunner-Cd4xkC6u.js → codemodRunner-BlQPfjzA.js} +277 -0
  15. package/dist/{commands-6Kzi92Np.js → commands-CWjfThXv.js} +35 -35
  16. package/dist/{dashboardCommand-Cq1PWvI1.js → dashboardCommand-BekcY5Ls.js} +3 -3
  17. package/dist/{dataCommand-DYzW8vkv.js → dataCommand-2pccgbIy.js} +267 -195
  18. package/dist/{dbCommand-B4NWZtGL.js → dbCommand-DpK_vQET.js} +457 -441
  19. package/dist/dbCommand-DrycGWWt.js +2 -0
  20. package/dist/{dev-cKUiZZsB.js → dev-B9Gz0k85.js} +1 -1
  21. package/dist/{dev-CmuvUKRq.js → dev-Dw263KPu.js} +2598 -2452
  22. package/dist/doctorCommand-BMWs6aVm.js +2 -0
  23. package/dist/{doctorCommand-DCiFVMtZ.js → doctorCommand-aR_bFmIi.js} +353 -251
  24. package/dist/{dormancyCommand-w1TrmgYP.js → dormancyCommand-eXTQMbHU.js} +1 -1
  25. package/dist/{embeddingsCommand-CMgPyRTr.js → embeddingsCommand-CTmiQvwa.js} +1 -1
  26. package/dist/{envCommand-Cyynmcfa.js → envCommand-BDUgV7EM.js} +12 -12
  27. package/dist/{evolveCommand-BwvQ8dVH.js → evolveCommand-YV8qW1LU.js} +2 -2
  28. package/dist/frameworkTableAssembly-CGNC0qr7.js +2 -0
  29. package/dist/{frameworkTableAssembly-D7LJuALW.js → frameworkTableAssembly-DNOFXfEQ.js} +102 -100
  30. package/dist/index.js +1 -1
  31. package/dist/{infoCommand-DlYlUPqs.js → infoCommand-BjVXpMlP.js} +1 -1
  32. package/dist/inspect-CNYvNXPU.js +1484 -0
  33. package/dist/inspect-S2rWy1Ys.js +2 -0
  34. package/dist/{inspectCmd-niF97fAq.js → inspectCmd-CP-G0sVK.js} +1 -1
  35. package/dist/{inspectFetch-EMuhTG_9.js → inspectFetch-BU1NyzxV.js} +36 -24
  36. package/dist/{inspectMetrics-CGF94puw.js → inspectMetrics-BY0Sjb2F.js} +19 -19
  37. package/dist/interruptedReplace-CwnkBb2X.js +41 -0
  38. package/dist/interruptedReplace-qzmFI020.js +2 -0
  39. package/dist/{logsCmd-B6oNsfaZ.js → logsCmd-BU8uCdys.js} +1 -1
  40. package/dist/{manifestBuild-Cqgsx2bM.js → manifestBuild-CEkjfpwc.js} +1 -1
  41. package/dist/manifestBuild-DIa_s6u0.js +2 -0
  42. package/dist/{migrate-BK_Bbx-_.js → migrate-SICulyz1.js} +2 -2
  43. package/dist/precompressAssets-YhTi1aWp.js +40 -0
  44. package/dist/{probeCommand-_C0YU207.js → probeCommand-6HxEkNDG.js} +2 -2
  45. package/dist/{runtimeTrace-CGWx1Q6l.js → runtimeTrace-DgYMc09E.js} +1 -1
  46. package/dist/{scheduleCmd-DQRu6BZC.js → scheduleCmd-DYBUfo_T.js} +1 -1
  47. package/dist/{sdkgen-CDGHQUFj.js → sdkgen-PY-umd6O.js} +1 -1
  48. package/dist/{seedRunner-Dgsiwk_e.js → seedRunner-DISBKow-.js} +16 -16
  49. package/dist/serveCommand-BITS8Hpj.js +2 -0
  50. package/dist/{serveCommand-Bje09q1v.js → serveCommand-DIJ3ma76.js} +951 -909
  51. package/dist/serveEntry.js +1 -1
  52. package/dist/{start-Clz-1BHB.js → start-B9NGB8gn.js} +627 -588
  53. package/dist/{start-B0bnJgxI.js → start-BFQQkL1i.js} +1 -1
  54. package/dist/startEntry.js +1 -1
  55. package/dist/staticCachePolicy-CIyj6DbS.js +15 -0
  56. package/dist/{test-D_kW4KMj.js → test-jipIQ5Mx.js} +1 -1
  57. package/dist/{tracesCmd-DgtgOUdi.js → tracesCmd-BWYDqMy6.js} +1 -1
  58. package/dist/{updateCommand-CRJlAOaM.js → updateCommand-C9n_Z_oG.js} +8 -2
  59. package/dist/updateCommand-DsXEAHbd.js +2 -0
  60. package/dist/webDev-C2dRz9s5.js +2 -0
  61. package/dist/{webDev-DSI9SOhs.js → webDev-C53hJdcL.js} +1265 -1288
  62. package/dist/{webhooksCommand-BvzXNHji.js → webhooksCommand-uuPu8qQX.js} +1 -1
  63. package/dist/{workflowsCmd-BGF-mRZ5.js → workflowsCmd-g-DNpaUc.js} +1 -1
  64. package/package.json +67 -19
  65. package/templates/AGENTS.core.md +20 -1
  66. package/templates/AGENTS.md +21 -2
  67. package/templates/agent-docs/_index.md +1 -1
  68. package/templates/agent-docs/_manifest.json +1 -1
  69. package/templates/agent-docs/authentication.md +49 -6
  70. package/templates/agent-docs/cli.md +216 -11
  71. package/templates/agent-docs/data.md +209 -18
  72. package/templates/agent-docs/database/scaling.md +40 -1
  73. package/templates/agent-docs/deployment.md +53 -1
  74. package/templates/agent-docs/internationalization.md +32 -0
  75. package/templates/agent-docs/local-first-mobile.md +9 -2
  76. package/templates/agent-docs/observability.md +227 -0
  77. package/templates/agent-docs/plugins/billing.md +15 -0
  78. package/templates/agent-docs/plugins/broadcast.md +2 -1
  79. package/templates/agent-docs/plugins/ratelimit.md +6 -1
  80. package/templates/agent-docs/plugins/row-history.md +11 -0
  81. package/templates/agent-docs/plugins.md +65 -8
  82. package/templates/agent-docs/reference.md +5 -3
  83. package/templates/agent-docs/routing.md +18 -0
  84. package/templates/agent-docs/schema-driven-ui.md +125 -0
  85. package/templates/agent-docs/templates/appshells.md +3 -3
  86. package/templates/agent-docs/whats-new.md +408 -106
  87. package/templates/apps/api-ai/package.json +6 -6
  88. package/templates/apps/api-auth/package.json +8 -8
  89. package/templates/apps/api-backend/package.json +7 -7
  90. package/templates/apps/api-backend-deactivation/package.json +7 -7
  91. package/templates/apps/api-backend-mail/package.json +8 -8
  92. package/templates/apps/api-backend-mariadb/package.json +9 -9
  93. package/templates/apps/api-backend-sqlite/package.json +8 -8
  94. package/templates/apps/api-backend-storage/package.json +8 -8
  95. package/templates/apps/api-cms/package.json +9 -9
  96. package/templates/apps/api-collab/package.json +8 -8
  97. package/templates/apps/api-data-advanced/package.json +8 -8
  98. package/templates/apps/api-durable/package.json +8 -8
  99. package/templates/apps/api-feature-flags/package.json +9 -9
  100. package/templates/apps/api-governance/package.json +8 -8
  101. package/templates/apps/api-kv/package.json +8 -8
  102. package/templates/apps/api-moderation/package.json +8 -8
  103. package/templates/apps/api-observability/package.json +8 -8
  104. package/templates/apps/api-ratelimit/package.json +8 -8
  105. package/templates/apps/api-rbac/package.json +8 -8
  106. package/templates/apps/api-rest/package.json +7 -7
  107. package/templates/apps/api-row-history/package.json +8 -8
  108. package/templates/apps/api-saas/package.json +11 -11
  109. package/templates/apps/api-saas-starter/package.json +10 -10
  110. package/templates/apps/api-search/package.json +8 -8
  111. package/templates/apps/api-status/package.json +8 -8
  112. package/templates/apps/api-webhooks/package.json +9 -9
  113. package/templates/apps/changelog/package.json +7 -7
  114. package/templates/apps/changelog/src/pages/[locale]/page.tsx +7 -1
  115. package/templates/apps/edge-functions/package.json +2 -2
  116. package/templates/apps/frontend-admin/package.json +7 -8
  117. package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +1 -2
  118. package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +1 -2
  119. package/templates/apps/frontend-app/package.json +8 -9
  120. package/templates/apps/frontend-app/src/pages/layout.tsx +1 -2
  121. package/templates/apps/frontend-auth/package.json +7 -8
  122. package/templates/apps/frontend-auth/src/components/AuthShell.tsx +1 -2
  123. package/templates/apps/frontend-blank/package.json +6 -7
  124. package/templates/apps/frontend-blank/src/pages/layout.tsx +1 -2
  125. package/templates/apps/frontend-cms/package.json +8 -9
  126. package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +1 -2
  127. package/templates/apps/frontend-collab/README.md +7 -2
  128. package/templates/apps/frontend-collab/package.json +9 -10
  129. package/templates/apps/frontend-collab/src/pages/layout.tsx +1 -2
  130. package/templates/apps/frontend-collab/src/pages/page.test.tsx +8 -4
  131. package/templates/apps/frontend-collab/src/pages/page.tsx +10 -5
  132. package/templates/apps/frontend-contact/package.json +7 -7
  133. package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +1 -1
  134. package/templates/apps/frontend-dashboard/package.json +6 -7
  135. package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +1 -2
  136. package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +1 -2
  137. package/templates/apps/frontend-docs/package.json +8 -8
  138. package/templates/apps/frontend-i18n/package.json +6 -6
  139. package/templates/apps/frontend-landing/package.json +7 -7
  140. package/templates/apps/frontend-portal/package.json +7 -8
  141. package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +1 -2
  142. package/templates/apps/frontend-saas/package.json +7 -8
  143. package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +1 -2
  144. package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +1 -2
  145. package/templates/apps/frontend-spa/package.json +6 -7
  146. package/templates/apps/frontend-spa/src/pages/layout.tsx +1 -2
  147. package/templates/apps/frontend-ssr/package.json +6 -7
  148. package/templates/apps/frontend-ssr/src/pages/layout.tsx +1 -2
  149. package/templates/apps/frontend-ssr-api/package.json +7 -8
  150. package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +1 -2
  151. package/templates/apps/frontend-static-blog/package.json +8 -8
  152. package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +1 -1
  153. package/templates/apps/frontend-static-blog/src/pages/[locale]/page.tsx +7 -1
  154. package/templates/apps/frontend-status/package.json +7 -8
  155. package/templates/apps/frontend-status/src/pages/layout.tsx +1 -2
  156. package/templates/apps/mobile-app/package.json +4 -4
  157. package/templates/baselines/compose/docker/api.Dockerfile +61 -5
  158. package/templates/baselines/compose/docker/web.Dockerfile +55 -10
  159. package/templates/baselines/compose-mariadb/docker/api.Dockerfile +61 -5
  160. package/templates/baselines/compose-mariadb/docker/web.Dockerfile +55 -10
  161. package/dist/apiBuild-CeUN55uk.js +0 -2
  162. package/dist/codegen-DjgxEOnD.js +0 -2
  163. package/dist/dbCommand-CSFWs9ev.js +0 -2
  164. package/dist/doctorCommand-J3qu4E0Y.js +0 -2
  165. package/dist/frameworkTableAssembly-IPD1pUnZ.js +0 -2
  166. package/dist/inspect-Bd8-9wsi.js +0 -1193
  167. package/dist/inspect-CuoDInfZ.js +0 -2
  168. package/dist/interruptedReplace-C3O3M1MM.js +0 -28
  169. package/dist/interruptedReplace-CvmiAM9K.js +0 -2
  170. package/dist/manifestBuild-C4-J1-m_.js +0 -2
  171. package/dist/serveCommand-BiPe8BJm.js +0 -2
  172. package/dist/updateCommand-CIoVDKnj.js +0 -2
  173. package/dist/webDev-DlvZO30c.js +0 -2
@@ -1,24 +1,24 @@
1
1
  import { a as e, t } from "./subcommandNames-CDzfEtKV.js";
2
2
  import { t as n } from "./cliError-BmdYnghb.js";
3
- import { B as r, c as i, i as a, o } from "./dev-CmuvUKRq.js";
3
+ import { B as r, c as i, i as a, o } from "./dev-Dw263KPu.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
- import { o as m, t as h } from "./inspectFetch-EMuhTG_9.js";
8
- import { r as g } from "./dbCommand-B4NWZtGL.js";
7
+ import { o as m, t as h } from "./inspectFetch-BU1NyzxV.js";
8
+ import { r as g } from "./dbCommand-DpK_vQET.js";
9
9
  import { resolve as _ } from "node:path";
10
10
  import { createLogger as v } from "@voltro/logger";
11
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
12
  import { Cause as E, Effect as D, Exit as re, Option as ie, Stream as ae } from "effect";
13
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
- import { request as Ne } from "node:http";
19
- import { request as Pe } from "node:https";
14
+ import { declaredSnapshot as se, fingerprintSchema as O, introspectSchema as ce } from "@voltro/database/sql";
15
+ import { STORAGE_REFS_TABLE as le, appStorageProvider as ue, configuredStorageProvider as k } from "@voltro/plugin-storage";
16
+ import { createHash as de } from "node:crypto";
17
+ import { ASSETS_DIR as A, ASSET_INDEX_FILE as j, BundleError as fe, asImportError as pe, assessLiveImport as me, backupArtifactName as M, backupCommand as he, captureAssetsToCas as ge, classificationFromSnapshot as _e, decryptStream as N, dumpExcludedTables as ve, encryptStream as ye, packBundle as be, peekBundle as P, previewMasking as xe, restoreAssetsFromCas as Se, restoreCommand as Ce, runExport as we, runImport as Te, runNativeStep as F, storageAssetSink as Ee, storageAssetSource as De, storageBlobSink as Oe, storageBlobSource as ke, unpackBundle as Ae, unpackBundleToDir as je } from "@voltro/data-transfer";
18
+ import { request as Me } from "node:http";
19
+ import { request as Ne } from "node:https";
20
20
  //#region src/longRequest.ts
21
- var Fe = /* @__PURE__ */ new Set([
21
+ var Pe = /* @__PURE__ */ new Set([
22
22
  "ECONNREFUSED",
23
23
  "ETIMEDOUT",
24
24
  "EHOSTUNREACH",
@@ -26,7 +26,7 @@ var Fe = /* @__PURE__ */ new Set([
26
26
  "ENOTFOUND",
27
27
  "ECONNRESET",
28
28
  "EAI_AGAIN"
29
- ]), P = class extends Error {
29
+ ]), I = class extends Error {
30
30
  url;
31
31
  cause;
32
32
  constructor(e, t) {
@@ -39,14 +39,14 @@ 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
- }, F = class extends Error {
42
+ }, Fe = 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
- }, I = async (e, t, n, r) => {
49
- let i = new URL(e), a = i.protocol === "https:" ? Pe : Ne, o = Date.now();
48
+ }, L = async (e, t, n, r) => {
49
+ let i = new URL(e), a = i.protocol === "https:" ? Ne : Me, o = Date.now();
50
50
  return await new Promise((s, c) => {
51
51
  let l = a({
52
52
  protocol: i.protocol,
@@ -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 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)) {
67
+ l.destroy(new Fe(Date.now() - o, e));
68
+ }), l.on("error", (t) => c(Pe.has(t.code ?? "") ? new I(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
- }, 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) {
81
+ }, Ie = (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`, Le = (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.", Re = 16777216, ze = 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,12 +100,12 @@ var Fe = /* @__PURE__ */ new Set([
100
100
  i += a.byteLength, yield s;
101
101
  }
102
102
  yield* a(!0);
103
- }, ze = (e) => fe("sha256").update(Buffer.from(e)).digest("hex"), Be = async (e) => {
103
+ }, Be = (e) => de("sha256").update(Buffer.from(e)).digest("hex"), Ve = async (e) => {
104
104
  let t = 0, n = 0, r = {
105
105
  status: 0,
106
106
  body: {}
107
107
  };
108
- for await (let i of Re(e.stream, e.chunkBytes)) {
108
+ for await (let i of ze(e.stream, e.chunkBytes)) {
109
109
  if (i.offset + i.bytes.byteLength <= e.alreadyHeld && !i.final) {
110
110
  n++;
111
111
  continue;
@@ -131,14 +131,15 @@ var Fe = /* @__PURE__ */ new Set([
131
131
  bytes: t,
132
132
  resumedFrom: e.alreadyHeld
133
133
  };
134
- }, R = "voltro-backup-stamp.json", Ve = (e) => ({
134
+ }, R = "voltro-backup-stamp.json", He = (e) => ({
135
135
  stampVersion: 1,
136
136
  dialect: e.dialect,
137
137
  schemaFingerprint: e.schemaFingerprint,
138
+ dumpFingerprint: e.dumpFingerprint ?? null,
138
139
  voltroVersion: e.voltroVersion,
139
140
  createdAt: e.createdAt,
140
141
  assets: e.assets ?? null
141
- }), He = (e) => {
142
+ }), Ue = (e) => {
142
143
  let t;
143
144
  try {
144
145
  t = JSON.parse(e);
@@ -150,25 +151,26 @@ var Fe = /* @__PURE__ */ new Set([
150
151
  if (n.stampVersion !== 1 || typeof n.dialect != "string" || typeof n.voltroVersion != "string" || typeof n.createdAt != "string") return null;
151
152
  let r = n.schemaFingerprint;
152
153
  if (r !== null && typeof r != "string") return null;
153
- let i = n.assets, a = (e) => {
154
- let t = i?.[e];
154
+ let i = n.dumpFingerprint, a = n.assets, o = (e) => {
155
+ let t = a?.[e];
155
156
  return typeof t == "number" ? t : void 0;
156
- }, o = typeof i == "object" && i && typeof i.count == "number" && typeof i.totalBytes == "number" ? {
157
- count: i.count,
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") }
157
+ }, s = typeof a == "object" && a && typeof a.count == "number" && typeof a.totalBytes == "number" ? {
158
+ count: a.count,
159
+ totalBytes: a.totalBytes,
160
+ ...o("references") === void 0 ? {} : { references: o("references") },
161
+ ...o("objects") === void 0 ? {} : { objects: o("objects") },
162
+ ...o("objectBytes") === void 0 ? {} : { objectBytes: o("objectBytes") }
162
163
  } : null;
163
164
  return {
164
165
  stampVersion: 1,
165
166
  dialect: n.dialect,
166
167
  schemaFingerprint: r,
168
+ dumpFingerprint: typeof i == "string" ? i : null,
167
169
  voltroVersion: n.voltroVersion,
168
170
  createdAt: n.createdAt,
169
- assets: o
171
+ assets: s
170
172
  };
171
- }, Ue = (e) => {
173
+ }, We = (e) => {
172
174
  let { stamp: t, currentDialect: n, targetFingerprint: r } = e;
173
175
  if (t === null) return {
174
176
  warn: `no ${R} beside the dump — an older/handmade backup. Cannot verify dialect or schema version; make sure this dump is a ${n} dump.`,
@@ -182,7 +184,7 @@ var Fe = /* @__PURE__ */ new Set([
182
184
  refuse: `dialect mismatch — backup is a ${t.dialect} dump, but DB_DIALECT is ${n}. A cross-dialect restore is never valid.`,
183
185
  info: i
184
186
  };
185
- }, We = async (e, t) => {
187
+ }, Ge = async (e, t) => {
186
188
  let n = w("dimp", `${t.direction}-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
187
189
  id: n,
188
190
  direction: t.direction,
@@ -230,7 +232,7 @@ var Fe = /* @__PURE__ */ new Set([
230
232
  ...r
231
233
  });
232
234
  } catch {}
233
- }, Ge = async (e, t) => {
235
+ }, Ke = async (e, t) => {
234
236
  let n = w("repl", `restore-${t.artifact}-${Date.now()}-${Math.random()}`), r = {
235
237
  id: n,
236
238
  startedAt: /* @__PURE__ */ new Date(),
@@ -271,7 +273,7 @@ var Fe = /* @__PURE__ */ new Set([
271
273
  } }).then(() => void 0, () => void 0);
272
274
  }
273
275
  };
274
- }, Ke = async (e) => {
276
+ }, qe = async (e) => {
275
277
  let t = await D.runPromiseExit(e);
276
278
  if (re.isSuccess(t)) return;
277
279
  let n = E.failureOption(t.cause);
@@ -287,7 +289,7 @@ var Fe = /* @__PURE__ */ new Set([
287
289
  } catch {
288
290
  return e.trim();
289
291
  }
290
- }, qe = (e) => {
292
+ }, Je = (e) => {
291
293
  let t = e.flagUrl ?? e.envUrl;
292
294
  if (t === void 0 || t.trim() === "") return {
293
295
  ok: !1,
@@ -301,39 +303,67 @@ var Fe = /* @__PURE__ */ new Set([
301
303
  ok: !0,
302
304
  url: t
303
305
  };
304
- }, Je = (e) => {
305
- let { stampFingerprint: t, restoredFingerprint: n, restoredTableCount: r } = e;
306
- return r === 0 ? {
306
+ }, Ye = (e) => {
307
+ let { dumpFingerprint: t, liveFingerprintAtBackup: n, restoredFingerprint: r, restoredTableCount: i, ledger: a } = e;
308
+ if (i === 0) return {
307
309
  pass: !1,
308
310
  lines: ["FAIL — the restore produced ZERO tables. The dump is empty or unreadable; this backup would NOT recover your app."]
309
- } : t !== null && n !== null && t !== n ? {
311
+ };
312
+ if (t !== null && r !== null && t !== r) return {
313
+ pass: !1,
314
+ lines: [`FAIL — restored ${i} table(s), but the schema fingerprint (${r.slice(0, 12)}) does NOT match what the backup's artifact should carry (${t.slice(0, 12)}).`, "The restore did not reproduce the schema that was backed up — the artifact is inconsistent."]
315
+ };
316
+ if (a.kind === "empty") return {
310
317
  pass: !1,
311
- lines: [`FAIL — restored ${r} table(s), but the schema fingerprint (${n.slice(0, 12)}) does NOT match the backup's stamp (${t.slice(0, 12)}).`, "The restore did not reproduce the schema that was backed up — the artifact is inconsistent."]
312
- } : t === null || n === null ? {
318
+ lines: [
319
+ `FAIL — restored ${i} table(s) with the right shape, but \`_voltro_migration_plans\` came back EMPTY.`,
320
+ "`voltro serve` reads the newest row of that table as its boot gate and refuses with `prod-mismatch` when there is none — no source tree makes an empty ledger boot. Restoring this artifact would give you a database the app will not serve.",
321
+ "Run `voltro db apply` after a real restore to record a baseline, or find out why the ledger rows did not cross."
322
+ ]
323
+ };
324
+ let o = a.kind === "recorded" ? `Migration ledger restored — newest recorded schema ${a.fingerprint.slice(0, 12)}; \`voltro serve\` boots against this database when your code declares that same schema.` : "No `_voltro_migration_plans` in the restored DB — not a voltro-managed schema, so nothing is claimed about booting against it.";
325
+ return t === null || r === null ? {
313
326
  pass: !0,
314
- lines: [`PASS (partial) — restored ${r} table(s) into the throwaway DB.`, "No stamped fingerprint to cross-check the schema shape (older/hand-made backup) — the bytes restore, but the shape was not verified."]
327
+ lines: [
328
+ `PASS (partial) — restored ${i} table(s) into the throwaway DB.`,
329
+ n === null ? "No stamped fingerprint to cross-check the schema shape (older/hand-made backup) — the bytes restore, but the shape was not verified." : "The stamp predates the dump-scoped fingerprint, and its `schemaFingerprint` covers the SOURCE database rather than the artifact — comparing against it would fail a healthy backup. Re-take the backup to cross-check the shape.",
330
+ o
331
+ ]
315
332
  } : {
316
333
  pass: !0,
317
- lines: [`PASS — restored ${r} table(s); schema fingerprint matches the backup stamp (${n.slice(0, 12)}).`]
334
+ lines: [`PASS — restored ${i} table(s); schema fingerprint matches the backup stamp (${r.slice(0, 12)}).`, o]
318
335
  };
319
- }, V = v({ scope: "voltro:data" }), Ye = async () => {
336
+ }, V = v({ scope: "voltro:data" }), Xe = async () => {
320
337
  try {
321
338
  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");
322
339
  return JSON.parse(n(r, "utf8")).version ?? "unknown";
323
340
  } catch {
324
341
  return "unknown";
325
342
  }
326
- }, Xe = async (e) => {
343
+ }, Ze = async (e) => {
327
344
  try {
328
- return ce(await tt(e));
345
+ return O(await K(e));
329
346
  } catch {
330
347
  return null;
331
348
  }
349
+ }, Qe = async (e) => {
350
+ try {
351
+ let t = await K(e), n = new Set(ve(e));
352
+ return {
353
+ live: O(t),
354
+ dump: O({ tables: t.tables.filter((e) => !n.has(e.name)) })
355
+ };
356
+ } catch {
357
+ return {
358
+ live: null,
359
+ dump: null
360
+ };
361
+ }
332
362
  }, H = (e) => {
333
363
  if (e.event !== "done") return;
334
364
  let t = e.total === void 0 ? "" : ` / ${e.total}`;
335
365
  V.info(`${e.phase} [${e.index + 1}/${e.tableCount}] ${e.table}`, { rows: `${e.rowsDone}${t}` });
336
- }, Ze = async (e) => {
366
+ }, $e = async (e) => {
337
367
  let t = (e) => e?.meta?.databaseId;
338
368
  if (e) {
339
369
  let n = await h({
@@ -353,21 +383,21 @@ var Fe = /* @__PURE__ */ new Set([
353
383
  };
354
384
  }
355
385
  return null;
356
- }, Qe = async (e) => {
357
- let t = e.includes("--allow-live"), n = t ? null : await Ze(U(e, "--api-url")), r = C({
386
+ }, et = async (e) => {
387
+ let t = e.includes("--allow-live"), n = t ? null : await $e(U(e, "--api-url")), r = C({
358
388
  dialect: G(),
359
389
  ...o()
360
390
  }), i = ee({
361
391
  dialect: G(),
362
392
  ...o()
363
- }), { refuse: a, message: s } = he({
393
+ }), { refuse: a, message: s } = me({
364
394
  liveInstance: n?.label ?? null,
365
395
  allowLive: t,
366
396
  sameTarget: te(r, n?.databaseId),
367
397
  ...i === void 0 ? {} : { targetLabel: `${i.host}:${i.port}/${i.database}` }
368
398
  });
369
399
  return !a || (V.error(s ?? "refusing: live instance detected"), !1);
370
- }, U = s, $e = [
400
+ }, U = s, tt = [
371
401
  "--api-url",
372
402
  "--bundle-key",
373
403
  "--compression",
@@ -387,42 +417,42 @@ var Fe = /* @__PURE__ */ new Set([
387
417
  "--timeout",
388
418
  "--tenant",
389
419
  "--token"
390
- ], W = (e) => c(e, $e), G = () => {
420
+ ], W = (e) => c(e, tt), G = () => {
391
421
  let e = (process.env.DB_DIALECT ?? "postgres").toLowerCase();
392
422
  if (e === "memory") throw new n("voltro data: requires a real dialect, not DB_DIALECT=memory.");
393
423
  return e;
394
- }, et = async (e, t) => {
424
+ }, nt = async (e, t) => {
395
425
  let n = (await l(e)).makeSqlLayer(t);
396
- return D.runPromise(oe.SqlClient.pipe(D.flatMap(le), D.provide(n)));
397
- }, tt = (e) => et(e, o()), nt = () => {
426
+ return D.runPromise(oe.SqlClient.pipe(D.flatMap(ce), D.provide(n)));
427
+ }, K = (e) => nt(e, o()), rt = () => {
398
428
  let e = o(), t = [];
399
429
  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 () => {
430
+ }, q = async () => {
401
431
  let e = process.cwd(), t = await i(e);
402
432
  if (!t) throw new n("voltro data: no app.config.ts found in the current directory.");
403
433
  let r = await a(t, void 0), o = G();
404
434
  return {
405
435
  store: r,
406
- snapshot: await tt(o),
436
+ snapshot: await K(o),
407
437
  dialect: o
408
438
  };
409
- }, rt = (e) => ne(e, {
410
- table: ue,
439
+ }, J = (e) => ne(e, {
440
+ table: le,
411
441
  chunkSize: 500
412
442
  }).pipe(ae.map((e) => ({
413
443
  key: String(e.key),
414
444
  contentType: String(e.contentType ?? "application/octet-stream"),
415
445
  size: Number(e.size ?? 0)
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) => {
446
+ }))), Y = (e) => !!e && e.endsWith(".vbundle"), X = (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) => {
417
447
  let { createWriteStream: n } = await import("node:fs"), { pipeline: r } = await import("node:stream/promises"), { Readable: i } = await import("node:stream");
418
448
  await r(i.from(e), n(t));
419
- }, Y = (e) => ({ async *[Symbol.asyncIterator]() {
449
+ }, Z = (e) => ({ async *[Symbol.asyncIterator]() {
420
450
  let { createReadStream: t } = await import("node:fs");
421
451
  for await (let n of t(e)) yield n instanceof Uint8Array ? n : new Uint8Array(n);
422
452
  } }), ot = async (e, t) => {
423
453
  if (t !== void 0 || e === void 0) return null;
424
454
  try {
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"));
455
+ let { resolve: t, join: n } = await import("node:path"), r = await import("node:fs/promises"), i = t(process.cwd(), e), a = Y(e) ? (await P(Z(i), ["manifest.json"])).get("manifest.json") : await r.readFile(n(i, "manifest.json"));
426
456
  if (a === void 0) return null;
427
457
  let o = (JSON.parse(Buffer.from(a).toString("utf8")).tables ?? []).map((e) => e.name).filter((e) => typeof e == "string");
428
458
  return o.length > 0 ? o : null;
@@ -491,7 +521,7 @@ var Fe = /* @__PURE__ */ new Set([
491
521
  } : { kind: "all" };
492
522
  }, pt = async () => {
493
523
  let e = await g(process.cwd());
494
- return ve(se(e));
524
+ return _e(se(e));
495
525
  }, mt = "voltro/tenant", ht = async () => {
496
526
  let e = (await g(process.cwd())).filter((e) => (e.appliedMixins ?? []).some((e) => e.id === mt)).map((e) => e.tableName);
497
527
  return e.length > 0 ? e : void 0;
@@ -509,7 +539,7 @@ var Fe = /* @__PURE__ */ new Set([
509
539
  for (let t of e.unclassified) console.log(` ${t}`);
510
540
  } else console.log("\n ✓ every exported column is classified — safe to run.");
511
541
  }, _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);
542
+ 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 ? Re : Math.round(Number(a) * 1024 * 1024);
513
543
  if (!Number.isFinite(o) || o <= 0) throw new n(`--chunk-size expects a positive number of megabytes, got '${String(a)}'`);
514
544
  let s = e.stream[Symbol.asyncIterator](), c = [], l = 0, u = !1;
515
545
  for (; l <= o;) {
@@ -524,11 +554,11 @@ var Fe = /* @__PURE__ */ new Set([
524
554
  kind: "small",
525
555
  body: Buffer.concat(c)
526
556
  };
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), {
557
+ let d = t(process.cwd(), e.bundleDir), f = Be(Y(e.bundleDir) ? await i.readFile(d) : await i.readFile(r(d, "manifest.json"))), p = `up-${f.slice(0, 32)}`, m = await L(e.url, Buffer.alloc(0), {
528
558
  ...e.headers,
529
559
  "x-import-upload-id": p,
530
560
  "x-import-probe": "1"
531
- }, Z(e.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
561
+ }, $(e.args)), h = m.status === 200 ? Number(JSON.parse(m.body.toString("utf8")).bytes ?? 0) : 0;
532
562
  h > 0 && e.log.info("api import: resuming a chunked upload the instance already holds", { bytes: h });
533
563
  let g = (async function* () {
534
564
  for (let e of c) yield e;
@@ -537,7 +567,7 @@ var Fe = /* @__PURE__ */ new Set([
537
567
  if (e.done === !0) return;
538
568
  yield e.value;
539
569
  }
540
- })(), _ = 0, v = await Be({
570
+ })(), _ = 0, v = await Ve({
541
571
  stream: g,
542
572
  chunkBytes: o,
543
573
  uploadId: p,
@@ -551,7 +581,7 @@ var Fe = /* @__PURE__ */ new Set([
551
581
  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.");
552
582
  },
553
583
  target: { send: async (t, n) => {
554
- let r = await I(e.url, t, n, Z(e.args)), i = {};
584
+ let r = await L(e.url, t, n, $(e.args)), i = {};
555
585
  try {
556
586
  i = JSON.parse(r.body.toString("utf8") || "{}");
557
587
  } catch {}
@@ -573,7 +603,7 @@ var Fe = /* @__PURE__ */ new Set([
573
603
  body: Buffer.from(JSON.stringify(v.body))
574
604
  }
575
605
  };
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) => {
606
+ }, 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.`, Q = (e, t) => e.includes("--no-atomic") ? !1 : e.includes("--atomic") || t === "replace", yt = (e, t, n) => {
577
607
  if (t !== "replace" || e.includes("--no-rollback")) return;
578
608
  let i = U(e, "--rollback-dir");
579
609
  return i === void 0 ? r(n) : _(i);
@@ -584,7 +614,7 @@ var Fe = /* @__PURE__ */ new Set([
584
614
  recordInterruption: !0,
585
615
  via: "direct"
586
616
  };
587
- }, Z = (e) => {
617
+ }, $ = (e) => {
588
618
  let t = U(e, "--timeout");
589
619
  if (t === void 0) return {};
590
620
  let r = Number(t);
@@ -611,15 +641,15 @@ var Fe = /* @__PURE__ */ new Set([
611
641
  ...n ? { bundleKey: n } : {}
612
642
  }, c = st(e), l;
613
643
  try {
614
- let n = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(s), {
644
+ let n = await L(`${t.replace(/\/+$/, "")}/_voltro/admin/export`, JSON.stringify(s), {
615
645
  "content-type": "application/json",
616
646
  authorization: `Bearer ${i}`
617
- }, c.perRequest ?? Z(e));
647
+ }, c.perRequest ?? $(e));
618
648
  if (n.status < 200 || n.status >= 300) return V.error(`api export failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
619
649
  l = JSON.parse(n.body.toString("utf8"));
620
650
  } catch (e) {
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;
651
+ if (e instanceof Fe) 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;
652
+ if (e instanceof I) return V.error(`api export: ${Ie(e)}`), 1;
623
653
  throw e;
624
654
  }
625
655
  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;
@@ -647,7 +677,7 @@ var Fe = /* @__PURE__ */ new Set([
647
677
  log: V,
648
678
  ...c.perRequest ? { perRequest: c.perRequest } : {}
649
679
  });
650
- return g === null ? 1 : (await Me((async function* () {
680
+ return g === null ? 1 : (await je((async function* () {
651
681
  yield g;
652
682
  })(), h(process.cwd(), r)), V.info("api export complete (downloaded from the running instance)", {
653
683
  outDir: r,
@@ -658,7 +688,7 @@ var Fe = /* @__PURE__ */ new Set([
658
688
  }), 0);
659
689
  }
660
690
  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;
691
+ let i = U(e, "--profile"), a = i ? await p(i, { allowPath: !0 }) : {}, { store: o, snapshot: s, dialect: c } = await q(), l = e.includes("--assets") || a.assets === !0, m = l ? k() : void 0;
662
692
  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
693
  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 } : {};
664
694
  if (e.includes("--dry-run")) {
@@ -684,7 +714,7 @@ var Fe = /* @__PURE__ */ new Set([
684
714
  }));
685
715
  return gt(t), +(t.unclassified.length > 0);
686
716
  }
687
- if (q(r)) {
717
+ if (Y(r)) {
688
718
  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-"));
689
719
  try {
690
720
  let t = await D.runPromise(we({
@@ -703,10 +733,10 @@ var Fe = /* @__PURE__ */ new Set([
703
733
  onProgress: H
704
734
  })), n = be({
705
735
  dir: u,
706
- ...m ? { blobs: Ae(m, () => rt(o)) } : {}
736
+ ...m ? { blobs: ke(m, () => J(o)) } : {}
707
737
  }), a = it(e);
708
738
  if (a) {
709
- let t = J(e);
739
+ let t = X(e);
710
740
  if (!t) return V.error("--encrypt requires --passphrase <p> or VOLTRO_BUNDLE_KEY (a dedicated key, NOT the transfer secret)"), 1;
711
741
  n = ye(n, t);
712
742
  }
@@ -737,7 +767,7 @@ var Fe = /* @__PURE__ */ new Set([
737
767
  ...h ? { compression: h } : {},
738
768
  ..._ ? { masking: _ } : {},
739
769
  ...b ? { classification: b } : {},
740
- ...m ? { assets: Oe(m, () => rt(o)) } : {},
770
+ ...m ? { assets: De(m, () => J(o)) } : {},
741
771
  onProgress: H
742
772
  }));
743
773
  return V.info("export complete", {
@@ -772,8 +802,8 @@ var Fe = /* @__PURE__ */ new Set([
772
802
  return;
773
803
  }
774
804
  V.info("import complete", t);
775
- }, Et = Symbol("refused"), Q = (e) => {
776
- let t = me(e) ?? e;
805
+ }, Et = Symbol("refused"), Dt = (e) => {
806
+ let t = pe(e) ?? e;
777
807
  if (t?._tag !== "RowsRefusedError" || !Array.isArray(t.rows)) return !1;
778
808
  let n = t.rows, r = n.filter((e) => !e.derived), i = Number(t.totalCount ?? n.length), a = Number(t.primaryCount ?? r.length);
779
809
  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.`);
@@ -783,7 +813,7 @@ var Fe = /* @__PURE__ */ new Set([
783
813
  n.length < i && V.error(` (the list above is capped at ${n.length} of ${i} — the counts by table are complete)`);
784
814
  let s = i - a;
785
815
  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) => {
816
+ }, Ot = (e) => {
787
817
  let t = typeof e == "string" ? e : e.toString("utf8"), n = t;
788
818
  try {
789
819
  let e = JSON.parse(t), r = e.error ?? e.message;
@@ -823,7 +853,7 @@ var Fe = /* @__PURE__ */ new Set([
823
853
  return;
824
854
  }
825
855
  }
826
- }, Ot = async (e) => {
856
+ }, kt = async (e) => {
827
857
  let t = U(e, "--target") === "api", n = U(e, "--bundle-key"), r = W(e);
828
858
  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
859
  let i = U(e, "--mode") ?? "upsert", a = U(e, "--on-conflict") ?? "skip";
@@ -838,7 +868,7 @@ var Fe = /* @__PURE__ */ new Set([
838
868
  authorization: `Bearer ${o}`,
839
869
  "x-import-mode": i,
840
870
  "x-import-on-conflict": a,
841
- ...X(e, i) ? { "x-import-atomic": "1" } : {},
871
+ ...Q(e, i) ? { "x-import-atomic": "1" } : {},
842
872
  ...e.includes("--no-atomic") ? { "x-import-atomic": "0" } : {},
843
873
  ...c === void 0 ? {} : { "x-import-rollback-key": c },
844
874
  ...e.includes("--force") ? { "x-import-force": "1" } : {},
@@ -846,23 +876,23 @@ var Fe = /* @__PURE__ */ new Set([
846
876
  ...s ? { "x-import-tables": s } : {}
847
877
  }, { resolve: p } = await import("node:path"), m;
848
878
  if (n) l["content-type"] = "application/json", m = JSON.stringify({ bundleKey: n });
849
- else if (q(r)) {
850
- let t = Y(p(process.cwd(), r)), n = J(e);
851
- n && (t = M(t, n)), l["content-type"] = "application/octet-stream", m = t;
879
+ else if (Y(r)) {
880
+ let t = Z(p(process.cwd(), r)), n = X(e);
881
+ n && (t = N(t, n)), l["content-type"] = "application/octet-stream", m = t;
852
882
  } 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) });
853
883
  if (i === "replace") {
854
884
  let i = await ot(r, n);
855
885
  {
856
- let r = i === null ? { bundleKey: n } : { tables: i }, a = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, JSON.stringify(r), {
886
+ let r = i === null ? { bundleKey: n } : { tables: i }, a = await L(`${t.replace(/\/+$/, "")}/_voltro/admin/import`, JSON.stringify(r), {
857
887
  ...l,
858
888
  "content-type": "application/json",
859
889
  "x-import-preflight": "1"
860
- }, Z(e)).catch(() => null), o = a !== null && a.status === 200 ? JSON.parse(a.body.toString("utf8")) : null;
890
+ }, $(e)).catch(() => null), o = a !== null && a.status === 200 ? JSON.parse(a.body.toString("utf8")) : null;
861
891
  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
862
892
  interrupted run leaves the target empty.\n Reason: ${o.reason ?? "not stated"}`);
863
893
  }
864
894
  }
865
- i === "replace" && !X(e, i) && V.warn(vt(!0));
895
+ i === "replace" && !Q(e, i) && V.warn(vt(!0));
866
896
  let h = `${t.replace(/\/+$/, "")}/_voltro/admin/import`, g = st(e), _;
867
897
  try {
868
898
  let t = typeof m == "string" ? null : await _t({
@@ -872,23 +902,23 @@ var Fe = /* @__PURE__ */ new Set([
872
902
  bundleDir: r,
873
903
  args: e,
874
904
  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;
905
+ }), n = t === null ? await L(h, m, l, g.perRequest ?? $(e)) : t.kind === "sent" ? t.res : await L(h, t.body, l, g.perRequest ?? $(e));
906
+ if (n.status < 200 || n.status >= 300) return Dt(Ot(n.body)) || V.error(`api import failed: HTTP ${n.status} — ${n.body.toString("utf8")}`), 1;
877
907
  _ = JSON.parse(n.body.toString("utf8"));
878
908
  } catch (e) {
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;
909
+ if (e instanceof Fe) return V.error(`api import: ${Le(e, i)}`), 1;
910
+ if (e instanceof I) return V.error(`api import: ${Ie(e)}`), 1;
881
911
  throw e;
882
912
  }
883
- let v = await I(`${t.replace(/\/+$/, "")}/_voltro/admin/import/start`, JSON.stringify(_.bundleKey === void 0 ? { uploadId: _.uploadId } : { bundleKey: _.bundleKey }), {
913
+ let v = await L(`${t.replace(/\/+$/, "")}/_voltro/admin/import/start`, JSON.stringify(_.bundleKey === void 0 ? { uploadId: _.uploadId } : { bundleKey: _.bundleKey }), {
884
914
  ...l,
885
915
  "content-type": "application/json"
886
- }, g.perRequest ?? Z(e)).catch((e) => {
887
- if (e instanceof P) return V.error(`api import: ${L(e)}`), null;
916
+ }, g.perRequest ?? $(e)).catch((e) => {
917
+ if (e instanceof I) return V.error(`api import: ${Ie(e)}`), null;
888
918
  throw e;
889
919
  });
890
920
  if (v === null) return 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;
921
+ if (v.status < 200 || v.status >= 300) return Dt(Ot(v.body)) || V.error(`api import refused: HTTP ${v.status} — ${v.body.toString("utf8")}`), 1;
892
922
  let y = JSON.parse(v.body.toString("utf8"));
893
923
  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
924
  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;
@@ -914,20 +944,20 @@ var Fe = /* @__PURE__ */ new Set([
914
944
  }), 0);
915
945
  }
916
946
  if (!r) return V.error("a <bundleDir> is required for a direct import"), 1;
917
- if (!await Qe(e)) return 1;
918
- i === "replace" && e.includes("--allow-live") && !X(e, i) && V.warn(vt(!1));
919
- let { store: s, snapshot: c } = await K();
947
+ if (!await et(e)) return 1;
948
+ i === "replace" && e.includes("--allow-live") && !Q(e, i) && V.warn(vt(!1));
949
+ let { store: s, snapshot: c } = await q();
920
950
  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;
951
+ let l = e.includes("--force"), p = (e) => V.warn(e), m = e.includes("--assets"), h = m ? k() : void 0;
922
952
  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
953
  let g = G() === "postgres" ? (await import("@voltro/sql-postgres")).makePgCopySession(o()) : void 0;
924
- if (q(r)) {
954
+ if (Y(r)) {
925
955
  let t = await import("node:os"), n = await import("node:fs/promises"), { resolve: o, join: u } = await import("node:path"), d = await n.mkdtemp(u(t.tmpdir(), "voltro-import-"));
926
956
  try {
927
- let t = Y(o(process.cwd(), r)), n = J(e);
928
- return n && (t = M(t, n)), await je(t, {
957
+ let t = Z(o(process.cwd(), r)), n = X(e);
958
+ return n && (t = N(t, n)), await Ae(t, {
929
959
  dir: d,
930
- blobs: ke(h ?? de())
960
+ blobs: Oe(h ?? ue())
931
961
  }), Tt(e, {
932
962
  file: r,
933
963
  ...wt(await D.runPromise(Te({
@@ -935,7 +965,7 @@ var Fe = /* @__PURE__ */ new Set([
935
965
  bundleDir: d,
936
966
  mode: i,
937
967
  onConflict: a,
938
- atomic: X(e, i),
968
+ atomic: Q(e, i),
939
969
  ...bt(e, i, r),
940
970
  verify: !e.includes("--no-verify"),
941
971
  targetDialect: G(),
@@ -962,7 +992,7 @@ var Fe = /* @__PURE__ */ new Set([
962
992
  bundleDir: r,
963
993
  mode: i,
964
994
  onConflict: a,
965
- atomic: X(e, i),
995
+ atomic: Q(e, i),
966
996
  verify: !e.includes("--no-verify"),
967
997
  targetDialect: G(),
968
998
  allowIncompatible: l,
@@ -971,10 +1001,10 @@ var Fe = /* @__PURE__ */ new Set([
971
1001
  onWarn: p,
972
1002
  onProgress: H,
973
1003
  ...Ct(e),
974
- ...h ? { assets: De(h) } : {},
1004
+ ...h ? { assets: Ee(h) } : {},
975
1005
  ...g ? { copyLoader: g } : {}
976
1006
  })).catch((e) => {
977
- if (Q(e)) return Et;
1007
+ if (Dt(e)) return Et;
978
1008
  throw e;
979
1009
  }).finally(() => g?.close());
980
1010
  return typeof _ == "symbol" ? 1 : (Tt(e, {
@@ -982,7 +1012,7 @@ var Fe = /* @__PURE__ */ new Set([
982
1012
  ...wt(_),
983
1013
  assets: _.manifest.assets.count
984
1014
  }), 0);
985
- }, kt = () => {
1015
+ }, At = () => {
986
1016
  let e = o();
987
1017
  return {
988
1018
  ...e.url ? { url: e.url } : {},
@@ -992,23 +1022,24 @@ var Fe = /* @__PURE__ */ new Set([
992
1022
  ...e.password ? { password: e.password } : {},
993
1023
  ...e.database ? { database: e.database } : {}
994
1024
  };
995
- }, At = async (e) => {
1025
+ }, jt = async (e) => {
996
1026
  let t = W(e);
997
1027
  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;
1028
+ let n = G(), r = e.includes("--assets"), { mkdir: i, writeFile: a } = await import("node:fs/promises"), { join: o } = await import("node:path"), s = await Nt(), c = r ? k() : void 0;
999
1029
  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;
1000
1030
  await i(t, { recursive: !0 });
1001
- let l = o(t, j(n)), u = Pt(s), d = Ft(s), f = u ? await We(u, {
1031
+ let l = o(t, M(n)), u = Ft(s), d = It(s), f = u ? await Ge(u, {
1002
1032
  direction: "backup",
1003
1033
  dialect: n,
1004
1034
  artifact: l,
1005
1035
  assets: r
1006
- }) : void 0, p = await Ke(Ee(ge(n, kt(), l)));
1036
+ }) : void 0, p = await qe(F(he(n, At(), l)));
1007
1037
  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({
1038
+ let m = await Qe(n), h = await Xe(), g = (/* @__PURE__ */ new Date()).toISOString(), _ = async (e) => {
1039
+ let r = He({
1010
1040
  dialect: n,
1011
- schemaFingerprint: m,
1041
+ schemaFingerprint: m.live,
1042
+ dumpFingerprint: m.dump,
1012
1043
  voltroVersion: h,
1013
1044
  createdAt: g,
1014
1045
  assets: e
@@ -1016,10 +1047,10 @@ var Fe = /* @__PURE__ */ new Set([
1016
1047
  return await a(o(t, R), `${JSON.stringify(r, null, 2)}\n`, "utf8"), r;
1017
1048
  }, v = 0, y = 0, b = [], x = 0, S = 0, C = 0;
1018
1049
  if (c && u) {
1019
- let e = o(t, k);
1050
+ let e = o(t, A);
1020
1051
  await i(e, { recursive: !0 });
1021
- let n = o(t, A), r = u, a = await D.runPromiseExit(_e({
1022
- source: Oe(c, () => rt(r)),
1052
+ let n = o(t, j), r = u, a = await D.runPromiseExit(ge({
1053
+ source: De(c, () => J(r)),
1023
1054
  assetsDir: e,
1024
1055
  progress: {
1025
1056
  done: () => !1,
@@ -1027,7 +1058,7 @@ var Fe = /* @__PURE__ */ new Set([
1027
1058
  try: async () => {
1028
1059
  await (await import("node:fs/promises")).appendFile(n, `${JSON.stringify(t)}\n`);
1029
1060
  },
1030
- catch: (e) => new pe({
1061
+ catch: (e) => new fe({
1031
1062
  reason: `asset index append failed: ${String(e)}`,
1032
1063
  path: n
1033
1064
  })
@@ -1054,17 +1085,17 @@ var Fe = /* @__PURE__ */ new Set([
1054
1085
  artifact: l,
1055
1086
  stamp: R,
1056
1087
  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)
1088
+ assets: r ? `${v} key(s) → ${x} object(s), ${S} byte(s) under ${A}/ (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`",
1089
+ recorded: Mt(f, d)
1059
1090
  }), 0;
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 () => {
1091
+ }, Mt = (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}`, Nt = async () => {
1061
1092
  let e;
1062
1093
  try {
1063
1094
  e = await i(process.cwd());
1064
1095
  } catch (e) {
1065
1096
  return {
1066
1097
  unavailable: "store-unopenable",
1067
- reason: `app.config.ts could not be loaded: ${Nt(e)}`
1098
+ reason: `app.config.ts could not be loaded: ${Pt(e)}`
1068
1099
  };
1069
1100
  }
1070
1101
  if (!e) return { unavailable: "no-app-config" };
@@ -1073,61 +1104,77 @@ var Fe = /* @__PURE__ */ new Set([
1073
1104
  } catch (e) {
1074
1105
  return {
1075
1106
  unavailable: "store-unopenable",
1076
- reason: Nt(e)
1107
+ reason: Pt(e)
1077
1108
  };
1078
1109
  }
1079
- }, Nt = (e) => {
1110
+ }, Pt = (e) => {
1080
1111
  let t = e?.message, n = typeof t == "string" && t.trim() !== "" ? t : String(e);
1081
1112
  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({
1113
+ }, Ft = (e) => "store" in e ? e.store : void 0, It = (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, Lt = "_voltro_migration_plans", Rt = async (e, t, n) => {
1114
+ if (!n.has(Lt)) return { kind: "no-table" };
1115
+ try {
1116
+ let n = (await l(e)).makeSqlLayer({ url: t }), r = (await D.runPromise(oe.SqlClient.pipe(D.flatMap((e) => e`
1117
+ SELECT ${e("fingerprint")} FROM ${e(Lt)}
1118
+ ORDER BY ${e("appliedAt")} DESC
1119
+ `), D.provide(n))))[0];
1120
+ return r === void 0 ? { kind: "empty" } : {
1121
+ kind: "recorded",
1122
+ fingerprint: r.fingerprint
1123
+ };
1124
+ } catch {
1125
+ return { kind: "no-table" };
1126
+ }
1127
+ }, zt = async (e, t) => {
1128
+ let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(e, M(n)), o = Je({
1084
1129
  flagUrl: U(t, "--drill-url"),
1085
1130
  envUrl: process.env.DRILL_DB_URL,
1086
- liveKeys: nt()
1131
+ liveKeys: rt()
1087
1132
  });
1088
1133
  if (!o.ok) return V.error(`restore --drill: ${o.error}`), 1;
1089
- let s = await i(r(e, R), "utf8").then(He, () => null);
1134
+ let s = await i(r(e, R), "utf8").then(Ue, () => null);
1090
1135
  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
1136
  V.info("restore drill: restoring into throwaway DB", {
1092
1137
  dialect: n,
1093
1138
  target: B(o.url)
1094
- }), await D.runPromise(Ee(Ce(n, { url: o.url }, a)));
1095
- let c = null, l = 0;
1139
+ }), await D.runPromise(F(Ce(n, { url: o.url }, a)));
1140
+ let c = null, l = 0, u = /* @__PURE__ */ new Set();
1096
1141
  try {
1097
- let e = await et(n, { url: o.url });
1098
- c = ce(e), l = e.tables.length;
1142
+ let e = await nt(n, { url: o.url });
1143
+ c = O(e), l = e.tables.length, u = new Set(e.tables.map((e) => e.name));
1099
1144
  } catch (e) {
1100
1145
  return V.error(`restore --drill: could not introspect the restored DB — ${String(e)}`), 1;
1101
1146
  }
1102
- let u = Je({
1103
- stampFingerprint: s?.schemaFingerprint ?? null,
1147
+ let d = Ye({
1148
+ dumpFingerprint: s?.dumpFingerprint ?? null,
1149
+ liveFingerprintAtBackup: s?.schemaFingerprint ?? null,
1104
1150
  restoredFingerprint: c,
1105
- restoredTableCount: l
1151
+ restoredTableCount: l,
1152
+ ledger: await Rt(n, o.url, u)
1106
1153
  });
1107
- for (let e of u.lines) u.pass ? V.info(`restore drill: ${e}`) : V.error(`restore drill: ${e}`);
1108
- return +!u.pass;
1109
- }, Lt = async (e) => {
1154
+ for (let e of d.lines) d.pass ? V.info(`restore drill: ${e}`) : V.error(`restore drill: ${e}`);
1155
+ return +!d.pass;
1156
+ }, Bt = async (e) => {
1110
1157
  let t = W(e);
1111
1158
  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);
1113
- if (!await Qe(e)) return 1;
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),
1159
+ if (e.includes("--drill")) return zt(t, e);
1160
+ if (!await et(e)) return 1;
1161
+ let n = G(), { join: r } = await import("node:path"), { readFile: i } = await import("node:fs/promises"), a = r(t, M(n)), o = e.includes("--assets"), s = We({
1162
+ stamp: await i(r(t, R), "utf8").then(Ue, () => null),
1116
1163
  currentDialect: n,
1117
- targetFingerprint: await Xe(n)
1164
+ targetFingerprint: await Ze(n)
1118
1165
  });
1119
1166
  for (let e of s.info) V.info(`backup stamp: ${e}`);
1120
1167
  if (s.warn && V.warn(s.warn), s.refuse) {
1121
1168
  if (!e.includes("--force")) return V.error(`restore refused: ${s.refuse} (pass --force to override)`), 1;
1122
1169
  V.warn(`restore proceeding despite: ${s.refuse} (--force)`);
1123
1170
  }
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
1171
+ let c = r(t, j), l = await i(c, "utf8").then(() => !0, () => !1);
1172
+ if (o && !l) return V.error(`--assets: this backup carries no assets — there is no ${j} in ${t}.\n It was taken without --assets, so its rows will reference blobs this restore cannot
1126
1173
  put back. Restore from a backup taken with --assets, or drop the flag to accept that.`), 1;
1127
1174
  !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;
1175
+ let u = await Nt(), d = o ? k() : void 0;
1129
1176
  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}`;
1177
+ let f = It(u), p = e.includes("--no-marker"), m = Ft(u), h = m ? await Ke(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
1178
  if (g !== void 0 && !p) return V.error(`refusing to restore: the in-progress marker cannot be written.
1132
1179
  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
1180
  half-loaded database. Without it this restore has no guard at all.
@@ -1136,20 +1183,20 @@ var Fe = /* @__PURE__ */ new Set([
1136
1183
  g !== void 0 && V.warn(`--no-marker: restoring with NO in-progress marker. If this run does not finish, nothing
1137
1184
  will stop the next boot from serving a half-loaded database.
1138
1185
  ${g}`);
1139
- let _ = m ? await We(m, {
1186
+ let _ = m ? await Ge(m, {
1140
1187
  direction: "restore",
1141
1188
  dialect: n,
1142
1189
  artifact: a,
1143
1190
  assets: o
1144
- }) : void 0, v = await Ke(Ee(Ce(n, kt(), a))), y = h === void 0 ? "lost" : await h.reassert();
1191
+ }) : void 0, v = await qe(F(Ce(n, At(), a))), y = h === void 0 ? "lost" : await h.reassert();
1145
1192
  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
1193
  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
1194
  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
1195
  let x = 0, S = 0;
1149
1196
  if (d) {
1150
1197
  let e = await D.runPromiseExit(Se({
1151
- sink: De(d),
1152
- assetsDir: r(t, k),
1198
+ sink: Ee(d),
1199
+ assetsDir: r(t, A),
1153
1200
  indexPath: c
1154
1201
  }));
1155
1202
  if (e._tag === "Failure") {
@@ -1163,9 +1210,9 @@ var Fe = /* @__PURE__ */ new Set([
1163
1210
  dialect: n,
1164
1211
  artifact: a,
1165
1212
  assets: o ? `${x} key(s) restored from ${S} object(s)` : "not restored (no --assets)",
1166
- recorded: jt(_, f)
1213
+ recorded: Mt(_, f)
1167
1214
  }), 0;
1168
- }, Rt = async (e) => {
1215
+ }, Vt = async (e) => {
1169
1216
  let t = W(e);
1170
1217
  if (!t) return V.error("usage: voltro data inspect <file.vbundle|dir> [--json] [--passphrase <p>]"), 1;
1171
1218
  let { resolve: n, join: r } = await import("node:path"), i = await import("node:fs/promises"), a = n(process.cwd(), t);
@@ -1178,38 +1225,63 @@ var Fe = /* @__PURE__ */ new Set([
1178
1225
  let s = await i.readFile(n, "utf8").catch(() => void 0);
1179
1226
  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);
1180
1227
  }
1181
- let o = J(e), s = () => {
1182
- let e = Y(a);
1183
- return o ? M(e, o) : e;
1228
+ let o = X(e), s = () => {
1229
+ let e = Z(a);
1230
+ return o ? N(e, o) : e;
1184
1231
  };
1185
1232
  if (e.includes("--json")) {
1186
- let e = (await N(s(), ["manifest.json"])).get("manifest.json");
1233
+ let e = (await P(s(), ["manifest.json"])).get("manifest.json");
1187
1234
  return e ? (console.log(e.toString("utf8")), 0) : (V.error("no manifest.json found in the bundle"), 1);
1188
1235
  }
1189
- let c = (await N(s(), ["README.md"])).get("README.md");
1236
+ let c = (await P(s(), ["README.md"])).get("README.md");
1190
1237
  if (c) return console.log(c.toString("utf8")), 0;
1191
- let l = (await N(s(), ["manifest.json"])).get("manifest.json");
1238
+ let l = (await P(s(), ["manifest.json"])).get("manifest.json");
1192
1239
  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);
1240
+ }, Ht = async (e) => {
1241
+ let { REPLACE_IN_PROGRESS_TABLE: t } = await import("@voltro/database"), { interruptedReplaceAtBoot: n } = await import("./interruptedReplace-qzmFI020.js"), { store: r } = await q(), i = await n(r);
1195
1242
  if (i === void 0) return V.info("no interrupted replace is recorded — nothing to clear"), 0;
1196
1243
  if (!e.includes("--yes")) return V.error(`${i}\n\n Re-run with --yes to clear it.`), 1;
1197
- let a = await r.query({
1244
+ let a = (await r.query({
1198
1245
  table: t,
1199
1246
  predicate: void 0,
1200
1247
  order: [],
1201
1248
  take: void 0,
1202
1249
  skip: void 0,
1203
1250
  projection: void 0
1204
- });
1251
+ })).filter((e) => String(e.kind ?? "replace") !== "staging");
1205
1252
  for (let e of a) await r.delete(t, String(e.id));
1206
1253
  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();
1254
+ }, Ut = async (e) => {
1255
+ let { STAGING_PREFIX: t, dropStagingSql: n, execStatements: r } = await import("@voltro/data-transfer"), { store: i } = await q(), a = G();
1209
1256
  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;
1210
1257
  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));
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) => {
1258
+ if (s.length === 0) return V.info("no staging tables are present — nothing to clear"), 0;
1259
+ let { REPLACE_IN_PROGRESS_TABLE: c, stagingNamesOf: l } = await import("@voltro/database"), u = await (async () => {
1260
+ try {
1261
+ return (await i.query({
1262
+ table: c,
1263
+ predicate: void 0,
1264
+ order: [],
1265
+ take: void 0,
1266
+ skip: void 0,
1267
+ projection: void 0
1268
+ })).filter((e) => String(e.kind ?? "") === "staging");
1269
+ } catch {
1270
+ return [];
1271
+ }
1272
+ })(), d = /* @__PURE__ */ new Map();
1273
+ for (let e of u) for (let t of l(e.staging)) d.set(t, e);
1274
+ let f = (e) => {
1275
+ let t = d.get(e);
1276
+ if (t === void 0) return `${e} — no run claims it (an orphan, or from before the marker)`;
1277
+ let n = t.heartbeatAt, r = n == null ? "never" : `${Math.max(0, Math.round((Date.now() - new Date(String(n)).getTime()) / 6e4))} min ago`;
1278
+ return `${e} — ${t.resumable === !0 || t.resumable === 1 ? "RESUMABLE: a `--no-atomic` re-run continues from it" : "from an interrupted run"}, last active ${r}`;
1279
+ };
1280
+ if (!e.includes("--yes")) return V.error(`${s.length} staging table(s) from an earlier \`--mode replace\`:\n` + s.map((e) => ` ${f(e)}`).join("\n") + "\n\n Each holds a copy of what a run was loading. Nothing else removes them.\n A RESUMABLE one is a resume point — dropping it makes the next run reload the whole bundle.\n Re-run with --yes to drop them all."), 1;
1281
+ await r(i, s.map((e) => n(e.slice(t.length), a)));
1282
+ for (let e of u) l(e.staging).some((e) => s.includes(e)) && await i.delete(c, String(e.id)).catch(() => void 0);
1283
+ return V.info(`dropped ${s.length} staging table(s)`), 0;
1284
+ }, Wt = async (e) => {
1213
1285
  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
1286
  if (U(e, "--target") === "api") {
1215
1287
  let t = U(e, "--api-url");
@@ -1226,7 +1298,7 @@ var Fe = /* @__PURE__ */ new Set([
1226
1298
  for (let e of s) V.info(` ${e.line ?? ""}`);
1227
1299
  return 0;
1228
1300
  }
1229
- let { store: a } = await K(), o;
1301
+ let { store: a } = await q(), o;
1230
1302
  try {
1231
1303
  o = await a.query({
1232
1304
  table: n,
@@ -1252,15 +1324,15 @@ var Fe = /* @__PURE__ */ new Set([
1252
1324
  })}`);
1253
1325
  }
1254
1326
  return 0;
1255
- }, Ht = async (e) => {
1327
+ }, Gt = async (e) => {
1256
1328
  let t = W(e), n = W(e.filter((e) => e !== t));
1257
1329
  if (!t || !n) return V.error("usage: voltro data unpack <file.vbundle> <dir> [--passphrase <p>]"), 1;
1258
- let { resolve: r } = await import("node:path"), i = Y(r(process.cwd(), t)), a = J(e);
1259
- return a && (i = M(i, a)), await Me(i, r(process.cwd(), n)), V.info("unpack complete", {
1330
+ let { resolve: r } = await import("node:path"), i = Z(r(process.cwd(), t)), a = X(e);
1331
+ return a && (i = N(i, a)), await je(i, r(process.cwd(), n)), V.info("unpack complete", {
1260
1332
  file: t,
1261
1333
  dir: n
1262
1334
  }), 0;
1263
- }, $ = {
1335
+ }, Kt = {
1264
1336
  export: {
1265
1337
  common: [
1266
1338
  "--target",
@@ -1341,40 +1413,40 @@ var Fe = /* @__PURE__ */ new Set([
1341
1413
  "--drill-url",
1342
1414
  "--api-url"
1343
1415
  ] }
1344
- }, Ut = (e) => e.split("=")[0], Wt = (e, t) => {
1345
- let n = $[e];
1416
+ }, qt = (e) => e.split("=")[0], Jt = (e, t) => {
1417
+ let n = Kt[e];
1346
1418
  if (!n) return;
1347
1419
  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 ?? []);
1348
1420
  for (let n of t) {
1349
1421
  if (!n.startsWith("--")) continue;
1350
- let t = Ut(n);
1422
+ let t = qt(n);
1351
1423
  if (i.has(t)) continue;
1352
1424
  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.`;
1353
- let o = Object.entries($).filter(([n, r]) => n !== e && [
1425
+ let o = Object.entries(Kt).filter(([n, r]) => n !== e && [
1354
1426
  ...r.common,
1355
1427
  ...r.directOnly ?? [],
1356
1428
  ...r.apiOnly ?? []
1357
1429
  ].includes(t)).map(([e]) => e);
1358
1430
  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.`;
1359
1431
  }
1360
- }, Gt = {
1432
+ }, Yt = {
1361
1433
  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) => {
1434
+ import: kt,
1435
+ transfers: Wt,
1436
+ inspect: Vt,
1437
+ unpack: Gt,
1438
+ backup: jt,
1439
+ restore: Bt,
1440
+ "clear-replace-marker": Ht,
1441
+ "clear-staging": Ut
1442
+ }, Xt = async (n) => {
1371
1443
  let [r, ...i] = n;
1372
- if (r !== void 0 && $[r] !== void 0) {
1373
- let e = Wt(r, i);
1444
+ if (r !== void 0 && Kt[r] !== void 0) {
1445
+ let e = Jt(r, i);
1374
1446
  if (e) return V.error(e), 2;
1375
1447
  }
1376
- let a = r === void 0 ? void 0 : Gt[r];
1448
+ let a = r === void 0 ? void 0 : Yt[r];
1377
1449
  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);
1378
1450
  };
1379
1451
  //#endregion
1380
- export { Kt as runDataCommand };
1452
+ export { Xt as runDataCommand };