@voltro/sql-sqlite 0.43.2 → 0.44.1
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 +50 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +85 -76
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,56 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.44.1] — 2026-08-19
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **@voltro/database, @voltro/data-transfer, @voltro/cli** — A refused import row reported `(FiberFailure) SqlError: Failed to execute statement` — our runtime's rendering of a rejection, marker and stack frames and all — instead of the constraint that fired. It names neither a rule, nor a code, nor even which layer refused, and every row refused for the same cause carries it identically.
|
|
47
|
+
|
|
48
|
+
Two defects, and fixing either alone still leaves a reader stuck.
|
|
49
|
+
|
|
50
|
+
**`Cause.squash` elects a branch, and first is a position, not a ranking.** A `Cause` is a tree, and a transaction routinely produces one with more than one leaf: the statement that failed, and whatever the rollback or a finalizer did on the way out. When the first leaf was the bare wrapper, the driver error sitting in the sibling branch was never looked at. Measured: `sequential(bareSqlError, sqlErrorWithDriver)` classified as nothing while the same two branches in the opposite order classified as `unique constraint PRIMARY … [ER_DUP_ENTRY/1062]`. `extractDbCause` flattens failures AND defects now, in Cause order, expanding a nested `FiberFailure` leaf, and elects the branch that names a driver — the others' chains are appended rather than dropped.
|
|
51
|
+
|
|
52
|
+
**A row's reason may never read like a stack trace.** The runtime rendering is stripped before any tier looks at the text, so the failure mode cannot return invisibly. And when no driver detail is reachable at all, the reason now names the CHAIN of wrappers the failure passed through — the difference between "the database refused this row" and "the connection died mid-import" — while the run logs the full rendering of the first few such failures. Never returned over the wire: it carries frames, and on some engines a driver's sentence carries row data.
|
|
53
|
+
|
|
54
|
+
`RowsRefusedError` also gained `byTable`: **complete** per-table counts. `rows` is capped at 20, so per-table counts tallied off the printed list sum to the cap rather than to the failure — and nothing else in the payload offered any.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## [0.44.0] — 2026-08-19
|
|
59
|
+
|
|
60
|
+
### ⚠ BREAKING
|
|
61
|
+
|
|
62
|
+
- **@voltro/data-transfer, @voltro/cli** — `runImport` returns an `ImportOutcome` instead of the bundle's `Manifest`, and `--mode replace` runs as ONE transaction by default.
|
|
63
|
+
|
|
64
|
+
**Why the return type changed.** The summary line counted the rows the BUNDLE carries, not the rows the run wrote. Those differ most exactly where it matters: a bundle directory carries its own resume ledger, so a copied directory imports nothing — correctly, with a warning naming the file to delete — and the run then printed `import complete … rows: 242950` over a target it had not touched. The warning was one line above, which is one line too far for anyone piping the output through `tail -1`. The outcome carries `rowsWritten`, `rowsSkipped` and `fullyResumed`, the CLI reports written-vs-carried, and a fully-skipped run says so on its LAST line.
|
|
65
|
+
|
|
66
|
+
Migration: `runImport(...)` now resolves to `{ manifest, tablesWritten, rowsWritten, tablesSkipped, rowsSkipped, fullyResumed }`. Read `.manifest` where you read the manifest before.
|
|
67
|
+
|
|
68
|
+
**Why replace is atomic now.** The all-or-nothing guarantee was written for the emptying step, and read — reasonably — as covering the run. A replace that died partway through the LOAD left the target emptied of its old rows and holding part of the new ones, measured on a live instance over sixteen minutes. There is no useful state for a replace to stop in, so it is a default rather than a flag you have to know about. It also closes the window that produced that failure: with the tables emptied and the load uncommitted, a concurrent writer in the application waits instead of inserting a row the bundle is about to insert too.
|
|
69
|
+
|
|
70
|
+
`--no-atomic` (CLI) / `atomic: false` (API) opts out. The trade is stated where it bites: every write to those tables waits for the load, and on postgres the bulk `COPY` loader cannot join a transaction it does not own — an atomic run now says that once rather than being quietly slower.
|
|
71
|
+
|
|
72
|
+
Two smaller things from the same report: `--mode replace` against a running instance warns about the empty-target window when it is NOT atomic, and the `--target api` upload reports progress per chunk plus a line explaining that the final request stays open for the whole import — sixteen minutes of silence is indistinguishable from a hang, and one operator killed a run that had finished.
|
|
73
|
+
|
|
74
|
+
**`voltro update` carries you across this** — codemod `0.44.0/01_import-outcome`.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **@voltro/sql-mysql, @voltro/sql-postgres, @voltro/sql-sqlite, @voltro/sql-mssql, @voltro/data-transfer** — A write and its write-recorders now succeed or fail together on every SQL dialect, and the data importer's retry is idempotent.
|
|
79
|
+
|
|
80
|
+
A recorder (a versioning trail, an audit log) runs on the caller's connection and is ALLOWED to fail — a recorder that throws must take the write down with it, that is its contract. Outside a caller transaction the two were not one unit: the row's statement committed on its own, and the recorder's INSERT ran afterwards as a second autocommit statement. So a recorder that threw left a COMMITTED row behind a write that reported failure. Measured directly: `insert` throws, the row is in the table, and a second attempt at the same row is `ER_DUP_ENTRY` on PRIMARY.
|
|
81
|
+
|
|
82
|
+
Anything that retries a failed write then meets its own row. The data importer retries by design — it holds a row whose write failed and tries again once the remaining tables have streamed — so a `--mode replace` that had just emptied a table failed on a duplicate key IN that table. From outside, that is the impossible-looking thing: an import that emptied a table and then failed because something was already in it. Reproduced verbatim, including the table name and `[ER_DUP_ENTRY/1062]`, by putting the old retry back.
|
|
83
|
+
|
|
84
|
+
Both ends are closed, and they are independent on purpose:
|
|
85
|
+
|
|
86
|
+
- **The cause.** A table that HAS recorders writes inside a transaction now, so the row and the trail commit together or not at all — on mysql, mariadb, postgres, sqlite and mssql, verified by one suite that asks all five the same question. A table with no recorders — the default — takes the direct path unchanged; `recordsTable` is a Map-size check first, so it costs one comparison. - **The defence.** The importer's retry upserts instead of inserting in `replace` mode. That covers every OTHER way a write can land while reporting failure: a driver timeout on a write the server applied, a connection lost after the commit, a concurrent writer inserting the same key. It is sound precisely because the table was emptied by this same run — there is nothing in it that is not ours.
|
|
87
|
+
|
|
88
|
+
The test that pinned the divergence went red when the fix landed, exactly as its own note said it would, and is inverted with that note kept.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
42
92
|
## [0.43.2] — 2026-08-18
|
|
43
93
|
|
|
44
94
|
### Fixed
|
package/dist/index.d.ts
CHANGED
|
@@ -136,6 +136,16 @@ export declare class SqliteStore implements DataStore {
|
|
|
136
136
|
* ask the question at all.
|
|
137
137
|
*/
|
|
138
138
|
private localWrite;
|
|
139
|
+
/**
|
|
140
|
+
* A write and its write-recorders, as ONE unit — see `MysqlStore.
|
|
141
|
+
* writeWithRecorders` for the failure this closes.
|
|
142
|
+
*
|
|
143
|
+
* The same divergence exists here even though sqlite is one process: the row's
|
|
144
|
+
* statement commits on its own and the recorder's INSERT is a second one, so a
|
|
145
|
+
* recorder that throws leaves a committed row behind a write that reported
|
|
146
|
+
* failure — and a caller that retries meets its own row.
|
|
147
|
+
*/
|
|
148
|
+
private writeWithRecorders;
|
|
139
149
|
insert(table: string, row: Row): Promise<Row>;
|
|
140
150
|
insertMany(table: string, rows: ReadonlyArray<Row>): Promise<ReadonlyArray<Row>>;
|
|
141
151
|
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<Row | null>;
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { SqliteClient as e } from "@effect/sql-sqlite-node";
|
|
2
|
-
import { Config as t, Context as n, Effect as r, Layer as i, ManagedRuntime as a } from "effect";
|
|
3
|
-
import { EventEmitter as
|
|
4
|
-
import { createLogger as
|
|
5
|
-
import { SqlClient as
|
|
6
|
-
import { EagerCardinalityError as
|
|
2
|
+
import { Config as t, Context as n, Effect as r, Layer as i, ManagedRuntime as a, Option as o } from "effect";
|
|
3
|
+
import { EventEmitter as s } from "node:events";
|
|
4
|
+
import { createLogger as c } from "@voltro/logger";
|
|
5
|
+
import { SqlClient as l, TransactionConnection as u } from "@effect/sql/SqlClient";
|
|
6
|
+
import { EagerCardinalityError as d, attachEagerLoads as f, attributionFields as p, bulkInsertLimitsFor as m, chunkRowsForInsert as h, compileEagerJson as g, compilePredicate as _, compileRawFragment as v, compileSelect as y, decodeRowsFromSchema as b, externalChangeEvent as x, hasEagerLoads as S, isTableReactive as C, makeEagerFallbackReporter as w, observeDbOp as T, qualifyTable as E, quoteIdent as D, raiseChangeListenerCeiling as O, recordsTable as k, requireTable as A, runStoreTransaction as j, runWriteRecorders as M, stampGeneratedId as N, stampGeneratedIds as P, withCapturedAttribution as F } from "@voltro/database";
|
|
7
7
|
//#region src/sqlLayer.ts
|
|
8
|
-
var
|
|
8
|
+
var I = (r) => e.layerConfig({
|
|
9
9
|
filename: t.succeed(r.filename),
|
|
10
10
|
...r.readonly === void 0 ? {} : { readonly: t.succeed(r.readonly) }
|
|
11
|
-
}).pipe(i.tap((t) => n.get(t, e.SqliteClient)`PRAGMA foreign_keys = ON`)),
|
|
11
|
+
}).pipe(i.tap((t) => n.get(t, e.SqliteClient)`PRAGMA foreign_keys = ON`)), L = (e) => {
|
|
12
12
|
if (e.url) {
|
|
13
13
|
if (e.url === ":memory:") return { filename: ":memory:" };
|
|
14
14
|
if (e.url.startsWith("file:")) return { filename: e.url.slice(5) };
|
|
@@ -16,12 +16,12 @@ var F = (r) => e.layerConfig({
|
|
|
16
16
|
}
|
|
17
17
|
if (e.database) return { filename: e.database };
|
|
18
18
|
throw Error("@voltro/sql-sqlite: no filename supplied. Set DB_URL=file:./db.sqlite or DB_DATABASE=path/to/db.sqlite.");
|
|
19
|
-
},
|
|
19
|
+
}, R = (e) => I(L(e)), z = /* @__PURE__ */ new Set([
|
|
20
20
|
"SQLITE_BUSY",
|
|
21
21
|
"SQLITE_LOCKED",
|
|
22
22
|
"5",
|
|
23
23
|
"6"
|
|
24
|
-
]),
|
|
24
|
+
]), B = (e) => {
|
|
25
25
|
let t = e;
|
|
26
26
|
for (let e = 0; e < 5 && typeof t == "object" && t; e++) {
|
|
27
27
|
let e = t.code;
|
|
@@ -29,18 +29,18 @@ var F = (r) => e.layerConfig({
|
|
|
29
29
|
if (typeof e == "number") return String(e);
|
|
30
30
|
t = t.cause;
|
|
31
31
|
}
|
|
32
|
-
},
|
|
33
|
-
let t =
|
|
34
|
-
return t !== void 0 &&
|
|
35
|
-
},
|
|
32
|
+
}, V = (e) => {
|
|
33
|
+
let t = B(e);
|
|
34
|
+
return t !== void 0 && z.has(t);
|
|
35
|
+
}, H = (e) => V(e) ? "retry" : "noRetry", U = c({ scope: "voltro:sqlite" }), W = async (e) => {
|
|
36
36
|
let t = e.tracerLayer ? i.mergeAll(e.sqlLayer, e.tracerLayer) : e.sqlLayer, n = a.make(t);
|
|
37
|
-
return new
|
|
38
|
-
},
|
|
37
|
+
return new q(await n.runPromise(l), n, null, void 0, e.isRetryable ?? V, e.systemName ?? "sqlite", e.wrapTransaction);
|
|
38
|
+
}, G = (e) => e, K = (e) => {
|
|
39
39
|
if (typeof e != "object" || !e) return e;
|
|
40
40
|
let t = {};
|
|
41
41
|
for (let [n, r] of Object.entries(e)) t[n] = r instanceof Date ? r.toISOString() : typeof r == "boolean" ? +!!r : r;
|
|
42
42
|
return t;
|
|
43
|
-
},
|
|
43
|
+
}, q = class e {
|
|
44
44
|
sql;
|
|
45
45
|
runtime;
|
|
46
46
|
namespace;
|
|
@@ -50,36 +50,36 @@ var F = (r) => e.layerConfig({
|
|
|
50
50
|
emitter;
|
|
51
51
|
inflightTxns = 0;
|
|
52
52
|
reportEagerFallback;
|
|
53
|
-
constructor(e, t, n = null, r, i =
|
|
54
|
-
this.sql = e, this.runtime = t, this.namespace = n, this.isRetryable = i, this.systemName = a, this.wrapTransaction =
|
|
53
|
+
constructor(e, t, n = null, r, i = V, a = "sqlite", o = G) {
|
|
54
|
+
this.sql = e, this.runtime = t, this.namespace = n, this.isRetryable = i, this.systemName = a, this.wrapTransaction = o, this.emitter = r ?? new s(), O(this.emitter), this.reportEagerFallback = w(U);
|
|
55
55
|
}
|
|
56
56
|
withNamespace(t) {
|
|
57
57
|
return t === this.namespace ? this : new e(this.sql, this.runtime, t, this.emitter, this.isRetryable, this.systemName, this.wrapTransaction);
|
|
58
58
|
}
|
|
59
59
|
nsT(e) {
|
|
60
|
-
return
|
|
60
|
+
return E(this.namespace, e);
|
|
61
61
|
}
|
|
62
62
|
async attachNamespace(e, t) {
|
|
63
63
|
let n = this.sql, r = t ?? `${e}.db`;
|
|
64
64
|
await this.runtime.runPromise(n`ATTACH DATABASE ${r} AS ${n(e)}`);
|
|
65
65
|
}
|
|
66
66
|
async executeQuery(e, t, n) {
|
|
67
|
-
let i =
|
|
68
|
-
return
|
|
67
|
+
let i = y(e, this.sql, this.namespace), a = t ? r.provideService(i, u, t) : i, o = await T(this.systemName, "select", () => this.runtime.runPromise(a));
|
|
68
|
+
return b(o, e.table, "sqlite");
|
|
69
69
|
}
|
|
70
70
|
async executeInsert(e, t, n, i, a) {
|
|
71
|
-
t =
|
|
72
|
-
let o = this.sql, s = o`INSERT INTO ${o(this.nsT(e))} ${o.insert(
|
|
73
|
-
if (!
|
|
71
|
+
t = N(e, t);
|
|
72
|
+
let o = this.sql, s = o`INSERT INTO ${o(this.nsT(e))} ${o.insert(K(t))} RETURNING *`, c = n ? r.provideService(s, u, n) : s, l = (await this.runtime.runPromise(c))[0];
|
|
73
|
+
if (!l) throw Error(`SqliteStore.insert: no row returned for table '${e}'`);
|
|
74
74
|
return await this.routeEvent({
|
|
75
75
|
table: e,
|
|
76
76
|
op: "insert",
|
|
77
77
|
old: null,
|
|
78
|
-
new:
|
|
79
|
-
}, i, n, a),
|
|
78
|
+
new: l
|
|
79
|
+
}, i, n, a), l;
|
|
80
80
|
}
|
|
81
81
|
async executeUpdate(e, t, n, i, a, o) {
|
|
82
|
-
let s = this.sql, c = s`UPDATE ${s(this.nsT(e))} SET ${s.update(
|
|
82
|
+
let s = this.sql, c = s`UPDATE ${s(this.nsT(e))} SET ${s.update(K(n))} WHERE ${s("id")} = ${t} RETURNING *`, l = i ? r.provideService(c, u, i) : c, d = (await this.runtime.runPromise(l))[0];
|
|
83
83
|
return d ? (await this.routeEvent({
|
|
84
84
|
table: e,
|
|
85
85
|
op: "update",
|
|
@@ -88,16 +88,16 @@ var F = (r) => e.layerConfig({
|
|
|
88
88
|
}, a, i, o), d) : null;
|
|
89
89
|
}
|
|
90
90
|
async executeInsertMany(e, t, n, i, a) {
|
|
91
|
-
if (t =
|
|
92
|
-
let o = this.sql, s = t.map((e) =>
|
|
91
|
+
if (t = P(e, t), t.length === 0) return [];
|
|
92
|
+
let o = this.sql, s = t.map((e) => K(e)), c = h(s, m("sqlite")), l = (t) => o`INSERT INTO ${o(this.nsT(e))} ${o.insert(t)} RETURNING *`, d;
|
|
93
93
|
if (c.length === 1) {
|
|
94
|
-
let e =
|
|
94
|
+
let e = l(c[0]), t = n ? r.provideService(e, u, n) : e;
|
|
95
95
|
d = await this.runtime.runPromise(t);
|
|
96
96
|
} else if (n) {
|
|
97
97
|
let e = [];
|
|
98
|
-
for (let t of c) e.push(...await this.runtime.runPromise(r.provideService(
|
|
98
|
+
for (let t of c) e.push(...await this.runtime.runPromise(r.provideService(l(t), u, n)));
|
|
99
99
|
d = e;
|
|
100
|
-
} else d = await this.runtime.runPromise(o.withTransaction(r.map(r.forEach(c,
|
|
100
|
+
} else d = await this.runtime.runPromise(o.withTransaction(r.map(r.forEach(c, l, { concurrency: 1 }), (e) => e.flat())));
|
|
101
101
|
for (let t of d) await this.routeEvent({
|
|
102
102
|
table: e,
|
|
103
103
|
op: "insert",
|
|
@@ -107,7 +107,7 @@ var F = (r) => e.layerConfig({
|
|
|
107
107
|
return d;
|
|
108
108
|
}
|
|
109
109
|
async executePatchJson(e, t, n, i, a, o, s) {
|
|
110
|
-
let c = this.sql,
|
|
110
|
+
let c = this.sql, l = n.split("."), d = l[0], f = l.slice(1), p = JSON.stringify(i ?? null), m = f.length === 0 ? c`${c(d)} = json_patch(COALESCE(${c(d)}, '{}'), json(${p}))` : c`${c(d)} = json_set(COALESCE(${c(d)}, '{}'), ${`$.${f.join(".")}`}, json(${p}))`, h = c`UPDATE ${c(this.nsT(e))} SET ${m} WHERE ${c("id")} = ${t} RETURNING *`, g = a ? r.provideService(h, u, a) : h, _ = (await this.runtime.runPromise(g))[0];
|
|
111
111
|
return _ ? (await this.routeEvent({
|
|
112
112
|
table: e,
|
|
113
113
|
op: "update",
|
|
@@ -116,27 +116,27 @@ var F = (r) => e.layerConfig({
|
|
|
116
116
|
}, o, a, s), _) : null;
|
|
117
117
|
}
|
|
118
118
|
async executeDelete(e, t, n, i, a) {
|
|
119
|
-
let o = this.sql, s = o`DELETE FROM ${o(this.nsT(e))} WHERE ${o("id")} = ${t} RETURNING *`, c = n ? r.provideService(s,
|
|
120
|
-
return
|
|
119
|
+
let o = this.sql, s = o`DELETE FROM ${o(this.nsT(e))} WHERE ${o("id")} = ${t} RETURNING *`, c = n ? r.provideService(s, u, n) : s, l = (await this.runtime.runPromise(c))[0];
|
|
120
|
+
return l ? (await this.routeEvent({
|
|
121
121
|
table: e,
|
|
122
122
|
op: "delete",
|
|
123
|
-
old:
|
|
123
|
+
old: l,
|
|
124
124
|
new: null
|
|
125
125
|
}, i, n, a), !0) : !1;
|
|
126
126
|
}
|
|
127
127
|
async appendInTxn(e, t, n) {
|
|
128
|
-
let i = this.sql, a = i`INSERT INTO ${i(this.nsT(e))} ${i.insert(
|
|
129
|
-
await this.runtime.runPromise(n ? r.provideService(a,
|
|
128
|
+
let i = this.sql, a = i`INSERT INTO ${i(this.nsT(e))} ${i.insert(K(t))}`;
|
|
129
|
+
await this.runtime.runPromise(n ? r.provideService(a, u, n) : a);
|
|
130
130
|
}
|
|
131
131
|
async maxInTxn(e, t, n, i) {
|
|
132
|
-
let a = this.sql, o = Object.entries(n).map(([e, t]) => a`${a(e)} = ${t}`), s = a`SELECT MAX(${a(t)}) AS ${a("m")} FROM ${a(this.nsT(e))} WHERE ${a.and(o)}`, c = (await this.runtime.runPromise(i ? r.provideService(s,
|
|
132
|
+
let a = this.sql, o = Object.entries(n).map(([e, t]) => a`${a(e)} = ${t}`), s = a`SELECT MAX(${a(t)}) AS ${a("m")} FROM ${a(this.nsT(e))} WHERE ${a.and(o)}`, c = (await this.runtime.runPromise(i ? r.provideService(s, u, i) : s))[0]?.m;
|
|
133
133
|
return c == null ? null : Number(c);
|
|
134
134
|
}
|
|
135
135
|
async routeEvent(e, t, n = null, r) {
|
|
136
136
|
e = {
|
|
137
|
-
...
|
|
137
|
+
...p(r),
|
|
138
138
|
...e
|
|
139
|
-
},
|
|
139
|
+
}, k(e.table) && await M({
|
|
140
140
|
append: (e, t) => this.appendInTxn(e, t, n),
|
|
141
141
|
maxOf: (e, t, r) => this.maxInTxn(e, t, r, n)
|
|
142
142
|
}, {
|
|
@@ -146,14 +146,14 @@ var F = (r) => e.layerConfig({
|
|
|
146
146
|
prev: e.old,
|
|
147
147
|
traceId: e.traceId,
|
|
148
148
|
subjectId: e.subjectId
|
|
149
|
-
}),
|
|
149
|
+
}), C(e.table) && (t === null ? this.emitter.emit("change", e) : t.push(e));
|
|
150
150
|
}
|
|
151
151
|
async executeUpsert(e, t, n, i, a, o) {
|
|
152
152
|
if (typeof n.update == "function") {
|
|
153
153
|
let r = await this.findByConflict(e, t, n.conflictColumns, i);
|
|
154
154
|
return r ? await this.executeUpdate(e, r.id, n.update(r), i, a) ?? r : this.executeInsert(e, t, i, a);
|
|
155
155
|
}
|
|
156
|
-
let s = this.sql, c = n.conflictColumns.map((e) => s`${s(e)}`),
|
|
156
|
+
let s = this.sql, c = n.conflictColumns.map((e) => s`${s(e)}`), l = Object.keys(t).filter((e) => t[e] !== void 0), d = n.update === void 0 ? l.filter((e) => e !== "id" && !n.conflictColumns.includes(e)) : n.update, f = d.length > 0 ? s.csv(d.map((e) => s`${s(e)} = excluded.${s(e)}`)) : s`${s(n.conflictColumns[0])} = excluded.${s(n.conflictColumns[0])}`, p = s`INSERT INTO ${s(this.nsT(e))} ${s.insert(K(t))} ON CONFLICT (${s.csv(c)}) DO UPDATE SET ${f} RETURNING *`, m = i ? r.provideService(p, u, i) : p, h = (await this.runtime.runPromise(m))[0];
|
|
157
157
|
if (!h) throw Error(`SqliteStore.upsert: no row returned for table '${e}'`);
|
|
158
158
|
let g = t.id !== void 0 && t.id === h.id ? "insert" : "update";
|
|
159
159
|
return await this.routeEvent({
|
|
@@ -164,8 +164,8 @@ var F = (r) => e.layerConfig({
|
|
|
164
164
|
}, a, i, o), h;
|
|
165
165
|
}
|
|
166
166
|
async executeInsertIgnore(e, t, n, i, a, o) {
|
|
167
|
-
t =
|
|
168
|
-
let s = this.sql, c = n.conflictColumns.map((e) => s`${s(e)}`),
|
|
167
|
+
t = N(e, t);
|
|
168
|
+
let s = this.sql, c = n.conflictColumns.map((e) => s`${s(e)}`), l = s`INSERT INTO ${s(this.nsT(e))} ${s.insert(K(t))} ON CONFLICT (${s.csv(c)}) DO NOTHING RETURNING *`, d = i ? r.provideService(l, u, i) : l, f = (await this.runtime.runPromise(d))[0];
|
|
169
169
|
if (f) return await this.routeEvent({
|
|
170
170
|
table: e,
|
|
171
171
|
op: "insert",
|
|
@@ -178,24 +178,24 @@ var F = (r) => e.layerConfig({
|
|
|
178
178
|
}
|
|
179
179
|
async findByConflict(e, t, n, i) {
|
|
180
180
|
if (n.length === 0) return;
|
|
181
|
-
let a = this.sql, o = n.map((e) => a`${a(e)} = ${t[e]}`), s = a`SELECT * FROM ${a(this.nsT(e))} WHERE ${a.and(o)} LIMIT 1`, c = i ? r.provideService(s,
|
|
181
|
+
let a = this.sql, o = n.map((e) => a`${a(e)} = ${t[e]}`), s = a`SELECT * FROM ${a(this.nsT(e))} WHERE ${a.and(o)} LIMIT 1`, c = i ? r.provideService(s, u, i) : s;
|
|
182
182
|
return (await this.runtime.runPromise(c))[0];
|
|
183
183
|
}
|
|
184
184
|
query(e) {
|
|
185
185
|
return this.runWithEager(e, null);
|
|
186
186
|
}
|
|
187
187
|
raw(e, t) {
|
|
188
|
-
let n =
|
|
189
|
-
return
|
|
188
|
+
let n = v(e, this.sql);
|
|
189
|
+
return T(this.systemName, "raw", () => this.runtime.runPromise(n));
|
|
190
190
|
}
|
|
191
191
|
async runWithEager(e, t) {
|
|
192
|
-
if (!
|
|
193
|
-
let n = this.namespace === null ?
|
|
192
|
+
if (!S(e)) return this.executeQuery(e, t);
|
|
193
|
+
let n = this.namespace === null ? g(e, this.sql, "sqlite") : null;
|
|
194
194
|
if (n !== null) try {
|
|
195
|
-
let e = t ? r.provideService(n.fragment,
|
|
195
|
+
let e = t ? r.provideService(n.fragment, u, t) : n.fragment, i = await T(this.systemName, "select", () => this.runtime.runPromise(e));
|
|
196
196
|
return n.decode(i);
|
|
197
197
|
} catch (t) {
|
|
198
|
-
if (t instanceof
|
|
198
|
+
if (t instanceof d) throw t;
|
|
199
199
|
this.reportEagerFallback({
|
|
200
200
|
dialect: this.systemName,
|
|
201
201
|
table: e.table,
|
|
@@ -209,31 +209,40 @@ var F = (r) => e.layerConfig({
|
|
|
209
209
|
reason: "not-compilable"
|
|
210
210
|
});
|
|
211
211
|
let i = await this.executeQuery(e, t);
|
|
212
|
-
return
|
|
212
|
+
return f(i, e.eager, e.sourceTable ?? A(e.table), (e) => this.executeQuery(e, t));
|
|
213
213
|
}
|
|
214
214
|
getInternalRunWithEager() {
|
|
215
215
|
return (e, t) => this.runWithEager(e, t);
|
|
216
216
|
}
|
|
217
|
-
localWrite(e, t) {
|
|
218
|
-
|
|
217
|
+
localWrite(e, t, n) {
|
|
218
|
+
let r = (e) => k(t) ? this.writeWithRecorders(n, e) : n(e, null, null);
|
|
219
|
+
return T(this.systemName, e, () => F(r));
|
|
220
|
+
}
|
|
221
|
+
async writeWithRecorders(e, t) {
|
|
222
|
+
let n = [], i = this.sql, a = await this.runtime.runPromise(i.withTransaction(r.flatMap(r.serviceOption(u), (i) => o.isNone(i) ? r.fail(/* @__PURE__ */ Error("SqliteStore.write: TransactionConnection missing.")) : r.tryPromise({
|
|
223
|
+
try: () => e(t, i.value, n),
|
|
224
|
+
catch: (e) => e
|
|
225
|
+
}))));
|
|
226
|
+
for (let e of n) this.emitChange(e);
|
|
227
|
+
return a;
|
|
219
228
|
}
|
|
220
229
|
insert(e, t) {
|
|
221
|
-
return this.localWrite("insert", (n) => this.executeInsert(e, t,
|
|
230
|
+
return this.localWrite("insert", e, (n, r, i) => this.executeInsert(e, t, r, i, n));
|
|
222
231
|
}
|
|
223
232
|
insertMany(e, t) {
|
|
224
|
-
return this.localWrite("insert", (n) => this.executeInsertMany(e, t,
|
|
233
|
+
return this.localWrite("insert", e, (n, r, i) => this.executeInsertMany(e, t, r, i, n));
|
|
225
234
|
}
|
|
226
235
|
patchJson(e, t, n, r) {
|
|
227
|
-
return this.localWrite("update", (i) => this.executePatchJson(e, t, n, r,
|
|
236
|
+
return this.localWrite("update", e, (i, a, o) => this.executePatchJson(e, t, n, r, a, o, i));
|
|
228
237
|
}
|
|
229
238
|
update(e, t, n) {
|
|
230
|
-
return this.localWrite("update", (r) => this.executeUpdate(e, t, n,
|
|
239
|
+
return this.localWrite("update", e, (r, i, a) => this.executeUpdate(e, t, n, i, a, r));
|
|
231
240
|
}
|
|
232
241
|
delete(e, t) {
|
|
233
|
-
return this.localWrite("delete", (n) => this.executeDelete(e, t,
|
|
242
|
+
return this.localWrite("delete", e, (n, r, i) => this.executeDelete(e, t, r, i, n));
|
|
234
243
|
}
|
|
235
244
|
async updateMany(e, t, n) {
|
|
236
|
-
let r = this.sql, i =
|
|
245
|
+
let r = this.sql, i = _(n.where, r, this.namespace), a = r`UPDATE ${r(this.nsT(e))} SET ${r.update(K(t))} WHERE ${i} RETURNING *`, o = await T(this.systemName, "update", () => this.runtime.runPromise(a));
|
|
237
246
|
for (let t of o) await this.routeEvent({
|
|
238
247
|
table: e,
|
|
239
248
|
op: "update",
|
|
@@ -243,7 +252,7 @@ var F = (r) => e.layerConfig({
|
|
|
243
252
|
return o.length;
|
|
244
253
|
}
|
|
245
254
|
async deleteMany(e, t) {
|
|
246
|
-
let n = this.sql, r =
|
|
255
|
+
let n = this.sql, r = _(t.where, n, this.namespace), i = n`DELETE FROM ${n(this.nsT(e))} WHERE ${r} RETURNING *`, a = await T(this.systemName, "delete", () => this.runtime.runPromise(i));
|
|
247
256
|
for (let t of a) await this.routeEvent({
|
|
248
257
|
table: e,
|
|
249
258
|
op: "delete",
|
|
@@ -253,10 +262,10 @@ var F = (r) => e.layerConfig({
|
|
|
253
262
|
return a.length;
|
|
254
263
|
}
|
|
255
264
|
upsert(e, t, n) {
|
|
256
|
-
return this.localWrite("upsert", (r) => this.executeUpsert(e, t, n,
|
|
265
|
+
return this.localWrite("upsert", e, (r, i, a) => this.executeUpsert(e, t, n, i, a, r));
|
|
257
266
|
}
|
|
258
267
|
insertIgnore(e, t, n) {
|
|
259
|
-
return this.localWrite("upsert", (r) => this.executeInsertIgnore(e, t, n,
|
|
268
|
+
return this.localWrite("upsert", e, (r, i, a) => this.executeInsertIgnore(e, t, n, i, a, r));
|
|
260
269
|
}
|
|
261
270
|
emitChange(e) {
|
|
262
271
|
this.emitter.emit("change", e);
|
|
@@ -293,7 +302,7 @@ var F = (r) => e.layerConfig({
|
|
|
293
302
|
let t = this.sql, n = new Set(e), r = await this.runtime.runPromise(t`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'`), i = [];
|
|
294
303
|
for (let e of r) {
|
|
295
304
|
if (n.has(e.name)) continue;
|
|
296
|
-
let r = await this.runtime.runPromise(t`${t.unsafe(`PRAGMA foreign_key_list(${
|
|
305
|
+
let r = await this.runtime.runPromise(t`${t.unsafe(`PRAGMA foreign_key_list(${D(e.name, "sqlite")})`)}`).catch(() => []);
|
|
297
306
|
for (let t of r) n.has(t.table) && i.push({
|
|
298
307
|
from: e.name,
|
|
299
308
|
column: t.from,
|
|
@@ -304,16 +313,16 @@ var F = (r) => e.layerConfig({
|
|
|
304
313
|
}
|
|
305
314
|
async emptyTablesOn(e, t) {
|
|
306
315
|
if (e.length === 0) return;
|
|
307
|
-
let n = this.sql, i = (e) => this.nsT(e), a = r.acquireUseRelease(n.unsafe("PRAGMA defer_foreign_keys = ON"), () => r.forEach(e, (e) => n`DELETE FROM ${n(i(e))}`, { discard: !0 }), () => r.orDie(r.ignore(n.unsafe("PRAGMA defer_foreign_keys = OFF")))), o = t === null ? n.withTransaction(a) : r.provideService(a,
|
|
316
|
+
let n = this.sql, i = (e) => this.nsT(e), a = r.acquireUseRelease(n.unsafe("PRAGMA defer_foreign_keys = ON"), () => r.forEach(e, (e) => n`DELETE FROM ${n(i(e))}`, { discard: !0 }), () => r.orDie(r.ignore(n.unsafe("PRAGMA defer_foreign_keys = OFF")))), o = t === null ? n.withTransaction(a) : r.provideService(a, u, t);
|
|
308
317
|
await this.runtime.runPromise(o);
|
|
309
318
|
}
|
|
310
319
|
async transactional(e) {
|
|
311
320
|
this.inflightTxns++;
|
|
312
321
|
try {
|
|
313
|
-
return await
|
|
322
|
+
return await j({
|
|
314
323
|
...this.txnSpec("SqliteStore.transactional"),
|
|
315
324
|
work: e,
|
|
316
|
-
makeView: (e, t) => new
|
|
325
|
+
makeView: (e, t) => new J(this, e, t)
|
|
317
326
|
});
|
|
318
327
|
} finally {
|
|
319
328
|
this.inflightTxns--;
|
|
@@ -342,7 +351,7 @@ var F = (r) => e.layerConfig({
|
|
|
342
351
|
};
|
|
343
352
|
}
|
|
344
353
|
injectExternalChange(e) {
|
|
345
|
-
|
|
354
|
+
C(e.table) && this.emitter.emit("change", x(e));
|
|
346
355
|
}
|
|
347
356
|
run(e) {
|
|
348
357
|
return this.runtime.runPromise(e);
|
|
@@ -351,7 +360,7 @@ var F = (r) => e.layerConfig({
|
|
|
351
360
|
if (this.inflightTxns > 0) {
|
|
352
361
|
let t = Date.now() + e;
|
|
353
362
|
for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
|
|
354
|
-
this.inflightTxns > 0 &&
|
|
363
|
+
this.inflightTxns > 0 && U.warn("close: grace period expired with in-flight transactions", {
|
|
355
364
|
gracePeriodMs: e,
|
|
356
365
|
inflight: this.inflightTxns
|
|
357
366
|
});
|
|
@@ -361,7 +370,7 @@ var F = (r) => e.layerConfig({
|
|
|
361
370
|
async ping() {
|
|
362
371
|
await this.runtime.runPromise(this.sql`SELECT 1`);
|
|
363
372
|
}
|
|
364
|
-
},
|
|
373
|
+
}, J = class {
|
|
365
374
|
parent;
|
|
366
375
|
txn;
|
|
367
376
|
attr;
|
|
@@ -437,12 +446,12 @@ var F = (r) => e.layerConfig({
|
|
|
437
446
|
this.events.length = 0;
|
|
438
447
|
}
|
|
439
448
|
}
|
|
440
|
-
},
|
|
449
|
+
}, Y = {
|
|
441
450
|
id: "sqlite",
|
|
442
|
-
makeSqlLayer: (e) =>
|
|
443
|
-
makeStore: (e) =>
|
|
451
|
+
makeSqlLayer: (e) => R(e),
|
|
452
|
+
makeStore: (e) => W(e),
|
|
444
453
|
compileContains: (e, t, n) => n`${e} LIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
|
|
445
|
-
retryFilter:
|
|
454
|
+
retryFilter: H
|
|
446
455
|
};
|
|
447
456
|
//#endregion
|
|
448
|
-
export { e as SqliteClient,
|
|
457
|
+
export { e as SqliteClient, L as connectionFromConfig, I as makeSqliteSqlLayer, R as makeSqliteSqlLayerFromConfig, W as makeSqliteStore, Y as sqliteDialect, H as sqliteRetryFilter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/sql-sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.1",
|
|
4
4
|
"description": "SQLite dialect adapter for Voltro's cross-dialect DataStore (single-process; in-process change events).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@effect/sql": "^0.52.0",
|
|
37
37
|
"@effect/sql-sqlite-node": "^0.53.0",
|
|
38
|
-
"@voltro/database": "0.
|
|
39
|
-
"@voltro/logger": "0.
|
|
38
|
+
"@voltro/database": "0.44.1",
|
|
39
|
+
"@voltro/logger": "0.44.1"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"effect": "^3.22.0"
|