@voltro/sql-sqlite 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 +67 -0
- package/dist/index.js +52 -49
- package/package.json +3 -3
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.js
CHANGED
|
@@ -3,12 +3,12 @@ import { Config as t, Context as n, Effect as r, Layer as i, ManagedRuntime as a
|
|
|
3
3
|
import { EventEmitter as s } from "node:events";
|
|
4
4
|
import { createLogger as c } from "@voltro/logger";
|
|
5
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,
|
|
6
|
+
import { EagerCardinalityError as d, attachEagerLoads as f, attributionFields as p, bulkInsertLimitsFor as m, chunkRowsForInsert as h, coercePredicateForSqlite as g, compileEagerJson as _, compilePredicate as v, compileRawFragment as y, compileSelect as b, decodeRowsFromSchema as x, externalChangeEvent as S, hasEagerLoads as C, isTableReactive as w, makeEagerFallbackReporter as T, observeDbOp as E, qualifyTable as D, quoteIdent as O, raiseChangeListenerCeiling as k, recordsTable as A, requireTable as j, runStoreTransaction as M, runWriteRecorders as N, stampGeneratedId as P, stampGeneratedIds as F, withCapturedAttribution as I } from "@voltro/database";
|
|
7
7
|
//#region src/sqlLayer.ts
|
|
8
|
-
var
|
|
8
|
+
var L = (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`)), R = (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 I = (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
|
+
}, z = (e) => L(R(e)), B = /* @__PURE__ */ new Set([
|
|
20
20
|
"SQLITE_BUSY",
|
|
21
21
|
"SQLITE_LOCKED",
|
|
22
22
|
"5",
|
|
23
23
|
"6"
|
|
24
|
-
]),
|
|
24
|
+
]), V = (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,21 @@ var I = (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
|
+
}, H = (e) => {
|
|
33
|
+
let t = V(e);
|
|
34
|
+
return t !== void 0 && B.has(t);
|
|
35
|
+
}, U = (e) => H(e) ? "retry" : "noRetry", W = c({ scope: "voltro:sqlite" }), G = 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 X(await n.runPromise(l), n, null, void 0, e.isRetryable ?? H, e.systemName ?? "sqlite", e.wrapTransaction);
|
|
38
|
+
}, K = (e) => e, q = (e) => g(e), J = (e) => e.predicate === void 0 || e.predicate === null ? e : {
|
|
39
|
+
...e,
|
|
40
|
+
predicate: q(e.predicate)
|
|
41
|
+
}, Y = (e) => {
|
|
39
42
|
if (typeof e != "object" || !e) return e;
|
|
40
43
|
let t = {};
|
|
41
44
|
for (let [n, r] of Object.entries(e)) t[n] = r instanceof Date ? r.toISOString() : typeof r == "boolean" ? +!!r : r;
|
|
42
45
|
return t;
|
|
43
|
-
},
|
|
46
|
+
}, X = class e {
|
|
44
47
|
sql;
|
|
45
48
|
runtime;
|
|
46
49
|
namespace;
|
|
@@ -50,26 +53,26 @@ var I = (r) => e.layerConfig({
|
|
|
50
53
|
emitter;
|
|
51
54
|
inflightTxns = 0;
|
|
52
55
|
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 = o, this.emitter = r ?? new s(),
|
|
56
|
+
constructor(e, t, n = null, r, i = H, a = "sqlite", o = K) {
|
|
57
|
+
this.sql = e, this.runtime = t, this.namespace = n, this.isRetryable = i, this.systemName = a, this.wrapTransaction = o, this.emitter = r ?? new s(), k(this.emitter), this.reportEagerFallback = T(W);
|
|
55
58
|
}
|
|
56
59
|
withNamespace(t) {
|
|
57
60
|
return t === this.namespace ? this : new e(this.sql, this.runtime, t, this.emitter, this.isRetryable, this.systemName, this.wrapTransaction);
|
|
58
61
|
}
|
|
59
62
|
nsT(e) {
|
|
60
|
-
return
|
|
63
|
+
return D(this.namespace, e);
|
|
61
64
|
}
|
|
62
65
|
async attachNamespace(e, t) {
|
|
63
66
|
let n = this.sql, r = t ?? `${e}.db`;
|
|
64
67
|
await this.runtime.runPromise(n`ATTACH DATABASE ${r} AS ${n(e)}`);
|
|
65
68
|
}
|
|
66
69
|
async executeQuery(e, t, n) {
|
|
67
|
-
let i =
|
|
68
|
-
return
|
|
70
|
+
let i = b(J(e), this.sql, this.namespace), a = t ? r.provideService(i, u, t) : i, o = await E(this.systemName, "select", () => this.runtime.runPromise(a));
|
|
71
|
+
return x(o, e.table, "sqlite");
|
|
69
72
|
}
|
|
70
73
|
async executeInsert(e, t, n, i, a) {
|
|
71
|
-
t =
|
|
72
|
-
let o = this.sql, s = o`INSERT INTO ${o(this.nsT(e))} ${o.insert(
|
|
74
|
+
t = P(e, t);
|
|
75
|
+
let o = this.sql, s = o`INSERT INTO ${o(this.nsT(e))} ${o.insert(Y(t))} RETURNING *`, c = n ? r.provideService(s, u, n) : s, l = (await this.runtime.runPromise(c))[0];
|
|
73
76
|
if (!l) throw Error(`SqliteStore.insert: no row returned for table '${e}'`);
|
|
74
77
|
return await this.routeEvent({
|
|
75
78
|
table: e,
|
|
@@ -79,7 +82,7 @@ var I = (r) => e.layerConfig({
|
|
|
79
82
|
}, i, n, a), l;
|
|
80
83
|
}
|
|
81
84
|
async executeUpdate(e, t, n, i, a, o) {
|
|
82
|
-
let s = this.sql, c = s`UPDATE ${s(this.nsT(e))} SET ${s.update(
|
|
85
|
+
let s = this.sql, c = s`UPDATE ${s(this.nsT(e))} SET ${s.update(Y(n))} WHERE ${s("id")} = ${t} RETURNING *`, l = i ? r.provideService(c, u, i) : c, d = (await this.runtime.runPromise(l))[0];
|
|
83
86
|
return d ? (await this.routeEvent({
|
|
84
87
|
table: e,
|
|
85
88
|
op: "update",
|
|
@@ -88,8 +91,8 @@ var I = (r) => e.layerConfig({
|
|
|
88
91
|
}, a, i, o), d) : null;
|
|
89
92
|
}
|
|
90
93
|
async executeInsertMany(e, t, n, i, a) {
|
|
91
|
-
if (t =
|
|
92
|
-
let o = this.sql, s = t.map((e) =>
|
|
94
|
+
if (t = F(e, t), t.length === 0) return [];
|
|
95
|
+
let o = this.sql, s = t.map((e) => Y(e)), c = h(s, m("sqlite")), l = (t) => o`INSERT INTO ${o(this.nsT(e))} ${o.insert(t)} RETURNING *`, d;
|
|
93
96
|
if (c.length === 1) {
|
|
94
97
|
let e = l(c[0]), t = n ? r.provideService(e, u, n) : e;
|
|
95
98
|
d = await this.runtime.runPromise(t);
|
|
@@ -125,7 +128,7 @@ var I = (r) => e.layerConfig({
|
|
|
125
128
|
}, i, n, a), !0) : !1;
|
|
126
129
|
}
|
|
127
130
|
async appendInTxn(e, t, n) {
|
|
128
|
-
let i = this.sql, a = i`INSERT INTO ${i(this.nsT(e))} ${i.insert(
|
|
131
|
+
let i = this.sql, a = i`INSERT INTO ${i(this.nsT(e))} ${i.insert(Y(t))}`;
|
|
129
132
|
await this.runtime.runPromise(n ? r.provideService(a, u, n) : a);
|
|
130
133
|
}
|
|
131
134
|
async maxInTxn(e, t, n, i) {
|
|
@@ -136,7 +139,7 @@ var I = (r) => e.layerConfig({
|
|
|
136
139
|
e = {
|
|
137
140
|
...p(r),
|
|
138
141
|
...e
|
|
139
|
-
},
|
|
142
|
+
}, A(e.table) && await N({
|
|
140
143
|
append: (e, t) => this.appendInTxn(e, t, n),
|
|
141
144
|
maxOf: (e, t, r) => this.maxInTxn(e, t, r, n)
|
|
142
145
|
}, {
|
|
@@ -146,14 +149,14 @@ var I = (r) => e.layerConfig({
|
|
|
146
149
|
prev: e.old,
|
|
147
150
|
traceId: e.traceId,
|
|
148
151
|
subjectId: e.subjectId
|
|
149
|
-
}),
|
|
152
|
+
}), w(e.table) && (t === null ? this.emitter.emit("change", e) : t.push(e));
|
|
150
153
|
}
|
|
151
154
|
async executeUpsert(e, t, n, i, a, o) {
|
|
152
155
|
if (typeof n.update == "function") {
|
|
153
156
|
let r = await this.findByConflict(e, t, n.conflictColumns, i);
|
|
154
157
|
return r ? await this.executeUpdate(e, r.id, n.update(r), i, a) ?? r : this.executeInsert(e, t, i, a);
|
|
155
158
|
}
|
|
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(
|
|
159
|
+
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(Y(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
160
|
if (!h) throw Error(`SqliteStore.upsert: no row returned for table '${e}'`);
|
|
158
161
|
let g = t.id !== void 0 && t.id === h.id ? "insert" : "update";
|
|
159
162
|
return await this.routeEvent({
|
|
@@ -164,8 +167,8 @@ var I = (r) => e.layerConfig({
|
|
|
164
167
|
}, a, i, o), h;
|
|
165
168
|
}
|
|
166
169
|
async executeInsertIgnore(e, t, n, i, a, o) {
|
|
167
|
-
t =
|
|
168
|
-
let s = this.sql, c = n.conflictColumns.map((e) => s`${s(e)}`), l = s`INSERT INTO ${s(this.nsT(e))} ${s.insert(
|
|
170
|
+
t = P(e, t);
|
|
171
|
+
let s = this.sql, c = n.conflictColumns.map((e) => s`${s(e)}`), l = s`INSERT INTO ${s(this.nsT(e))} ${s.insert(Y(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
172
|
if (f) return await this.routeEvent({
|
|
170
173
|
table: e,
|
|
171
174
|
op: "insert",
|
|
@@ -185,14 +188,14 @@ var I = (r) => e.layerConfig({
|
|
|
185
188
|
return this.runWithEager(e, null);
|
|
186
189
|
}
|
|
187
190
|
raw(e, t) {
|
|
188
|
-
let n =
|
|
189
|
-
return
|
|
191
|
+
let n = y(e, this.sql);
|
|
192
|
+
return E(this.systemName, "raw", () => this.runtime.runPromise(n));
|
|
190
193
|
}
|
|
191
194
|
async runWithEager(e, t) {
|
|
192
|
-
if (!
|
|
193
|
-
let n = this.namespace === null ?
|
|
195
|
+
if (!C(e)) return this.executeQuery(e, t);
|
|
196
|
+
let n = this.namespace === null ? _(e, this.sql, "sqlite") : null;
|
|
194
197
|
if (n !== null) try {
|
|
195
|
-
let e = t ? r.provideService(n.fragment, u, t) : n.fragment, i = await
|
|
198
|
+
let e = t ? r.provideService(n.fragment, u, t) : n.fragment, i = await E(this.systemName, "select", () => this.runtime.runPromise(e));
|
|
196
199
|
return n.decode(i);
|
|
197
200
|
} catch (t) {
|
|
198
201
|
if (t instanceof d) throw t;
|
|
@@ -209,14 +212,14 @@ var I = (r) => e.layerConfig({
|
|
|
209
212
|
reason: "not-compilable"
|
|
210
213
|
});
|
|
211
214
|
let i = await this.executeQuery(e, t);
|
|
212
|
-
return f(i, e.eager, e.sourceTable ??
|
|
215
|
+
return f(i, e.eager, e.sourceTable ?? j(e.table), (e) => this.executeQuery(e, t));
|
|
213
216
|
}
|
|
214
217
|
getInternalRunWithEager() {
|
|
215
218
|
return (e, t) => this.runWithEager(e, t);
|
|
216
219
|
}
|
|
217
220
|
localWrite(e, t, n) {
|
|
218
|
-
let r = (e) =>
|
|
219
|
-
return
|
|
221
|
+
let r = (e) => A(t) ? this.writeWithRecorders(n, e) : n(e, null, null);
|
|
222
|
+
return E(this.systemName, e, () => I(r));
|
|
220
223
|
}
|
|
221
224
|
async writeWithRecorders(e, t) {
|
|
222
225
|
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({
|
|
@@ -242,7 +245,7 @@ var I = (r) => e.layerConfig({
|
|
|
242
245
|
return this.localWrite("delete", e, (n, r, i) => this.executeDelete(e, t, r, i, n));
|
|
243
246
|
}
|
|
244
247
|
async updateMany(e, t, n) {
|
|
245
|
-
let r = this.sql, i =
|
|
248
|
+
let r = this.sql, i = v(q(n.where), r, this.namespace), a = r`UPDATE ${r(this.nsT(e))} SET ${r.update(Y(t))} WHERE ${i} RETURNING *`, o = await E(this.systemName, "update", () => this.runtime.runPromise(a));
|
|
246
249
|
for (let t of o) await this.routeEvent({
|
|
247
250
|
table: e,
|
|
248
251
|
op: "update",
|
|
@@ -252,7 +255,7 @@ var I = (r) => e.layerConfig({
|
|
|
252
255
|
return o.length;
|
|
253
256
|
}
|
|
254
257
|
async deleteMany(e, t) {
|
|
255
|
-
let n = this.sql, r =
|
|
258
|
+
let n = this.sql, r = v(q(t.where), n, this.namespace), i = n`DELETE FROM ${n(this.nsT(e))} WHERE ${r} RETURNING *`, a = await E(this.systemName, "delete", () => this.runtime.runPromise(i));
|
|
256
259
|
for (let t of a) await this.routeEvent({
|
|
257
260
|
table: e,
|
|
258
261
|
op: "delete",
|
|
@@ -302,7 +305,7 @@ var I = (r) => e.layerConfig({
|
|
|
302
305
|
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 = [];
|
|
303
306
|
for (let e of r) {
|
|
304
307
|
if (n.has(e.name)) continue;
|
|
305
|
-
let r = await this.runtime.runPromise(t`${t.unsafe(`PRAGMA foreign_key_list(${
|
|
308
|
+
let r = await this.runtime.runPromise(t`${t.unsafe(`PRAGMA foreign_key_list(${O(e.name, "sqlite")})`)}`).catch(() => []);
|
|
306
309
|
for (let t of r) n.has(t.table) && i.push({
|
|
307
310
|
from: e.name,
|
|
308
311
|
column: t.from,
|
|
@@ -319,10 +322,10 @@ var I = (r) => e.layerConfig({
|
|
|
319
322
|
async transactional(e) {
|
|
320
323
|
this.inflightTxns++;
|
|
321
324
|
try {
|
|
322
|
-
return await
|
|
325
|
+
return await M({
|
|
323
326
|
...this.txnSpec("SqliteStore.transactional"),
|
|
324
327
|
work: e,
|
|
325
|
-
makeView: (e, t) => new
|
|
328
|
+
makeView: (e, t) => new Z(this, e, t)
|
|
326
329
|
});
|
|
327
330
|
} finally {
|
|
328
331
|
this.inflightTxns--;
|
|
@@ -351,7 +354,7 @@ var I = (r) => e.layerConfig({
|
|
|
351
354
|
};
|
|
352
355
|
}
|
|
353
356
|
injectExternalChange(e) {
|
|
354
|
-
|
|
357
|
+
w(e.table) && this.emitter.emit("change", S(e));
|
|
355
358
|
}
|
|
356
359
|
run(e) {
|
|
357
360
|
return this.runtime.runPromise(e);
|
|
@@ -360,7 +363,7 @@ var I = (r) => e.layerConfig({
|
|
|
360
363
|
if (this.inflightTxns > 0) {
|
|
361
364
|
let t = Date.now() + e;
|
|
362
365
|
for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
|
|
363
|
-
this.inflightTxns > 0 &&
|
|
366
|
+
this.inflightTxns > 0 && W.warn("close: grace period expired with in-flight transactions", {
|
|
364
367
|
gracePeriodMs: e,
|
|
365
368
|
inflight: this.inflightTxns
|
|
366
369
|
});
|
|
@@ -370,7 +373,7 @@ var I = (r) => e.layerConfig({
|
|
|
370
373
|
async ping() {
|
|
371
374
|
await this.runtime.runPromise(this.sql`SELECT 1`);
|
|
372
375
|
}
|
|
373
|
-
},
|
|
376
|
+
}, Z = class {
|
|
374
377
|
parent;
|
|
375
378
|
txn;
|
|
376
379
|
attr;
|
|
@@ -446,12 +449,12 @@ var I = (r) => e.layerConfig({
|
|
|
446
449
|
this.events.length = 0;
|
|
447
450
|
}
|
|
448
451
|
}
|
|
449
|
-
},
|
|
452
|
+
}, Q = {
|
|
450
453
|
id: "sqlite",
|
|
451
|
-
makeSqlLayer: (e) =>
|
|
452
|
-
makeStore: (e) =>
|
|
454
|
+
makeSqlLayer: (e) => z(e),
|
|
455
|
+
makeStore: (e) => G(e),
|
|
453
456
|
compileContains: (e, t, n) => n`${e} LIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
|
|
454
|
-
retryFilter:
|
|
457
|
+
retryFilter: U
|
|
455
458
|
};
|
|
456
459
|
//#endregion
|
|
457
|
-
export { e as SqliteClient,
|
|
460
|
+
export { e as SqliteClient, R as connectionFromConfig, L as makeSqliteSqlLayer, z as makeSqliteSqlLayerFromConfig, G as makeSqliteStore, Q as sqliteDialect, U as sqliteRetryFilter };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/sql-sqlite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
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.49.0",
|
|
39
|
+
"@voltro/logger": "0.49.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"effect": "^3.22.0"
|