@voltro/data-transfer 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -39,6 +39,73 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.49.0] — 2026-08-23
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/cli, @voltro/data-transfer, @voltro/database, @voltro/sql-sqlite, @voltro/voltro** — A data transfer is a series of short requests now — no single one may outlive a caller's budget.
47
+
48
+ **The rule:** *a request that carries bytes never runs a transfer; a request that starts a transfer never carries bytes.* It was broken in the worst available place. The upload was already chunked and resumable — many short requests, each abandonable — and then the FINAL chunk fell through and ran the whole import. So the longest request of the flow arrived AFTER the entire upload had succeeded, and a caller under a policy that caps a single request (a job runner that kills a client at ten minutes; a 30 s ingress ceiling) lost the most expensive thing they had already paid for. A single-request upload had the same shape without the excuse, and the export had no protocol at all: one request that read the whole database and streamed it back.
49
+
50
+ **Import.** `POST /_voltro/admin/import` accumulates and answers `202`. `POST /_voltro/admin/import/start` begins the run and answers `202 { runId }` as soon as the run's first row exists. `start` is idempotent per upload — it is a short request and therefore a retryable one, and without a claim a retry would begin a second destructive run from the same bytes. A claim whose run has ENDED is taken over rather than honoured forever, so a process that dies holding one cannot poison a bundle.
51
+
52
+ **Export.** `POST /_voltro/admin/export` answers `202 { runId }` and produces in the background; the bytes come back from `GET /_voltro/admin/export/download?runId=&offset=&length=` in ranges, resumable, with the total in a header. Object storage (`--bundle-key`) remains for a bundle you want to KEEP — it is no longer the only way to get one out, because requiring it would leave an instance without storage unable to export at all.
53
+
54
+ **The client.** `--max-request-seconds` (or `VOLTRO_MAX_REQUEST_SECONDS`) declares the budget — declared rather than probed, because the thing that kills a request is a policy on the caller's side and only they know it. `--detach` returns once the run has started and says the outcome is NOT known. Attached, the CLI polls the record and prints per-table progress; Ctrl-C then loses the watching and never the run.
55
+
56
+ **The trap, stated because it is the one way to get this wrong:** a failure used to arrive in the response (409 on drift, 409 on a refused mode, 500 otherwise). After the split the response is a `202`, so **a client deriving its exit code from the status line reports a failed import as a success.** The exit code comes from the polled record, in one shared function, and the drift check moved into `start` where it can still be a refusal that leaves no history.
57
+
58
+ **Two knobs that were constants.** `VOLTRO_IMPORT_UPLOAD_DIR` and `VOLTRO_EXPORT_ARTIFACT_DIR` move the staging areas off the default temp filesystem — which on a container is frequently a small tmpfs, so an instance simply could not accept a bundle the size a grown database produces, and the failure arrived as a write error halfway through an upload somebody had been waiting on.
59
+
60
+ **Renamed:** `voltro data imports` → `voltro data transfers`, and `_voltro_data_imports` → `_voltro_data_transfers` with a `direction` column. The command showed one direction and now shows both; the table rename carries its rows via the declarative differ on every dialect and needs nothing from you. The CLI rename ships a `manual` codemod — the command lives in scripts, CI jobs and runbooks, which `voltro update` cannot see or rewrite. The four PUBLIC exports that named the same record were renamed with it (`ImportRun`, `describeImportRun`, `IMPORT_RUNS_TABLE`, `_voltroImportRunsTable`); those are application source, they ship their own `transform` codemod, and they have their own entry.
61
+ - **@voltro/database, @voltro/voltro** — The run-history exports say `transfer`, not `import`.
62
+
63
+ `@voltro/database` (and `voltro/database`) renamed four public exports along with the table behind them:
64
+
65
+ | was | is | |---|---| | `ImportRun` | `DataTransferRun` | | `describeImportRun` | `describeTransferRun` | | `IMPORT_RUNS_TABLE` | `DATA_TRANSFERS_TABLE` | | `_voltroImportRunsTable` | `_voltroDataTransfersTable` |
66
+
67
+ An EXPORT writes to this record now — the row carries a `direction` — so every one of those names described half of what it holds.
68
+
69
+ A `transform` codemod rewrites all four, alias-aware, from either module spelling. It also rewrites a hand-spelled `_voltro_data_imports` in a string, template or raw-SQL fragment, and that is the half worth stating: the four identifiers announce themselves as compile errors, while a query that addresses the table by name has nothing to fail on. The table itself moves with its rows via the declarative differ on every dialect.
70
+
71
+ This is filed apart from the transfer-protocol entry beside it deliberately. That one's codemod is `manual` and is about a CLI invocation living in scripts and CI jobs; this one is application source and is rewritten for you. Reading the first as covering both is what would leave a build broken with a note saying nothing in your source was affected.
72
+
73
+ ### Added
74
+
75
+ - **@voltro/database, @voltro/data-transfer, @voltro/cli** — An import records WHETHER it staged, and a soft-dropped column says so in a drift refusal.
76
+
77
+ **`_voltro_data_imports.staged`.** The pre-upload preflight says what the instance WILL do; the run's own line says what it did — and that line is printed inside the instance, which is exactly where an operator using `--target api` cannot read it. So "we were told it would stage" and "it staged" were two claims with no way to close the gap between them from outside. The column is `null` for a mode where the question does not arise; `voltro data imports` and `GET /_voltro/admin/imports` both carry it.
78
+
79
+ **A soft-dropped column is named as one.** `<original>__dropped_<stamp>` is what the differ leaves behind when an app stops declaring a column — only the database that did the drop has it, so it drifts against every target. The refusal reported it as an ordinary missing column ("the value has nowhere to go"), which points at the TARGET's schema: the one place the fix does not lie. It now says where the column lives and how to reclaim it.
80
+
81
+ ### Fixed
82
+
83
+ - **@voltro/data-transfer, @voltro/sql-sqlite, @voltro/database** — A successful `--mode replace --no-atomic` left a marker that refused the next boot.
84
+
85
+ Two defects, one visible symptom, and both were silent by construction.
86
+
87
+ **The clear was gated on the WRITE's condition.** `!(useLedger && ledger.truncated)` means "an earlier attempt already recorded this destructive run, do not write a second row" — correct on the write. Copied down to the clear its meaning inverts: `ledger.truncated` is set by the emptying step of THIS run, so on `--no-atomic` (the only mode where `useLedger` is true) the clear was skipped by the very run that wrote the marker.
88
+
89
+ **And a `Date` in a predicate is not bindable on sqlite.** `better-sqlite3` binds numbers, strings, bigints, buffers and null; the row path has coerced Dates since that store was written, and the eager-join compiler carried its own private copy of the fix, but `query` / `updateMany` / `deleteMany` bound the raw value. So every comparison of a timestamp column against a `Date` failed with `Failed to execute statement` — including the marker's clear, which swallows its errors ON PURPOSE (a store with no marker table must not fail an import over a bookkeeping row) and therefore said nothing. The retention sweep compares `lt(column, cutoff)` the same way.
90
+
91
+ Together: a fully successful replace left the marker standing, and the next boot REFUSED with "a destructive import did not finish" over a database that was completely fine. The one recovery is a command the operator has no reason to think they need. It stayed invisible because `atomic` defaults to true for `replace`, and because a staged replace writes no marker at all — two defaults hiding the one mode that exists for large, interruptible loads.
92
+
93
+ The coercion is shared now and applied at all three predicate sites, with a guard that fails on a fourth that forgets. A dry run also closes its trace row: a preview that finished instantly used to leave the record open, so a polling caller waited out its whole budget over a run that was long done.
94
+ - **@voltro/cli** — `voltro codegen` declared twenty framework tables fewer than a boot.
95
+
96
+ The app half of this was fixed last release and looked like the whole thing. It was not: `codegen` derived the FEATURE MIX from the file list it had just walked, and that list is the entity/relations set — which contains no `*.workflow.tsx`, no `*.agent.ts`, no `*.cron.tsx`. So every feature flag came back false, and the dialect was never passed at all, taking `_voltro_cdc_offsets` with it. Measured on an app with two workflow files: 18 framework tables where the shared assembly produces 33.
97
+
98
+ It calls `assembleFrameworkTables({ root })` now — the same entry `voltro db plan/apply` uses, which detects the mix from the root rather than being handed one.
99
+
100
+ The parity guard moved with it. Comparing the two WALKS could not see this: both walks were right about files, and the divergence was introduced one layer past them. It compares the assembled SETS now, and runs codegen's own table function on a tree whose only feature signals are a workflow file and an agent file — a source assertion that the right function is CALLED cannot see a wrong argument handed to it, and a wrong argument is what this was.
101
+ - **@voltro/cli** — `voltro data --help` advertised four subcommands out of nine.
102
+
103
+ It printed `<export|import|backup|restore>` while the command dispatched those four plus `imports`, `inspect`, `unpack`, `clear-replace-marker` and `clear-staging`. A quoted enumeration is read as exhaustive — the same failure `subcommandNames.ts` was written for after a `db` list cost two wrong conclusions — and the missing entry here is the one that answers "what is my import doing right now" for somebody who cannot reach the pod.
104
+
105
+ `subcommandHelpParity.test.ts` had listed `data` among the commands it could not check, loudly and correctly: there was no name list to check against. There is one now (`DATA_SUBCOMMANDS`), the dispatch is a keyed `Record`, so a subcommand with no name and a name with no handler are both compile errors, and the usage line is generated from the same array. `data` is out of the unchecked list.
106
+
107
+ ---
108
+
42
109
  ## [0.48.0] — 2026-08-22
43
110
 
44
111
  ### Added
package/dist/index.d.ts CHANGED
@@ -366,15 +366,6 @@ export declare const DEFAULT_CLASS_ACTIONS: Readonly<Record<string, MaskAction>>
366
366
  /** Identity helper for authoring a profile with full type-checking + inference. */
367
367
  export declare const defineDataProfile: (profile: DataTransferProfile) => DataTransferProfile;
368
368
 
369
- /**
370
- * Differences that would actually break THIS import, in a stable order.
371
- *
372
- * `bundle` is the shape reconstructed from the manifest, so only its table
373
- * names, column names and column TYPES are meaningful — its `nullable` /
374
- * `hasDefault` are placeholders and are never read here. Every judgement about
375
- * required-ness is therefore made from the TARGET side, which is introspected
376
- * and real.
377
- */
378
369
  export declare const diffSnapshots: (bundle: SchemaSnapshot, target: SchemaSnapshot) => ReadonlyArray<string>;
379
370
 
380
371
  /** A {@link BlobSink} that writes blobs to the LOCAL dir as `assets/<sha>` files
@@ -710,6 +701,19 @@ export declare interface ImportOptions {
710
701
  /** Which transport is running this — named in the refusal a later boot prints,
711
702
  * so the reader knows where to look for logs. */
712
703
  readonly via?: string;
704
+ /**
705
+ * The id of a trace row the CALLER has already written.
706
+ *
707
+ * The admin-import `start` endpoint answers `202` with a run id before the
708
+ * import has done anything, and a caller who polls immediately must find a
709
+ * row rather than an absence — "not started" and "no record" are the two
710
+ * states a poll loop cannot be allowed to confuse. So `start` opens the row
711
+ * itself and hands the id here; this run then ADVANCES and CLOSES that row
712
+ * instead of opening a second one.
713
+ *
714
+ * Absent, the run opens its own row exactly as before.
715
+ */
716
+ readonly traceId?: string;
713
717
  /**
714
718
  * Record that this run emptied the target, so a later boot can refuse over a
715
719
  * database it left half-replaced.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Cause as e, Data as t, Effect as n, Exit as r, Metric as i, MetricBoundaries as a, Runtime as o, Schedule as s, Schema as c, Stream as l } from "effect";
2
- import { IMPORT_RUNS_TABLE as u, REPLACE_IN_PROGRESS_TABLE as d, _voltroImportRunsTable as f, classifyConstraintViolation as p, derivedRowId as m, ensureTableRegistered as h, eq as g, extractDbCause as _, getTable as v, inSet as y, isNotNull as b, or as x, recordsTable as S, registerTable as C, streamTable as w, suspendWriteRecorders as ee, suspendWriteRecordersEffect as te, unregisterTable as ne } from "@voltro/database";
2
+ import { DATA_TRANSFERS_TABLE as u, REPLACE_IN_PROGRESS_TABLE as d, _voltroDataTransfersTable as f, classifyConstraintViolation as p, derivedRowId as m, ensureTableRegistered as h, eq as g, extractDbCause as _, getTable as v, inSet as y, isNotNull as b, or as x, recordsTable as S, registerTable as C, streamTable as w, suspendWriteRecorders as ee, suspendWriteRecordersEffect as te, unregisterTable as ne } from "@voltro/database";
3
3
  import { fingerprintSchema as re } from "@voltro/database/sql";
4
- import { createCipheriv as T, createDecipheriv as ie, createHash as E, createHmac as ae, randomBytes as oe, scrypt as se } from "node:crypto";
5
- import { STORAGE_REFS_TABLE as ce, fromNodeReadable as le, getObjectStream as ue, putObjectStream as D, toNodeReadable as O } from "@voltro/plugin-storage";
6
- import * as k from "node:zlib";
7
- import { PassThrough as A, Readable as j } from "node:stream";
4
+ import { createCipheriv as T, createDecipheriv as ie, createHash as E, createHmac as ae, randomBytes as D, scrypt as oe } from "node:crypto";
5
+ import { STORAGE_REFS_TABLE as se, fromNodeReadable as ce, getObjectStream as le, putObjectStream as O, toNodeReadable as k } from "@voltro/plugin-storage";
6
+ import * as A from "node:zlib";
7
+ import { PassThrough as ue, Readable as j } from "node:stream";
8
8
  import { copyFile as de, createReadStream as fe, createWriteStream as M, existsSync as N, unlinkSync as pe } from "node:fs";
9
9
  import { SqlClient as P } from "@effect/sql";
10
10
  import { mkdir as F, readdir as me, stat as he } from "node:fs/promises";
@@ -217,7 +217,7 @@ var xe = (e) => {
217
217
  ["_voltro_seeds", "which seeds ran HERE — a foreign row suppresses seeding the target still needs"],
218
218
  ["_voltro_cdc_offsets", "binlog/WAL positions of the SOURCE server — meaningless and unsafe against another one"],
219
219
  ["_voltro_schedule_claims", "live leases held by THIS deployment's replicas — a foreign row hands out a lease nobody holds"],
220
- ["_voltro_data_imports", "which bundles were imported HERE — a foreign row claims an import this deployment never ran"],
220
+ ["_voltro_data_transfers", "which bundles moved through HERE, in or out — a foreign row claims a transfer this deployment never ran"],
221
221
  ["_voltro_replace_in_progress", "the marker a destructive import HERE left behind — a foreign row refuses a boot over a healthy database"],
222
222
  ["_voltro_wakeups", "pending wakeups addressed to THIS deployment's runners"],
223
223
  ["_voltro_workflow_watermarks", "how far a consumer has read THIS deployment's event journal — a foreign position skips events"],
@@ -416,16 +416,16 @@ var xe = (e) => {
416
416
  };
417
417
  }), rt = (e, t) => ({
418
418
  list: t,
419
- open: (t) => ue(e, t)
419
+ open: (t) => le(e, t)
420
420
  }), it = (e) => ({
421
- write: (t, n, r) => D(e, t, n, r),
421
+ write: (t, n, r) => O(e, t, n, r),
422
422
  has: (t) => e.head(t).pipe(n.map((e) => e !== null))
423
423
  }), at = (e, t) => n.tryPromise({
424
424
  try: async () => {
425
425
  let { createWriteStream: n } = await import("node:fs"), r = await import("node:fs/promises"), { createHash: i, randomUUID: a } = await import("node:crypto"), { pipeline: o } = await import("node:stream/promises"), { Transform: s } = await import("node:stream"), c = await import("node:path"), l = c.join(t, `.tmp-${a()}`), u = i("sha256"), d = 0, f = new s({ transform(e, t, n) {
426
426
  u.update(e), d += e.length, n(null, e);
427
427
  } });
428
- await o(await O(e.stream), f, n(l));
428
+ await o(await k(e.stream), f, n(l));
429
429
  let p = u.digest("hex"), m = c.join(t, p);
430
430
  try {
431
431
  await r.access(m), await r.rm(l, { force: !0 });
@@ -443,7 +443,7 @@ var xe = (e) => {
443
443
  path: t
444
444
  })
445
445
  }), ot = (e, t, r) => n.gen(function* () {
446
- let i = yield* n.promise(() => import("node:path")), { createReadStream: a } = yield* n.promise(() => import("node:fs")), { createHash: o } = yield* n.promise(() => import("node:crypto")), s = i.join(t, r.sha256), c = o("sha256"), u = le("bundle", () => a(s)).pipe(l.tap((e) => n.sync(() => c.update(e))));
446
+ let i = yield* n.promise(() => import("node:path")), { createReadStream: a } = yield* n.promise(() => import("node:fs")), { createHash: o } = yield* n.promise(() => import("node:crypto")), s = i.join(t, r.sha256), c = o("sha256"), u = ce("bundle", () => a(s)).pipe(l.tap((e) => n.sync(() => c.update(e))));
447
447
  yield* e.write(r.key, u, {
448
448
  contentType: r.contentType,
449
449
  totalSize: r.size
@@ -454,7 +454,7 @@ var xe = (e) => {
454
454
  expected: r.sha256,
455
455
  actual: d
456
456
  }));
457
- }), st = () => typeof k.createZstdCompress == "function", ct = (e) => e === "none" ? "none" : e === "gzip" ? "gzip" : st() ? "zstd" : "gzip", lt = (e) => e === "zstd" ? ".zst" : e === "gzip" ? ".gz" : "", ut = (e) => e === "zstd" ? k.createZstdCompress() : e === "gzip" ? k.createGzip() : new A(), dt = (e) => e === "zstd" ? k.createZstdDecompress() : e === "gzip" ? k.createGunzip() : new A(), ft = 1, pt = c.Struct({
457
+ }), st = () => typeof A.createZstdCompress == "function", ct = (e) => e === "none" ? "none" : e === "gzip" ? "gzip" : st() ? "zstd" : "gzip", lt = (e) => e === "zstd" ? ".zst" : e === "gzip" ? ".gz" : "", ut = (e) => e === "zstd" ? A.createZstdCompress() : e === "gzip" ? A.createGzip() : new ue(), dt = (e) => e === "zstd" ? A.createZstdDecompress() : e === "gzip" ? A.createGunzip() : new ue(), ft = 1, pt = c.Struct({
458
458
  name: c.String,
459
459
  file: c.String,
460
460
  rowCount: c.Number,
@@ -1150,7 +1150,7 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1150
1150
  rows: r.tables.reduce((e, t) => e + t.rowCount, 0),
1151
1151
  tables: r.tables.length
1152
1152
  };
1153
- }), Rn = (e) => `rollback capture: ${e.rows} row(s) across ${e.tables} table(s) → ${e.dir}\n If this run does not finish, restore with: voltro data import ${e.dir} --mode replace`, zn = (e, t) => e.tables.find((e) => e.name === t), Bn = (e, t) => new Map((zn(e, t)?.columns ?? []).map((e) => [e.name, e])), Vn = (e, t) => {
1153
+ }), Rn = (e) => `rollback capture: ${e.rows} row(s) across ${e.tables} table(s) → ${e.dir}\n If this run does not finish, restore with: voltro data import ${e.dir} --mode replace`, zn = (e, t) => e.tables.find((e) => e.name === t), Bn = (e, t) => new Map((zn(e, t)?.columns ?? []).map((e) => [e.name, e])), Vn = /__dropped_\d+$/, Hn = (e, t) => {
1154
1154
  let n = [];
1155
1155
  for (let r of [...e.tables].sort((e, t) => e.name.localeCompare(t.name))) {
1156
1156
  if (zn(t, r.name) === void 0) {
@@ -1161,7 +1161,7 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1161
1161
  for (let [t, a] of i) {
1162
1162
  let i = e.get(t);
1163
1163
  if (i === void 0) {
1164
- n.push(`${r.name}.${t}: in the bundle, MISSING from the target — the value has nowhere to go`);
1164
+ n.push(Vn.test(t) ? `${r.name}.${t}: a soft-dropped column, present only in the SOURCE — reclaim it there (voltro db gc-snapshots --before <date>) or re-export without it; the target is not missing anything` : `${r.name}.${t}: in the bundle, MISSING from the target — the value has nowhere to go`);
1165
1165
  continue;
1166
1166
  }
1167
1167
  i.type !== a.type && n.push(`${r.name}.${t}: type '${a.type}' (bundle) vs '${i.type}' (target)`);
@@ -1169,7 +1169,7 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1169
1169
  for (let [t, a] of e) i.has(t) || !a.nullable && !a.hasDefault && n.push(`${r.name}.${t}: the target requires it (NOT NULL, no default) and the bundle carries no value — every row of this table would fail`);
1170
1170
  }
1171
1171
  return n.length > 30 ? [...n.slice(0, 30), `… and ${n.length - 30} more difference(s)`] : n;
1172
- }, Hn = (e, t, n) => {
1172
+ }, Un = (e, t, n) => {
1173
1173
  let r = re(t);
1174
1174
  if (r === e || n === void 0) return {
1175
1175
  drifted: !1,
@@ -1177,14 +1177,14 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1177
1177
  targetFingerprint: r,
1178
1178
  diff: []
1179
1179
  };
1180
- let i = Vn(n, t);
1180
+ let i = Hn(n, t);
1181
1181
  return {
1182
1182
  drifted: i.length > 0,
1183
1183
  bundleFingerprint: e,
1184
1184
  targetFingerprint: r,
1185
1185
  diff: i
1186
1186
  };
1187
- }, Un = 2e3, Wn = 200, Gn = /* @__PURE__ */ new Set([
1187
+ }, Wn = 2e3, Gn = 200, Kn = /* @__PURE__ */ new Set([
1188
1188
  "BundleError",
1189
1189
  "CodecError",
1190
1190
  "IntegrityError",
@@ -1193,17 +1193,17 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1193
1193
  "RowsRefusedError",
1194
1194
  "SchemaDriftError",
1195
1195
  "StorageError"
1196
- ]), Kn = (t) => {
1196
+ ]), qn = (t) => {
1197
1197
  let n = o.isFiberFailure(t) ? [...e.failures(t[o.FiberFailureCauseId]), ...e.defects(t[o.FiberFailureCauseId])] : [t];
1198
1198
  for (let e of n) {
1199
1199
  let t = e?._tag;
1200
- if (typeof t == "string" && Gn.has(t)) return e;
1200
+ if (typeof t == "string" && Kn.has(t)) return e;
1201
1201
  if (o.isFiberFailure(e)) {
1202
- let t = Kn(e);
1202
+ let t = qn(e);
1203
1203
  if (t) return t;
1204
1204
  }
1205
1205
  }
1206
- }, qn = (t) => [...e.failures(t)][0] ?? Error(e.pretty(t)), Jn = (e) => n.tryPromise({
1206
+ }, Jn = (t) => [...e.failures(t)][0] ?? Error(e.pretty(t)), Yn = (e) => n.tryPromise({
1207
1207
  try: async () => {
1208
1208
  let t = await import("node:fs/promises"), n = await import("node:path");
1209
1209
  return vt(await t.readFile(n.join(e, K), "utf8"));
@@ -1212,10 +1212,10 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1212
1212
  reason: `manifest read/parse failed: ${String(e?.message ?? e)}`,
1213
1213
  path: K
1214
1214
  })
1215
- }), Yn = (e) => {
1215
+ }), Xn = (e) => {
1216
1216
  let t;
1217
1217
  return n.gen(function* () {
1218
- let i = yield* n.promise(() => import("node:path")), a = yield* Jn(e.bundleDir);
1218
+ let i = yield* n.promise(() => import("node:path")), a = yield* Yn(e.bundleDir);
1219
1219
  if (a.formatVersion !== 1) return yield* n.fail(new H({ reason: `unsupported bundle formatVersion ${a.formatVersion} (expected 1)` }));
1220
1220
  if (e.tables !== void 0) {
1221
1221
  let t = new Set(a.tables.map((e) => e.name)), r = e.tables.filter((e) => !t.has(e));
@@ -1236,7 +1236,7 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1236
1236
  unique: !1,
1237
1237
  hasDefault: !1
1238
1238
  }))
1239
- })) }, r = Hn(s.source.schemaFingerprint, e.targetSnapshot, t);
1239
+ })) }, r = Un(s.source.schemaFingerprint, e.targetSnapshot, t);
1240
1240
  if (r.drifted) {
1241
1241
  if (!e.force) return yield* n.fail(new Je({
1242
1242
  bundleFingerprint: r.bundleFingerprint,
@@ -1295,7 +1295,12 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1295
1295
  reason: sn(i)
1296
1296
  }));
1297
1297
  }
1298
- if (e.dryRun === !0) return {
1298
+ if (e.dryRun === !0) return e.traceId !== void 0 && (yield* n.promise(() => e.store.update(u, e.traceId, {
1299
+ finishedAt: /* @__PURE__ */ new Date(),
1300
+ tablesWritten: 0,
1301
+ rowsWritten: 0,
1302
+ failure: null
1303
+ }).then(() => void 0, () => void 0))), {
1299
1304
  manifest: s,
1300
1305
  tablesWritten: 0,
1301
1306
  rowsWritten: 0,
@@ -1319,16 +1324,16 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1319
1324
  return p === "append" ? _ === "fail" ? e.insert(i, o) : e.insertIgnore(i, o, { conflictColumns: [a] }) : p === "replace" ? r ? e.upsert(i, o, { conflictColumns: [a] }) : e.insert(i, o) : e.upsert(i, o, { conflictColumns: [a] });
1320
1325
  }, ie = (e, t) => e.savepoint ? e.savepoint(t) : t(), E = 0, ae = (t, n, r) => {
1321
1326
  r.unclassified && (E++, !(E > 3) && e.onWarn?.(`row refused with no driver detail — table '${t.name}', row '${String(n.id ?? "?")}'. The full rendering follows so the LAYER that refused is on the record; it is logged, never returned. ${r.raw}` + (E === 3 ? " (further unclassified refusals are counted, not logged)" : "")));
1322
- }, oe = (e, t, r = !0) => n.tryPromise({
1327
+ }, D = (e, t, r = !0) => n.tryPromise({
1323
1328
  try: e,
1324
1329
  catch: (e) => _n(e)
1325
- }).pipe(n.map(() => !0), n.catchAll((e) => n.sync(() => (r && (t.reason = e.reason, ae(t.entry, t.row, e)), !1)))), se = (e) => {
1330
+ }).pipe(n.map(() => !0), n.catchAll((e) => n.sync(() => (r && (t.reason = e.reason, ae(t.entry, t.row, e)), !1)))), oe = (e) => {
1326
1331
  let t = Object.entries(e.entry.columnTypes).filter(([t, n]) => n === "reference" && e.row[t] !== null && e.row[t] !== void 0).map(([e]) => e);
1327
1332
  if (t.length !== 0) return {
1328
1333
  ...e.row,
1329
1334
  ...Object.fromEntries(t.map((e) => [e, null]))
1330
1335
  };
1331
- }, ce = (e) => {
1336
+ }, se = (e) => {
1332
1337
  let t = new Set(e.map((e) => String(e.row[e.entry.primaryKey]))), n = (e) => Object.entries(e.entry.columnTypes).some(([n, r]) => {
1333
1338
  if (r !== "reference") return !1;
1334
1339
  let i = e.row[n];
@@ -1356,31 +1361,31 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1356
1361
  primary: t.primary
1357
1362
  })).sort((e, t) => t.refused - e.refused || e.table.localeCompare(t.table)).slice(0, 100)
1358
1363
  });
1359
- }, ue = (e, t) => n.gen(function* () {
1364
+ }, le = (e, t) => n.gen(function* () {
1360
1365
  let r = t, i = [], a = !0;
1361
1366
  for (; r.length > 0 && a;) {
1362
1367
  a = !1;
1363
1368
  let t = [];
1364
- for (let n of r) (yield* oe(() => ie(e, () => T(e, n.entry, n.row, !0)), n)) ? a = !0 : t.push(n);
1369
+ for (let n of r) (yield* D(() => ie(e, () => T(e, n.entry, n.row, !0)), n)) ? a = !0 : t.push(n);
1365
1370
  if (!a && t.length > 0) for (let n = t.length - 1; n >= 0; n--) {
1366
- let r = t[n], o = se(r);
1367
- o !== void 0 && (yield* oe(() => ie(e, () => T(e, r.entry, o, !0)), r, !1)) && (i.push(r), t.splice(n, 1), a = !0);
1371
+ let r = t[n], o = oe(r);
1372
+ o !== void 0 && (yield* D(() => ie(e, () => T(e, r.entry, o, !0)), r, !1)) && (i.push(r), t.splice(n, 1), a = !0);
1368
1373
  }
1369
1374
  r = t;
1370
1375
  }
1371
- for (let t of i) (yield* oe(() => ie(e, () => e.upsert(t.entry.name, t.row, { conflictColumns: [t.entry.primaryKey] })), t)) || (r = [...r, t]);
1372
- if (r.length > 0) return yield* n.fail(ce(r));
1373
- }), D = (() => {
1376
+ for (let t of i) (yield* D(() => ie(e, () => e.upsert(t.entry.name, t.row, { conflictColumns: [t.entry.primaryKey] })), t)) || (r = [...r, t]);
1377
+ if (r.length > 0) return yield* n.fail(se(r));
1378
+ }), O = (() => {
1374
1379
  let t = e.targetDialect;
1375
1380
  return t === "postgres" || t === "mysql" || t === "mariadb" || t === "sqlite" || t === "mssql" ? t : void 0;
1376
- })(), O = In({
1381
+ })(), k = In({
1377
1382
  mode: p,
1378
1383
  ...e.targetDialect === void 0 ? {} : { targetDialect: e.targetDialect },
1379
1384
  ...e.targetSnapshot === void 0 ? {} : { targetSnapshot: e.targetSnapshot },
1380
1385
  bundleTables: s.tables.map((e) => e.name),
1381
1386
  isSkipped: V,
1382
1387
  canRunSql: xn(e.store)
1383
- }), k = O.tables, A = s.tables.filter((e) => !V(e.name) && S(e.name)).map((e) => e.name), j = O.active, de = !1, M = 0, N = 0, pe, P = 0, F = 0, me = (t, r) => n.gen(function* () {
1388
+ }), A = k.tables, ue = s.tables.filter((e) => !V(e.name) && S(e.name)).map((e) => e.name), j = k.active, de = !1, M = 0, N = 0, pe, P = 0, F = 0, me = (t, r) => n.gen(function* () {
1384
1389
  if (p === "replace" && !j && !(r && x.truncated)) {
1385
1390
  e.recordInterruption === !0 && (yield* n.tryPromise({
1386
1391
  try: () => t.insert(d, {
@@ -1433,7 +1438,7 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1433
1438
  total: m.rowCount
1434
1439
  });
1435
1440
  let v = a.length, b = n.gen(function* () {
1436
- let { createHash: r } = yield* n.promise(() => import("node:crypto")), i = r("sha256"), o = 0, c = le("bundle", () => fe(_).pipe(dt(s.compression))).pipe(l.tap((e) => n.sync(() => i.update(e)))), u = async (e) => {
1441
+ let { createHash: r } = yield* n.promise(() => import("node:crypto")), i = r("sha256"), o = 0, c = ce("bundle", () => fe(_).pipe(dt(s.compression))).pipe(l.tap((e) => n.sync(() => i.update(e)))), u = async (e) => {
1437
1442
  let n = {
1438
1443
  entry: m,
1439
1444
  row: e,
@@ -1533,7 +1538,7 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1533
1538
  checksum: m.checksum
1534
1539
  }, yield* J(y, x)));
1535
1540
  }
1536
- if (a.length > 0 && (yield* ue(t, a)), p === "replace" && !(r && x.truncated) && (yield* n.promise(() => t.deleteMany(d, { where: {
1541
+ if (a.length > 0 && (yield* le(t, a)), p === "replace" && (yield* n.promise(() => t.deleteMany(d, { where: {
1537
1542
  column: "startedAt",
1538
1543
  op: "lte",
1539
1544
  value: ge
@@ -1571,26 +1576,31 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1571
1576
  path: I.dir
1572
1577
  })
1573
1578
  })), e.onWarn?.(Rn(I)));
1574
- let _e = (e) => p === "replace" ? ee(e) : e(), L = (e) => p === "replace" ? te(e) : e, R = m("dimp", `${Date.now()}-${s.source.schemaFingerprint ?? ""}`), ve = !1, ye = (e.targetSnapshot?.tables.some((e) => e.name === u) ?? !1) || v(u) !== void 0;
1575
- ye && h(f), ye && (yield* n.promise(() => e.store.insert(u, {
1579
+ let _e = (e) => p === "replace" ? ee(e) : e(), L = (e) => p === "replace" ? te(e) : e, R = e.traceId ?? m("dimp", `${Date.now()}-${s.source.schemaFingerprint ?? ""}`), ve = e.traceId !== void 0, ye = (e.targetSnapshot?.tables.some((e) => e.name === u) ?? !1) || v(u) !== void 0;
1580
+ ye && h(f), ye && e.traceId === void 0 && (yield* n.promise(() => e.store.insert(u, {
1576
1581
  id: R,
1577
1582
  mode: p,
1578
1583
  via: e.via ?? "direct",
1584
+ direction: "import",
1579
1585
  bundle: e.bundleDir,
1580
1586
  sourceFingerprint: s.source.schemaFingerprint ?? null,
1581
1587
  tablesWritten: 0,
1582
- rowsWritten: 0
1588
+ rowsWritten: 0,
1589
+ staged: p === "replace" ? j : null
1583
1590
  }).then(() => {
1584
1591
  ve = !0;
1585
1592
  }, (t) => {
1586
1593
  e.onWarn?.(`the import trace could not be written (${String(t?.message ?? t)}).\n The import continues — this is a record of what happened, not a safety check. Run
1587
1594
  \`voltro db apply\` so the next one is recorded.`);
1588
- })));
1595
+ }))), e.traceId !== void 0 && ye && (yield* n.promise(() => e.store.update(u, R, {
1596
+ staged: p === "replace" ? j : null,
1597
+ sourceFingerprint: s.source.schemaFingerprint ?? null
1598
+ }).then(() => void 0, () => void 0)));
1589
1599
  let be = 0, xe = !1;
1590
1600
  pe = () => {
1591
1601
  if (!ve || xe) return;
1592
1602
  let t = Date.now();
1593
- t - be < Un || (be = t, e.store.update(u, R, {
1603
+ t - be < Wn || (be = t, e.store.update(u, R, {
1594
1604
  tablesWritten: N,
1595
1605
  rowsWritten: M
1596
1606
  }).then(() => void 0, () => {
@@ -1603,11 +1613,11 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1603
1613
  rowsWritten: M,
1604
1614
  failure: t
1605
1615
  }).then(() => void 0, () => void 0) : Promise.resolve();
1606
- if (t = (e) => Se(e), p === "replace" && !j && O.blocker !== void 0 && e.onWarn?.(Sn(O.blocker)), p === "replace" && A.length > 0 && e.onWarn?.(`write recorders are SUSPENDED for this replace (${A.length} table(s) have one).\n A replace sets a state rather than changing rows, so a per-row history entry would describe
1616
+ if (t = (e) => Se(e), p === "replace" && !j && k.blocker !== void 0 && e.onWarn?.(Sn(k.blocker)), p === "replace" && ue.length > 0 && e.onWarn?.(`write recorders are SUSPENDED for this replace (${ue.length} table(s) have one).\n A replace sets a state rather than changing rows, so a per-row history entry would describe
1607
1617
  something that did not happen — and this path already writes through the raw store, without
1608
1618
  tenant scoping or \`audit()\` stamping, for the same reason.`), j) {
1609
- e.onWarn?.(`staging ${k.length} table(s) before the swap — the target keeps its rows until the load stands.\n The destructive step is one server-side transaction at the end, not the whole load.`);
1610
- let t = e.store, r = k.map((e) => e.name), i = Dn(r, {
1619
+ e.onWarn?.(`staging ${A.length} table(s) before the swap — the target keeps its rows until the load stands.\n The destructive step is one server-side transaction at the end, not the whole load.`);
1620
+ let t = e.store, r = A.map((e) => e.name), i = Dn(r, {
1611
1621
  getTable: (e) => v(e),
1612
1622
  registerTable: (e) => {
1613
1623
  C(e);
@@ -1618,30 +1628,30 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1618
1628
  let i = g ? r : r.filter((e) => !Mt(x, e));
1619
1629
  yield* n.tryPromise({
1620
1630
  try: async () => {
1621
- await jn(t, i.map((e) => Tn(e, D))), await jn(t, i.map((e) => wn(e, D)));
1631
+ await jn(t, i.map((e) => Tn(e, O))), await jn(t, i.map((e) => wn(e, O)));
1622
1632
  },
1623
1633
  catch: (e) => new H({ reason: `could not prepare the staging tables, so the import did not start and the target is untouched: ${String(e?.message ?? e)}` })
1624
1634
  }), yield* L(me(e.store, !g)), !g && x.truncated === !0 ? a = !0 : yield* n.tryPromise({
1625
- try: () => Mn(t, k, D).then(() => {
1635
+ try: () => Mn(t, A, O).then(() => {
1626
1636
  a = !0;
1627
1637
  }),
1628
1638
  catch: (e) => new H({ reason: String(e?.message ?? e) })
1629
1639
  }).pipe(n.catchAll((i) => n.gen(function* () {
1630
- let a = An(yield* n.promise(() => Nn(t, On(e.targetSnapshot, r), D).then((e) => e, () => [])));
1640
+ let a = An(yield* n.promise(() => Nn(t, On(e.targetSnapshot, r), O).then((e) => e, () => [])));
1631
1641
  return yield* n.fail(new H({ reason: a ?? `the swap failed and the target is UNCHANGED: ${i.reason}` }));
1632
1642
  }))), !g && a && x.truncated !== !0 && (x.truncated = !0, yield* J(y, x));
1633
1643
  } finally {
1634
- i.undo(), a || g ? yield* n.promise(() => jn(t, r.map((e) => Tn(e, D))).then(() => void 0, () => void 0)) : e.onWarn?.("the staged rows are KEPT so a re-run can continue from them rather than reload.\n If you are not going to re-run this bundle, drop them: voltro data clear-staging --yes");
1644
+ i.undo(), a || g ? yield* n.promise(() => jn(t, r.map((e) => Tn(e, O))).then(() => void 0, () => void 0)) : e.onWarn?.("the staged rows are KEPT so a re-run can continue from them rather than reload.\n If you are not going to re-run this bundle, drop them: voltro data clear-staging --yes");
1635
1645
  }
1636
1646
  } else g ? yield* n.tryPromise({
1637
1647
  try: () => e.store.transactional(async (e) => {
1638
1648
  let t = await _e(() => n.runPromiseExit(me(e, !1)));
1639
- if (r.isFailure(t)) throw qn(t.cause);
1649
+ if (r.isFailure(t)) throw Jn(t.cause);
1640
1650
  }),
1641
- catch: (e) => Kn(e) ?? new H({ reason: `atomic import failed (rolled back): ${String(e?.message ?? e)}` })
1651
+ catch: (e) => qn(e) ?? new H({ reason: `atomic import failed (rolled back): ${String(e?.message ?? e)}` })
1642
1652
  }) : yield* L(me(e.store, !0));
1643
1653
  if (e.assets && s.assets.index && !e.assetsAlreadyRestored) {
1644
- let t = e.assets, r = i.join(e.bundleDir, xt), a = i.join(e.bundleDir, s.assets.index ?? "assets/index.ndjson"), o = le("bundle", () => fe(a)).pipe(l.decodeText(), l.splitLines, l.filter((e) => e.trim().length > 0), l.map((e) => JSON.parse(e)));
1654
+ let t = e.assets, r = i.join(e.bundleDir, xt), a = i.join(e.bundleDir, s.assets.index ?? "assets/index.ndjson"), o = ce("bundle", () => fe(a)).pipe(l.decodeText(), l.splitLines, l.filter((e) => e.trim().length > 0), l.map((e) => JSON.parse(e)));
1645
1655
  yield* l.runForEach(o, (e) => e.key in x.assets ? n.void : ot(t, r, e).pipe(n.andThen(n.sync(() => {
1646
1656
  x.assets[e.key] = !0;
1647
1657
  })), n.andThen(J(y, x))));
@@ -1656,14 +1666,14 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1656
1666
  ...I === void 0 ? {} : { rollback: I }
1657
1667
  };
1658
1668
  }).pipe(n.tapError((e) => n.promise(() => t?.(e?.reason ?? e?._tag ?? "the import did not finish") ?? Promise.resolve())));
1659
- }, Xn = (e) => e, Zn = (e) => {
1669
+ }, Zn = (e) => e, Qn = (e) => {
1660
1670
  if (e.allowLive || e.liveInstance === null || e.sameTarget === "different") return { refuse: !1 };
1661
1671
  let t = e.targetLabel === void 0 ? "" : ` Target: ${e.targetLabel}.`, n = e.sameTarget === "same" ? " That instance is connected to the SAME database." : " We could not confirm which database that instance is connected to, so this is refused on the conservative reading — an older instance, or one whose connection could not be parsed, reports nothing to compare.";
1662
1672
  return {
1663
1673
  refuse: !0,
1664
1674
  message: `refusing to write into a target with a LIVE instance (${e.liveInstance}).${t}${n} A direct import is an uncoordinated concurrent writer — readers see partial state, the reactive layer either storms or goes stale, and rows race with live writes. Re-run with --allow-live to override, or use a live-safe strategy (shadow-swap for replace, or the api target).`
1665
1675
  };
1666
- }, Q = Buffer.from("VBUNDLE2"), Qn = 0, $n = 1, er = 2, tr = 32, nr = async (e) => {
1676
+ }, Q = Buffer.from("VBUNDLE2"), $n = 0, er = 1, tr = 2, nr = 32, rr = async (e) => {
1667
1677
  let t = [], n = async (r) => {
1668
1678
  let i;
1669
1679
  try {
@@ -1677,21 +1687,21 @@ voltro data import <this-directory> --target api --api-url <url> --token <secret
1677
1687
  }
1678
1688
  };
1679
1689
  return await n(""), t.sort();
1680
- }, rr = (e, t) => {
1690
+ }, ir = (e, t) => {
1681
1691
  let n = Buffer.from(e, "utf8"), r = Buffer.alloc(5 + n.length + 8);
1682
- return r.writeUInt8($n, 0), r.writeUInt32BE(n.length, 1), n.copy(r, 5), r.writeBigUInt64BE(BigInt(t), 5 + n.length), r;
1683
- }, ir = (e, t, n) => {
1692
+ return r.writeUInt8(er, 0), r.writeUInt32BE(n.length, 1), n.copy(r, 5), r.writeBigUInt64BE(BigInt(t), 5 + n.length), r;
1693
+ }, ar = (e, t, n) => {
1684
1694
  let r = Buffer.from(e, "utf8"), i = Buffer.from(t, "utf8"), a = Buffer.alloc(5 + r.length + 2 + i.length + 8), o = 0;
1685
- return a.writeUInt8(er, o), o += 1, a.writeUInt32BE(r.length, o), o += 4, r.copy(a, o), o += r.length, a.writeUInt16BE(i.length, o), o += 2, i.copy(a, o), o += i.length, a.writeBigUInt64BE(BigInt(n), o), a;
1695
+ return a.writeUInt8(tr, o), o += 1, a.writeUInt32BE(r.length, o), o += 4, r.copy(a, o), o += r.length, a.writeUInt16BE(i.length, o), o += 2, i.copy(a, o), o += i.length, a.writeBigUInt64BE(BigInt(n), o), a;
1686
1696
  };
1687
- async function* ar(e) {
1697
+ async function* or(e) {
1688
1698
  yield new Uint8Array(Q);
1689
- for (let t of await nr(e.dir)) {
1699
+ for (let t of await rr(e.dir)) {
1690
1700
  let n = L(e.dir, t), r = await he(n);
1691
- if (yield new Uint8Array(rr(t, r.size)), r.size > 0) for await (let e of fe(n)) yield new Uint8Array(e);
1701
+ if (yield new Uint8Array(ir(t, r.size)), r.size > 0) for await (let e of fe(n)) yield new Uint8Array(e);
1692
1702
  }
1693
1703
  if (e.blobs) for await (let t of e.blobs.list()) {
1694
- yield new Uint8Array(ir(t.key, t.contentType, t.size));
1704
+ yield new Uint8Array(ar(t.key, t.contentType, t.size));
1695
1705
  let n = E("sha256"), r = 0;
1696
1706
  for await (let i of e.blobs.open(t.key)) {
1697
1707
  let e = Buffer.from(i);
@@ -1700,9 +1710,9 @@ async function* ar(e) {
1700
1710
  if (r !== t.size) throw Error(`blob '${t.key}' size mismatch: header ${t.size}, streamed ${r}`);
1701
1711
  yield new Uint8Array(n.digest());
1702
1712
  }
1703
- yield new Uint8Array(Buffer.from([Qn]));
1713
+ yield new Uint8Array(Buffer.from([$n]));
1704
1714
  }
1705
- var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr = (e) => new Promise((t) => e.end(t)), cr = async (e, t) => {
1715
+ var sr = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), cr = (e) => new Promise((t) => e.end(t)), lr = async (e, t) => {
1706
1716
  let n = R(t.dir);
1707
1717
  await F(n, { recursive: !0 });
1708
1718
  let r = Buffer.alloc(0), i = e[Symbol.asyncIterator](), a = !1, o = async () => {
@@ -1719,8 +1729,8 @@ var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr
1719
1729
  for (;;) {
1720
1730
  await s(1);
1721
1731
  let e = c(1).readUInt8(0);
1722
- if (e === Qn) return;
1723
- if (e === $n) {
1732
+ if (e === $n) return;
1733
+ if (e === er) {
1724
1734
  await s(4);
1725
1735
  let e = c(4).readUInt32BE(0);
1726
1736
  await s(e);
@@ -1733,10 +1743,10 @@ var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr
1733
1743
  for (; i > 0n;) {
1734
1744
  r.length === 0 && await s(1);
1735
1745
  let e = i < BigInt(r.length) ? Number(i) : r.length;
1736
- await or(o, c(e)), i -= BigInt(e);
1746
+ await sr(o, c(e)), i -= BigInt(e);
1737
1747
  }
1738
- await sr(o);
1739
- } else if (e === er) {
1748
+ await cr(o);
1749
+ } else if (e === tr) {
1740
1750
  await s(4);
1741
1751
  let e = c(4).readUInt32BE(0);
1742
1752
  await s(e);
@@ -1746,24 +1756,24 @@ var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr
1746
1756
  await s(i);
1747
1757
  let a = c(i).toString("utf8");
1748
1758
  await s(8);
1749
- let o = c(8).readBigUInt64BE(0), l = await t.blobs.has(n), u = E("sha256"), d = new A(), f = l ? Promise.resolve() : t.blobs.write(n, d, {
1759
+ let o = c(8).readBigUInt64BE(0), l = await t.blobs.has(n), u = E("sha256"), d = new ue(), f = l ? Promise.resolve() : t.blobs.write(n, d, {
1750
1760
  contentType: a,
1751
1761
  size: Number(o)
1752
1762
  }), p = o;
1753
1763
  for (; p > 0n;) {
1754
1764
  r.length === 0 && await s(1);
1755
1765
  let e = p < BigInt(r.length) ? Number(p) : r.length, t = c(e);
1756
- u.update(t), l || await or(d, t), p -= BigInt(e);
1766
+ u.update(t), l || await sr(d, t), p -= BigInt(e);
1757
1767
  }
1758
- l ? d.destroy() : await sr(d), await f, await s(tr);
1759
- let m = c(tr);
1768
+ l ? d.destroy() : await cr(d), await f, await s(nr);
1769
+ let m = c(nr);
1760
1770
  if (!u.digest().equals(m)) throw Error(`blob '${n}' failed integrity (sha mismatch)`);
1761
1771
  } else throw Error(`unknown bundle entry kind ${e}`);
1762
1772
  }
1763
1773
  } finally {
1764
1774
  a || await Promise.resolve(i.return?.()).catch(() => void 0);
1765
1775
  }
1766
- }, lr = async (e, t) => {
1776
+ }, ur = async (e, t) => {
1767
1777
  let n = new Set(t), r = /* @__PURE__ */ new Map(), i = Buffer.alloc(0), a = e[Symbol.asyncIterator](), o = async () => {
1768
1778
  let { value: e, done: t } = await a.next();
1769
1779
  return !t && (i = i.length === 0 ? Buffer.from(e) : Buffer.concat([i, Buffer.from(e)]), !0);
@@ -1785,8 +1795,8 @@ var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr
1785
1795
  if (!await s(Q.length) || !c(Q.length).equals(Q)) throw Error("not a bundle archive (bad magic)");
1786
1796
  for (; await s(1);) {
1787
1797
  let e = c(1).readUInt8(0);
1788
- if (e === Qn || e === er) break;
1789
- if (e !== $n) throw Error(`unknown bundle entry kind ${e}`);
1798
+ if (e === $n || e === tr) break;
1799
+ if (e !== er) throw Error(`unknown bundle entry kind ${e}`);
1790
1800
  if (!await s(4)) break;
1791
1801
  let t = c(4).readUInt32BE(0);
1792
1802
  if (!await s(t)) break;
@@ -1796,10 +1806,10 @@ var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr
1796
1806
  if (a === null || n.has(i) && (r.set(i, a), n.delete(i), n.size === 0)) break;
1797
1807
  }
1798
1808
  return r;
1799
- }, ur = (e, t) => cr(e, {
1809
+ }, dr = (e, t) => lr(e, {
1800
1810
  dir: t,
1801
- blobs: dr(t)
1802
- }), dr = (e) => {
1811
+ blobs: fr(t)
1812
+ }), fr = (e) => {
1803
1813
  let t = /* @__PURE__ */ new Set();
1804
1814
  return {
1805
1815
  has: async (e) => t.has(e),
@@ -1819,33 +1829,33 @@ var or = (e, t) => new Promise((n, r) => e.write(t, (e) => e ? r(e) : n())), sr
1819
1829
  })}\n`), t.add(n);
1820
1830
  }
1821
1831
  };
1822
- }, fr = ye(se), pr = Buffer.from("VENC1\0\0\0"), mr = 32, hr = 16, gr = 7, _r = 16, vr = 262144, yr = {
1832
+ }, pr = ye(oe), mr = Buffer.from("VENC1\0\0\0"), hr = 32, gr = 16, _r = 7, vr = 16, yr = 262144, br = {
1823
1833
  N: 32768,
1824
1834
  r: 8,
1825
1835
  p: 1
1826
- }, br = 134217728, xr = 43, Sr = (e, t, n) => {
1836
+ }, xr = 134217728, Sr = 43, Cr = (e, t, n) => {
1827
1837
  let r = Buffer.alloc(12);
1828
- return e.copy(r, 0, 0, gr), r.writeUInt32BE(t >>> 0, gr), r.writeUInt8(+!!n, 11), r;
1838
+ return e.copy(r, 0, 0, _r), r.writeUInt32BE(t >>> 0, _r), r.writeUInt8(+!!n, 11), r;
1829
1839
  };
1830
- async function* Cr(e, t) {
1840
+ async function* wr(e, t) {
1831
1841
  if (!t) throw Error("encryptStream: a non-empty passphrase is required");
1832
- let n = oe(hr), r = oe(gr), i = await fr(t, n, mr, {
1833
- ...yr,
1834
- maxmem: br
1835
- }), a = Buffer.alloc(xr);
1836
- pr.copy(a, 0), a.writeUInt32BE(yr.N, 8), a.writeUInt32BE(yr.r, 12), a.writeUInt32BE(yr.p, 16), n.copy(a, 20), r.copy(a, 36), yield new Uint8Array(a);
1842
+ let n = D(gr), r = D(_r), i = await pr(t, n, hr, {
1843
+ ...br,
1844
+ maxmem: xr
1845
+ }), a = Buffer.alloc(Sr);
1846
+ mr.copy(a, 0), a.writeUInt32BE(br.N, 8), a.writeUInt32BE(br.r, 12), a.writeUInt32BE(br.p, 16), n.copy(a, 20), r.copy(a, 36), yield new Uint8Array(a);
1837
1847
  let o = 0, s = (e, t) => {
1838
- let n = T("aes-256-gcm", i, Sr(r, o, t));
1848
+ let n = T("aes-256-gcm", i, Cr(r, o, t));
1839
1849
  o === 0 && n.setAAD(a);
1840
1850
  let s = Buffer.concat([n.update(e), n.final()]), c = n.getAuthTag();
1841
1851
  o++;
1842
- let l = Buffer.alloc(5 + s.length + _r);
1843
- return l.writeUInt8(+!!t, 0), l.writeUInt32BE(s.length + _r, 1), s.copy(l, 5), c.copy(l, 5 + s.length), new Uint8Array(l);
1852
+ let l = Buffer.alloc(5 + s.length + vr);
1853
+ return l.writeUInt8(+!!t, 0), l.writeUInt32BE(s.length + vr, 1), s.copy(l, 5), c.copy(l, 5 + s.length), new Uint8Array(l);
1844
1854
  }, c = Buffer.alloc(0);
1845
- for await (let t of e) for (c = c.length === 0 ? Buffer.from(t) : Buffer.concat([c, Buffer.from(t)]); c.length > vr;) yield s(c.subarray(0, vr), !1), c = c.subarray(vr);
1855
+ for await (let t of e) for (c = c.length === 0 ? Buffer.from(t) : Buffer.concat([c, Buffer.from(t)]); c.length > yr;) yield s(c.subarray(0, yr), !1), c = c.subarray(yr);
1846
1856
  yield s(c, !0);
1847
1857
  }
1848
- async function* wr(e, t) {
1858
+ async function* Tr(e, t) {
1849
1859
  if (!t) throw Error("decryptStream: a non-empty passphrase is required");
1850
1860
  let n = e[Symbol.asyncIterator](), r = Buffer.alloc(0), i = !1, a = async () => {
1851
1861
  let { value: e, done: t } = await n.next();
@@ -1854,16 +1864,16 @@ async function* wr(e, t) {
1854
1864
  for (; r.length < e && !i && await a(););
1855
1865
  return r.length >= e;
1856
1866
  };
1857
- if (!await o(xr)) throw Error("encrypted bundle truncated (no header)");
1858
- let s = r.subarray(0, xr);
1859
- if (!s.subarray(0, 8).equals(pr)) throw Error("not an encrypted bundle (bad magic / wrong passphrase target)");
1860
- let c = s.readUInt32BE(8), l = s.readUInt32BE(12), u = s.readUInt32BE(16), d = Buffer.from(s.subarray(20, 36)), f = Buffer.from(s.subarray(36, xr)), p = await fr(t, d, mr, {
1867
+ if (!await o(Sr)) throw Error("encrypted bundle truncated (no header)");
1868
+ let s = r.subarray(0, Sr);
1869
+ if (!s.subarray(0, 8).equals(mr)) throw Error("not an encrypted bundle (bad magic / wrong passphrase target)");
1870
+ let c = s.readUInt32BE(8), l = s.readUInt32BE(12), u = s.readUInt32BE(16), d = Buffer.from(s.subarray(20, 36)), f = Buffer.from(s.subarray(36, Sr)), p = await pr(t, d, hr, {
1861
1871
  N: c,
1862
1872
  r: l,
1863
1873
  p: u,
1864
- maxmem: br
1874
+ maxmem: xr
1865
1875
  });
1866
- r = r.subarray(xr);
1876
+ r = r.subarray(Sr);
1867
1877
  let m = 0, h = !1;
1868
1878
  for (;;) {
1869
1879
  if (!await o(5)) {
@@ -1872,11 +1882,11 @@ async function* wr(e, t) {
1872
1882
  }
1873
1883
  if (h) throw Error("encrypted bundle has trailing data after the final frame");
1874
1884
  let e = r.readUInt8(0) === 1, t = r.readUInt32BE(1);
1875
- if (t < _r) throw Error("encrypted bundle corrupt (short frame)");
1885
+ if (t < vr) throw Error("encrypted bundle corrupt (short frame)");
1876
1886
  if (!await o(5 + t)) throw Error("encrypted bundle truncated (frame body)");
1877
- let n = r.subarray(5, 5 + t - _r), i = r.subarray(5 + t - _r, 5 + t);
1887
+ let n = r.subarray(5, 5 + t - vr), i = r.subarray(5 + t - vr, 5 + t);
1878
1888
  r = r.subarray(5 + t);
1879
- let a = ie("aes-256-gcm", p, Sr(f, m, e));
1889
+ let a = ie("aes-256-gcm", p, Cr(f, m, e));
1880
1890
  m === 0 && a.setAAD(s), a.setAuthTag(i);
1881
1891
  let c = Buffer.concat([a.update(n), a.final()]);
1882
1892
  m++, e && (h = !0), c.length > 0 && (yield new Uint8Array(c));
@@ -1884,7 +1894,7 @@ async function* wr(e, t) {
1884
1894
  }
1885
1895
  //#endregion
1886
1896
  //#region src/blobStorage.ts
1887
- var Tr = (e, t) => ({
1897
+ var Er = (e, t) => ({
1888
1898
  list: () => {
1889
1899
  let e = t();
1890
1900
  return { async *[Symbol.asyncIterator]() {
@@ -1897,29 +1907,29 @@ var Tr = (e, t) => ({
1897
1907
  } };
1898
1908
  },
1899
1909
  open: (t) => ({ async *[Symbol.asyncIterator]() {
1900
- let r = await n.runPromise(ue(e, t)), i = await O(r.stream);
1910
+ let r = await n.runPromise(le(e, t)), i = await k(r.stream);
1901
1911
  for await (let e of i) yield e instanceof Uint8Array ? e : new Uint8Array(e);
1902
1912
  } })
1903
- }), Er = (e, t) => Tr(e, () => w(t, {
1904
- table: ce,
1913
+ }), Dr = (e, t) => Er(e, () => w(t, {
1914
+ table: se,
1905
1915
  chunkSize: 500
1906
1916
  }).pipe(l.map((e) => ({
1907
1917
  key: String(e.key),
1908
1918
  contentType: String(e.contentType ?? "application/octet-stream"),
1909
1919
  size: Number(e.size ?? 0)
1910
- })))), Dr = (e) => ({
1920
+ })))), Or = (e) => ({
1911
1921
  has: (t) => n.runPromise(e.head(t).pipe(n.map((e) => e !== null))),
1912
1922
  write: async (t, r, i) => {
1913
- let a = le("storage", () => j.from(r));
1914
- await n.runPromise(D(e, t, a, {
1923
+ let a = ce("storage", () => j.from(r));
1924
+ await n.runPromise(O(e, t, a, {
1915
1925
  contentType: i.contentType,
1916
1926
  totalSize: i.size
1917
1927
  }));
1918
1928
  }
1919
- }), Or = (e, t) => {
1929
+ }), kr = (e, t) => {
1920
1930
  let n = t === "dump" ? "mariadb-dump" : "mariadb", r = t === "dump" ? "mysqldump" : "mysql";
1921
- return e === "mariadb" && kr(n) ? n : r;
1922
- }, kr = (e) => (process.env.PATH ?? "").split(I).filter((e) => e !== "").some((t) => N(L(t, e))), Ar = (e) => {
1931
+ return e === "mariadb" && Ar(n) ? n : r;
1932
+ }, Ar = (e) => (process.env.PATH ?? "").split(I).filter((e) => e !== "").some((t) => N(L(t, e))), jr = (e) => {
1923
1933
  switch (e) {
1924
1934
  case "postgres": return "db.dump";
1925
1935
  case "mysql":
@@ -1929,7 +1939,7 @@ var Tr = (e, t) => ({
1929
1939
  case "mssql": return "db.bacpac";
1930
1940
  default: return "db.dump";
1931
1941
  }
1932
- }, jr = (e) => e.filename ?? e.database ?? ((e.url ?? "").replace(/^file:/, "") || "app.sqlite"), $ = (e) => {
1942
+ }, Mr = (e) => e.filename ?? e.database ?? ((e.url ?? "").replace(/^file:/, "") || "app.sqlite"), $ = (e) => {
1933
1943
  if (e.host || e.database) return {
1934
1944
  host: e.host ?? "localhost",
1935
1945
  port: e.port ?? 3306,
@@ -1945,7 +1955,7 @@ var Tr = (e, t) => ({
1945
1955
  password: decodeURIComponent(t.password) || "",
1946
1956
  database: t.pathname.replace(/^\//, "") || ""
1947
1957
  };
1948
- }, Mr = (e) => `Server=${e.host},${e.port};Database=${e.database};User ID=${e.username};Password=${e.password};TrustServerCertificate=True`, Nr = (e, t, n) => {
1958
+ }, Nr = (e) => `Server=${e.host},${e.port};Database=${e.database};User ID=${e.username};Password=${e.password};TrustServerCertificate=True`, Pr = (e, t, n) => {
1949
1959
  switch (e) {
1950
1960
  case "postgres": {
1951
1961
  if (t.url) return {
@@ -1992,7 +2002,7 @@ var Tr = (e, t) => ({
1992
2002
  let r = $(t);
1993
2003
  return {
1994
2004
  kind: "spawn",
1995
- tool: Or(e, "dump"),
2005
+ tool: kr(e, "dump"),
1996
2006
  args: [
1997
2007
  "--single-transaction",
1998
2008
  "--routines",
@@ -2014,7 +2024,7 @@ var Tr = (e, t) => ({
2014
2024
  case "sqlite":
2015
2025
  case "turso": return {
2016
2026
  kind: "copy",
2017
- from: jr(t),
2027
+ from: Mr(t),
2018
2028
  to: n
2019
2029
  };
2020
2030
  case "mssql": return {
@@ -2022,7 +2032,7 @@ var Tr = (e, t) => ({
2022
2032
  tool: "sqlpackage",
2023
2033
  args: [
2024
2034
  "/Action:Export",
2025
- `/SourceConnectionString:${Mr($({
2035
+ `/SourceConnectionString:${Nr($({
2026
2036
  ...t,
2027
2037
  port: t.port ?? 1433
2028
2038
  }))}`,
@@ -2035,7 +2045,7 @@ var Tr = (e, t) => ({
2035
2045
  reason: `no native backup for dialect '${e}'`
2036
2046
  });
2037
2047
  }
2038
- }, Pr = (e, t, n) => {
2048
+ }, Fr = (e, t, n) => {
2039
2049
  switch (e) {
2040
2050
  case "postgres": {
2041
2051
  if (t.url) return {
@@ -2080,7 +2090,7 @@ var Tr = (e, t) => ({
2080
2090
  let r = $(t);
2081
2091
  return {
2082
2092
  kind: "spawn",
2083
- tool: Or(e, "client"),
2093
+ tool: kr(e, "client"),
2084
2094
  args: [
2085
2095
  "--protocol=TCP",
2086
2096
  "-h",
@@ -2099,14 +2109,14 @@ var Tr = (e, t) => ({
2099
2109
  case "turso": return {
2100
2110
  kind: "copy",
2101
2111
  from: n,
2102
- to: jr(t)
2112
+ to: Mr(t)
2103
2113
  };
2104
2114
  case "mssql": return {
2105
2115
  kind: "spawn",
2106
2116
  tool: "sqlpackage",
2107
2117
  args: [
2108
2118
  "/Action:Import",
2109
- `/TargetConnectionString:${Mr($({
2119
+ `/TargetConnectionString:${Nr($({
2110
2120
  ...t,
2111
2121
  port: t.port ?? 1433
2112
2122
  }))}`,
@@ -2119,7 +2129,7 @@ var Tr = (e, t) => ({
2119
2129
  reason: `no native restore for dialect '${e}'`
2120
2130
  });
2121
2131
  }
2122
- }, Fr = (e) => e.kind === "copy" ? n.async((t) => {
2132
+ }, Ir = (e) => e.kind === "copy" ? n.async((t) => {
2123
2133
  de(e.from, e.to, (r) => t(r ? n.fail(new W({
2124
2134
  tool: "copy",
2125
2135
  reason: `copy ${e.from} → ${e.to}: ${r.message}`
@@ -2160,7 +2170,7 @@ var Tr = (e, t) => ({
2160
2170
  stderr: i.slice(-2e3)
2161
2171
  })));
2162
2172
  });
2163
- }), Ir = (e) => typeof e == "object" && e ? "fake" in e ? `fake:${e.fake}` : "custom" : String(e), Lr = (e) => n.gen(function* () {
2173
+ }), Lr = (e) => typeof e == "object" && e ? "fake" in e ? `fake:${e.fake}` : "custom" : String(e), Rr = (e) => n.gen(function* () {
2164
2174
  let t = yield* nt(e.scope ?? { kind: "all" }, e.snapshot, {
2165
2175
  store: e.store,
2166
2176
  ...e.tenantTables ? { tenantTables: e.tenantTables } : {}
@@ -2184,7 +2194,7 @@ var Tr = (e, t) => ({
2184
2194
  let n = e.slice(i.length + 1);
2185
2195
  m.push({
2186
2196
  column: n,
2187
- action: Ir(t),
2197
+ action: Lr(t),
2188
2198
  before: f[n],
2189
2199
  after: p[n]
2190
2200
  });
@@ -2199,7 +2209,7 @@ var Tr = (e, t) => ({
2199
2209
  leaks: c,
2200
2210
  unclassified: o.unclassified
2201
2211
  };
2202
- }), Rr = (e, t) => {
2212
+ }), zr = (e, t) => {
2203
2213
  let n = new Map(t.map((e) => [e.name, e])), r = [], i = {};
2204
2214
  for (let t of e) {
2205
2215
  let e = n.get(t.name);
@@ -2254,7 +2264,7 @@ var Tr = (e, t) => ({
2254
2264
  skipColumns: i,
2255
2265
  refuse: r.some((e) => e.verdict === "refuse")
2256
2266
  };
2257
- }, zr = (e) => {
2267
+ }, Br = (e) => {
2258
2268
  if (e.findings.length === 0) return [" schema matches the bundle"];
2259
2269
  let t = {
2260
2270
  safe: "✓",
@@ -2265,4 +2275,4 @@ var Tr = (e, t) => ({
2265
2275
  return n.push(""), n.push(` discarded: ${r("value-dropped")} defaulted: ${r("filled-by-default")} refused: ${r("refuse")}`), e.refuse && (n.push(""), n.push(" REFUSED — no row was written. These are the differences that would make"), n.push(" imported rows WRONG rather than merely incomplete.")), n;
2266
2276
  };
2267
2277
  //#endregion
2268
- export { xt as ASSETS_DIR, St as ASSET_INDEX_FILE, H as BundleError, We as CodecError, Ke as CompressionError, Ze as CrossDialectError, bt as DATA_DIR, Se as DEFAULT_CLASS_ACTIONS, Be as ENVIRONMENT_LOCAL_TABLES, Ve as ENVIRONMENT_PORTABLE_TABLES, ft as FORMAT_VERSION, Xe as ImportModeError, Ge as IntegrityError, Ct as LEDGER_FILE, K as MANIFEST_FILE, Ye as MaskingError, W as NativeToolError, qe as RowsRefusedError, Wn as SAVEPOINT_BATCH_SIZE, bn as STAGING_PREFIX, Je as SchemaDriftError, U as ScopeError, Pe as applyAction, Kn as asImportError, Zn as assessLiveImport, Nt as assetDone, Ar as backupArtifactName, Nr as backupCommand, cn as canonicalType, dn as checkPortability, Hn as checkSchemaDrift, Ce as classificationFromSnapshot, Rr as classifyImportDrift, lt as compressionExt, $e as computeSubsetIds, In as decideStaging, vt as decodeManifest, Ut as decodeRow, Vt as decodeValue, wr as decryptStream, Xn as defineDataProfile, Vn as diffSnapshots, dr as dirBlobSink, Tn as dropStagingSql, At as emptyLedger, _t as encodeManifest, Ht as encodeRow, zt as encodeValue, Cr as encryptStream, Ue as environmentLocalNotice, He as environmentLocalReason, jn as execStatements, zr as formatImportDrift, fn as formatIssue, st as hasZstd, V as isEnvironmentLocal, ut as makeCompressor, dt as makeDecompressor, ze as maskRow, Kt as ndjsonToRows, sn as outsideReferenceRefusal, on as outsideReferencesInto, ar as packBundle, lr as peekBundle, ct as pickCompression, Ie as planMasking, Lr as previewMasking, jt as readLedger, we as resolveAction, nt as resolveScope, ot as restoreAssetFromCas, Pr as restoreCommand, Gt as rowsToNdjson, an as runExport, Yn as runImport, Fr as runNativeStep, Re as scanForLeaks, X as stagingNameFor, it as storageAssetSink, rt as storageAssetSource, Dr as storageBlobSink, Tr as storageBlobSource, Er as storageRefsBlobSource, et as subsetToScope, Mt as tableDone, qt as topoSortTables, cr as unpackBundle, ur as unpackBundleToDir, at as writeAssetToCas, J as writeLedger };
2278
+ export { xt as ASSETS_DIR, St as ASSET_INDEX_FILE, H as BundleError, We as CodecError, Ke as CompressionError, Ze as CrossDialectError, bt as DATA_DIR, Se as DEFAULT_CLASS_ACTIONS, Be as ENVIRONMENT_LOCAL_TABLES, Ve as ENVIRONMENT_PORTABLE_TABLES, ft as FORMAT_VERSION, Xe as ImportModeError, Ge as IntegrityError, Ct as LEDGER_FILE, K as MANIFEST_FILE, Ye as MaskingError, W as NativeToolError, qe as RowsRefusedError, Gn as SAVEPOINT_BATCH_SIZE, bn as STAGING_PREFIX, Je as SchemaDriftError, U as ScopeError, Pe as applyAction, qn as asImportError, Qn as assessLiveImport, Nt as assetDone, jr as backupArtifactName, Pr as backupCommand, cn as canonicalType, dn as checkPortability, Un as checkSchemaDrift, Ce as classificationFromSnapshot, zr as classifyImportDrift, lt as compressionExt, $e as computeSubsetIds, In as decideStaging, vt as decodeManifest, Ut as decodeRow, Vt as decodeValue, Tr as decryptStream, Zn as defineDataProfile, Hn as diffSnapshots, fr as dirBlobSink, Tn as dropStagingSql, At as emptyLedger, _t as encodeManifest, Ht as encodeRow, zt as encodeValue, wr as encryptStream, Ue as environmentLocalNotice, He as environmentLocalReason, jn as execStatements, Br as formatImportDrift, fn as formatIssue, st as hasZstd, V as isEnvironmentLocal, ut as makeCompressor, dt as makeDecompressor, ze as maskRow, Kt as ndjsonToRows, sn as outsideReferenceRefusal, on as outsideReferencesInto, or as packBundle, ur as peekBundle, ct as pickCompression, Ie as planMasking, Rr as previewMasking, jt as readLedger, we as resolveAction, nt as resolveScope, ot as restoreAssetFromCas, Fr as restoreCommand, Gt as rowsToNdjson, an as runExport, Xn as runImport, Ir as runNativeStep, Re as scanForLeaks, X as stagingNameFor, it as storageAssetSink, rt as storageAssetSource, Or as storageBlobSink, Er as storageBlobSource, Dr as storageRefsBlobSource, et as subsetToScope, Mt as tableDone, qt as topoSortTables, lr as unpackBundle, dr as unpackBundleToDir, at as writeAssetToCas, J as writeLedger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/data-transfer",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Portable export/import for a Voltro app's data + assets — typed-NDJSON row codec, content-addressed assets, framed compression, a resumable chunk ledger, and FK-ordered idempotent import. Dialect-agnostic; the resilient backbone under `voltro data export|import` and native backups.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -33,8 +33,8 @@
33
33
  "node": ">=24.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@voltro/database": "0.48.0",
37
- "@voltro/plugin-storage": "0.48.0"
36
+ "@voltro/database": "0.49.0",
37
+ "@voltro/plugin-storage": "0.49.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "effect": "^3.22.0"