@voltro/data-transfer 0.85.0 → 0.86.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.
@@ -0,0 +1,164 @@
1
+ import { Effect as e } from "effect";
2
+ import { STAGING_TABLE_PREFIX as t } from "@voltro/database";
3
+ import { SqlClient as n } from "effect/sql";
4
+ //#region src/stagedSwap.ts
5
+ var r = t, i = (e) => `${r}${e}`, a = (e) => typeof e.run == "function", o = (e) => `staging is not available for this run — loading directly into the target instead: ${e}\n The load therefore holds the destructive transaction open for its whole duration, so a
6
+ process that dies mid-load depends on the database rolling it back. The rollback capture
7
+ written before the first delete is what covers that case.`, s = /^[a-zA-Z_][a-zA-Z0-9_]*$/, c = (e, t) => {
8
+ if (!s.test(e)) throw Error(`refusing to build SQL for the table name ${JSON.stringify(e)}: it is outside the identifier class the framework allows. A name from a bundle manifest reaches this concatenation, so it is checked here rather than assumed to still be what the declaration validated.`);
9
+ return t === "mysql" || t === "mariadb" ? `\`${e}\`` : t === "mssql" ? `[${e}]` : `"${e}"`;
10
+ }, l = (e, t) => {
11
+ let n = c(e, t), r = c(i(e), t);
12
+ switch (t) {
13
+ case "postgres": return `CREATE TABLE ${r} (LIKE ${n})`;
14
+ case "mysql":
15
+ case "mariadb": return `CREATE TABLE ${r} LIKE ${n}`;
16
+ case "sqlite": return `CREATE TABLE ${r} AS SELECT * FROM ${n} WHERE 0`;
17
+ case "mssql": return `SELECT * INTO ${r} FROM ${n} WHERE 1 = 0`;
18
+ }
19
+ }, u = (e, t) => {
20
+ let n = c(i(e), t);
21
+ return t === "mssql" ? `IF OBJECT_ID('${i(e)}', 'U') IS NOT NULL DROP TABLE ${n}` : `DROP TABLE IF EXISTS ${n}`;
22
+ }, d = (e, t) => {
23
+ let n = t === "mysql" || t === "mariadb", r = [];
24
+ n && r.push("SET FOREIGN_KEY_CHECKS = 0");
25
+ for (let n of [...e].reverse()) r.push(`DELETE FROM ${c(n.name, t)}`);
26
+ for (let n of e) {
27
+ if (n.columns.length === 0) throw Error(`refusing to build a swap for '${n.name}' with no columns: an empty list would have to mean \`SELECT *\`, which is the shape that fails on a generated column.`);
28
+ let e = n.columns.map((e) => c(e, t)).join(", ");
29
+ r.push(`INSERT INTO ${c(n.name, t)} (${e}) SELECT ${e} FROM ${c(i(n.name), t)}`);
30
+ }
31
+ return n && r.push("SET FOREIGN_KEY_CHECKS = 1"), r;
32
+ }, f = (e, t) => {
33
+ let n = [], r = () => {
34
+ for (let e of n) t.unregisterTable(e);
35
+ };
36
+ try {
37
+ for (let r of e) {
38
+ let e = t.getTable(r);
39
+ if (e === void 0) continue;
40
+ let a = i(r);
41
+ t.registerTable({
42
+ ...e,
43
+ tableName: a,
44
+ isReactive: !1
45
+ }), n.push(a);
46
+ }
47
+ } catch (e) {
48
+ throw r(), e;
49
+ }
50
+ return {
51
+ staged: n,
52
+ undo: r
53
+ };
54
+ }, p = (e, t) => {
55
+ let n = new Set(t), r = [];
56
+ for (let t of e.tables) {
57
+ if (!n.has(t.name)) continue;
58
+ let e = t.primaryKey?.[0] ?? "id";
59
+ for (let i of t.columns) {
60
+ let a = i.references;
61
+ a !== void 0 && n.has(a.table) && r.push({
62
+ from: t.name,
63
+ column: i.name,
64
+ to: a.table,
65
+ toColumn: a.column,
66
+ primaryKey: e
67
+ });
68
+ }
69
+ }
70
+ return r;
71
+ }, m = (e, t) => {
72
+ let n = c(i(e.from), t), r = c(i(e.to), t), a = c(e.column, t), o = c(e.toColumn, t);
73
+ return `SELECT ${c(e.primaryKey, t)} AS pk, ${a} AS value FROM ${n} c WHERE ${a} IS NOT NULL AND NOT EXISTS (SELECT 1 FROM ${r} p WHERE p.${o} = c.${a})`;
74
+ }, h = (e) => {
75
+ if (e.length === 0) return;
76
+ let t = e.slice(0, 20), n = t.map((e) => ` ${e.table}.${e.primaryKey}: ${e.column} = ${JSON.stringify(e.value)} — no such row in ${e.references}`);
77
+ return `the swap could not run: ${e.length} row(s) in the bundle reference a row the bundle does not carry.\n${n.join("\n")}\n` + (e.length > t.length ? ` … and ${e.length - t.length} more\n` : "") + " The target is UNCHANGED — the swap runs in one transaction and none of it committed.\n Every offending row is listed above rather than only the first, so this can be fixed in one pass.";
78
+ }, g = async (t, r) => {
79
+ r.length !== 0 && await t.run(n.SqlClient.pipe(e.flatMap((t) => e.forEach(r, (e) => t.unsafe(e), { discard: !0 }))));
80
+ }, _ = async (t, r, i) => {
81
+ let a = d(r, i);
82
+ a.length !== 0 && await t.run(n.SqlClient.pipe(e.flatMap((t) => t.withTransaction(e.forEach(a, (e) => t.unsafe(e), { discard: !0 })))));
83
+ }, v = async (t, r, i) => {
84
+ let a = [];
85
+ for (let o of r) {
86
+ let r = await t.run(n.SqlClient.pipe(e.flatMap((e) => e.unsafe(m(o, i)))));
87
+ for (let e of r) a.push({
88
+ table: o.from,
89
+ primaryKey: String(e.pk ?? ""),
90
+ column: o.column,
91
+ value: e.value,
92
+ references: o.to
93
+ });
94
+ }
95
+ return a;
96
+ }, y = (e, t) => {
97
+ let n = new Map(t.map((e, t) => [e, t]));
98
+ return e.filter((e) => {
99
+ if (e.from === e.to) return !1;
100
+ let t = n.get(e.from), r = n.get(e.to);
101
+ return t === void 0 || r === void 0 ? !1 : r > t;
102
+ });
103
+ }, b = (e) => e === "mysql" || e === "mariadb", x = (e) => {
104
+ if (e.mode !== "replace") return {
105
+ active: !1,
106
+ tables: [],
107
+ blocker: `staging applies to \`replace\` only (this run is \`${e.mode}\`).`
108
+ };
109
+ let t = (() => {
110
+ let t = e.targetDialect;
111
+ return t === "postgres" || t === "mysql" || t === "mariadb" || t === "sqlite" || t === "mssql" ? t : void 0;
112
+ })();
113
+ if (t === void 0) return {
114
+ active: !1,
115
+ tables: [],
116
+ blocker: `the target dialect (${e.targetDialect ?? "unknown"}) is not one this build can stage on.`
117
+ };
118
+ if (!e.canRunSql) return {
119
+ active: !1,
120
+ tables: [],
121
+ blocker: "this store cannot send DDL (no raw-SQL channel), which a staged swap needs."
122
+ };
123
+ let n = e.targetSnapshot;
124
+ if (n === void 0) return {
125
+ active: !1,
126
+ tables: [],
127
+ blocker: "this caller did not supply a snapshot of the target schema, so the staging tables cannot be given the target's column shape."
128
+ };
129
+ let r = new Map(n.tables.map((e) => [e.name, e])), i = e.bundleTables.filter((t) => !e.isSkipped(t)), a = i.filter((e) => !r.has(e));
130
+ if (a.length > 0) return {
131
+ active: !1,
132
+ tables: [],
133
+ blocker: `the bundle carries ${a.length} table(s) the target does not have (${a.join(", ")}). Staging copies the TARGET's column shape, and there is none for those.`
134
+ };
135
+ let o = [];
136
+ for (let e of i) {
137
+ let t = r.get(e).columns.filter((e) => e.generatedAs === void 0).map((e) => e.name);
138
+ if (t.length === 0) return {
139
+ active: !1,
140
+ tables: [],
141
+ blocker: `\`${e}\` has no ordinary columns in the target (every column is generated), so a staged copy would have nothing to write.`
142
+ };
143
+ o.push({
144
+ name: e,
145
+ columns: t
146
+ });
147
+ }
148
+ if (o.length === 0) return {
149
+ active: !1,
150
+ tables: [],
151
+ blocker: "every table in the bundle is one that describes a deployment, so there is nothing for a staged swap to move."
152
+ };
153
+ let s = o.map((e) => e.name), c = b(t) ? [] : y(p(n, s), s);
154
+ return c.length > 0 ? {
155
+ active: !1,
156
+ tables: [],
157
+ blocker: `the bundle's tables reference each other in a cycle (${c.map((e) => `${e.from}.${e.column} → ${e.to}`).join(", ")}), and a bulk copy cannot satisfy both directions on this dialect.`
158
+ } : {
159
+ active: !0,
160
+ tables: o
161
+ };
162
+ };
163
+ //#endregion
164
+ export { r as STAGING_PREFIX, a as canStage, l as createStagingSql, m as danglingProbeSql, h as danglingReport, x as decideStaging, u as dropStagingSql, g as execStatements, v as findDangling, y as forwardReferences, c as quoteIdent, f as registerStagingClones, _ as runSwap, p as stagedReferences, i as stagingNameFor, o as stagingUnavailable, b as suspendsIntegrity, d as swapStatements };
package/dist/subset.js ADDED
@@ -0,0 +1,90 @@
1
+ import { Effect as e, Stream as t } from "effect";
2
+ import { inSet as n, or as r, streamTable as i } from "@voltro/database";
3
+ //#region src/subset.ts
4
+ var a = (e, t) => e.tables.find((e) => e.name === t)?.columns.find((e) => e.type === "id")?.name ?? "id", o = (a, o, s) => e.gen(function* () {
5
+ let c = s.maxDepth ?? 12, l = new Set(s.pinned ?? []), u = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), f = /* @__PURE__ */ new Map(), p = /* @__PURE__ */ new Map();
6
+ for (let e of o.tables) p.has(e.name) || p.set(e.name, e);
7
+ let m = /* @__PURE__ */ new Map();
8
+ for (let [e, t] of p) m.set(e, {
9
+ pk: t.columns.find((e) => e.type === "id")?.name ?? "id",
10
+ fks: t.columns.filter((e) => e.references !== void 0).map((e) => ({
11
+ col: e.name,
12
+ refTable: e.references.table
13
+ }))
14
+ });
15
+ let h = (e) => m.get(e)?.pk ?? "id", g = (e) => {
16
+ let t = d.get(e) ?? /* @__PURE__ */ new Set();
17
+ return d.set(e, t), t;
18
+ }, _ = (e, t) => {
19
+ if (t == null || l.has(e) || d.get(e)?.has(t)) return;
20
+ let n = f.get(e) ?? /* @__PURE__ */ new Set();
21
+ n.add(t), f.set(e, n);
22
+ }, v = (e, t, n) => {
23
+ let r = g(e), i = l.has(e) ? void 0 : u.get(e) ?? (() => {
24
+ let t = /* @__PURE__ */ new Set();
25
+ return u.set(e, t), t;
26
+ })();
27
+ return (e) => {
28
+ r.add(e[t]), i?.add(e[t]);
29
+ for (let t of n) _(t.refTable, e[t.col]);
30
+ };
31
+ }, y = (n, r) => {
32
+ let o = v(n, h(n), m.get(n)?.fks ?? []);
33
+ return t.runForEach(i(a, {
34
+ table: n,
35
+ pk: h(n),
36
+ ...r === void 0 ? {} : { where: r }
37
+ }), (t) => e.sync(() => o(t)));
38
+ }, b = e.gen(function* () {
39
+ for (let e = 0; e < c && f.size > 0; e++) {
40
+ let e = [...f.entries()];
41
+ f.clear();
42
+ for (let [t, r] of e) {
43
+ let e = [...r].filter((e) => !d.get(t)?.has(e));
44
+ if (e.length !== 0) for (let r = 0; r < e.length; r += 500) yield* y(t, n(h(t), e.slice(r, r + 500)));
45
+ }
46
+ }
47
+ });
48
+ for (let [e, t] of Object.entries(s.seeds)) yield* y(e, t);
49
+ if (yield* b, s.children) {
50
+ let t = new Set(s.children === !0 ? [...d.keys()] : s.children.roots.filter((e) => d.has(e))), i = /* @__PURE__ */ new Map();
51
+ for (let a = 0; a < c; a++) {
52
+ let a = !1, s = [];
53
+ for (let c of o.tables) {
54
+ let o = c.name, l = c.columns.map((e) => ({
55
+ col: e.name,
56
+ refTable: e.references?.table
57
+ })).filter((e) => !!e.refTable && t.has(e.refTable) && (d.get(e.refTable)?.size ?? 0) > 0);
58
+ if (l.length === 0) continue;
59
+ let u = d.get(o)?.size ?? 0, f = i.get(o);
60
+ f === void 0 && (f = /* @__PURE__ */ new Map(), i.set(o, f));
61
+ let p = [], m = 0, h = () => {
62
+ let t = p;
63
+ return p = [], m = 0, t.length === 0 ? e.void : y(o, t.length === 1 ? t[0] : r(...t));
64
+ };
65
+ for (let e of l) {
66
+ let t = f.get(e.col);
67
+ t === void 0 && (t = /* @__PURE__ */ new Set(), f.set(e.col, t));
68
+ let r = [...d.get(e.refTable)].filter((e) => !t.has(e));
69
+ for (let t = 0; t < r.length; t += 500) {
70
+ let i = r.slice(t, t + 500);
71
+ m + i.length > 500 && (yield* h()), p.push(n(e.col, i)), m += i.length;
72
+ }
73
+ for (let e of r) t.add(e);
74
+ }
75
+ yield* h(), (d.get(o)?.size ?? 0) > u && !t.has(o) && s.push(o), (d.get(o)?.size ?? 0) > u && (a = !0);
76
+ }
77
+ for (let e of s) t.add(e);
78
+ if (yield* b, !a) break;
79
+ }
80
+ }
81
+ return u;
82
+ }), s = (e, t) => ({
83
+ tables: [...e.keys()],
84
+ predicateFor: (r) => {
85
+ let i = e.get(r);
86
+ return i ? n(a(t, r), [...i]) : void 0;
87
+ }
88
+ });
89
+ //#endregion
90
+ export { o as computeSubsetIds, a as pkOf, s as subsetToScope };
package/dist/topo.js ADDED
@@ -0,0 +1,24 @@
1
+ //#region src/topo.ts
2
+ var e = (e, t) => {
3
+ let n = new Set(e), r = /* @__PURE__ */ new Map();
4
+ for (let e of t.tables) {
5
+ if (!n.has(e.name)) continue;
6
+ let t = /* @__PURE__ */ new Set();
7
+ for (let r of e.columns) {
8
+ let i = r.references?.table;
9
+ i && i !== e.name && n.has(i) && t.add(i);
10
+ }
11
+ r.set(e.name, t);
12
+ }
13
+ let i = [], a = /* @__PURE__ */ new Set(), o = /* @__PURE__ */ new Set(), s = (e) => {
14
+ if (!(a.has(e) || o.has(e))) {
15
+ o.add(e);
16
+ for (let t of r.get(e) ?? []) s(t);
17
+ o.delete(e), a.add(e), i.push(e);
18
+ }
19
+ };
20
+ for (let t of e) n.has(t) && s(t);
21
+ return i;
22
+ };
23
+ //#endregion
24
+ export { e as topoSortTables };
@@ -0,0 +1,36 @@
1
+ //#region src/typedCodec.ts
2
+ var e = /* @__PURE__ */ new Set([
3
+ "json",
4
+ "array",
5
+ "vector"
6
+ ]), t = (e) => typeof e == "object" && !!e && !Array.isArray(e) && typeof e.$v == "string", n = (e) => Buffer.from(e).toString("base64"), r = (e) => new Uint8Array(Buffer.from(e, "base64")), i = (e) => e instanceof Date ? e.toISOString() : typeof e == "number" ? new Date(e).toISOString() : String(e), a = (t, r) => t == null ? null : r !== void 0 && e.has(r) ? t : r === "bytes" ? n(t) : r === "timestamp" || r === "date" ? i(t) : typeof t == "bigint" ? {
7
+ $v: "bigint",
8
+ d: t.toString()
9
+ } : typeof t == "number" && !Number.isFinite(t) ? {
10
+ $v: "number",
11
+ d: String(t)
12
+ } : t instanceof Date ? {
13
+ $v: "date",
14
+ d: t.toISOString()
15
+ } : t instanceof Uint8Array ? {
16
+ $v: "bytes",
17
+ d: n(t)
18
+ } : t, o = (e) => {
19
+ switch (e.$v) {
20
+ case "bigint": return BigInt(e.d);
21
+ case "number": return Number(e.d);
22
+ case "date": return new Date(e.d);
23
+ case "bytes": return r(e.d);
24
+ default: return e;
25
+ }
26
+ }, s = (n, i) => n == null ? null : i !== void 0 && e.has(i) ? n : t(n) ? o(n) : i === "bytes" ? r(n) : i === "timestamp" || i === "date" ? new Date(n) : n, c = (e, t) => {
27
+ let n = {};
28
+ for (let [r, i] of Object.entries(e)) n[r] = a(i, t[r]);
29
+ return n;
30
+ }, l = (e, t) => {
31
+ let n = {};
32
+ for (let [r, i] of Object.entries(e)) n[r] = s(i, t[r]);
33
+ return n;
34
+ };
35
+ //#endregion
36
+ export { l as decodeRow, s as decodeValue, c as encodeRow, a as encodeValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/data-transfer",
3
- "version": "0.85.0",
3
+ "version": "0.86.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.85.0",
37
- "@voltro/plugin-storage": "0.85.0"
36
+ "@voltro/database": "0.86.0",
37
+ "@voltro/plugin-storage": "0.86.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "effect": "4.0.1"