@delali/sirannon-db 0.1.6 → 0.1.7
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/README.md +134 -19
- package/dist/backup-scheduler/index.d.ts +18 -3
- package/dist/backup-scheduler/index.mjs +2 -2
- package/dist/{change-tracker-CFTQ9TSn.d.ts → change-tracker-CbmaMO-N.d.ts} +12 -2
- package/dist/chunk-4IGMIJQK.mjs +318 -0
- package/dist/chunk-BNUTBHHH.mjs +22 -0
- package/dist/chunk-CJLYFDP5.mjs +26 -0
- package/dist/chunk-CW6S3WL5.mjs +222 -0
- package/dist/chunk-DJLX6CAE.mjs +20 -0
- package/dist/chunk-DVWQD3GF.mjs +49 -0
- package/dist/chunk-GEZUUIKV.mjs +268 -0
- package/dist/{chunk-UVMVN3OT.mjs → chunk-H5AB6NIR.mjs} +1 -1
- package/dist/{chunk-UTO3ZAFS.mjs → chunk-HHRMRFFR.mjs} +148 -28
- package/dist/chunk-TKGHYWQ6.mjs +35 -0
- package/dist/chunk-VLTICJOD.mjs +470 -0
- package/dist/{chunk-O7BHI3CF.mjs → chunk-YPYVQJ4C.mjs} +15 -1
- package/dist/client/index.d.ts +107 -15
- package/dist/client/index.mjs +348 -37
- package/dist/core/index.d.ts +70 -13
- package/dist/core/index.mjs +635 -192
- package/dist/core/writer-worker.d.ts +2 -0
- package/dist/core/writer-worker.mjs +107 -0
- package/dist/{database-BVY1GqE7.d.ts → database-DuGp0Rtr.d.ts} +51 -19
- package/dist/driver/better-sqlite3.d.ts +1 -1
- package/dist/driver/better-sqlite3.mjs +44 -6
- package/dist/driver/bun.mjs +35 -6
- package/dist/driver/expo.mjs +2 -2
- package/dist/driver/node.d.ts +1 -1
- package/dist/driver/node.mjs +48 -5
- package/dist/driver/wa-sqlite.d.ts +109 -0
- package/dist/driver/wa-sqlite.mjs +30 -2
- package/dist/{errors-C00ed08Q.d.ts → errors-5Nf5ZAEC.d.ts} +19 -1
- package/dist/file-migrations/index.d.ts +2 -3
- package/dist/file-migrations/index.mjs +1 -1
- package/dist/replication/coordinator/etcd.mjs +2 -2
- package/dist/replication/index.d.ts +7 -8
- package/dist/replication/index.mjs +79 -78
- package/dist/server/index.d.ts +111 -28
- package/dist/server/index.mjs +1016 -384
- package/dist/{sirannon-Cd-lK6T0.d.ts → sirannon-4SspRvP5.d.ts} +3 -3
- package/dist/transport/grpc.d.ts +3 -4
- package/dist/transport/grpc.mjs +2 -2
- package/dist/{types-Lc7ywFx7.d.ts → types-BsjobKbl.d.ts} +2 -2
- package/dist/{types-BeozgNPr.d.ts → types-D4p4UyDK.d.ts} +1 -1
- package/dist/types-D_hQW1hr.d.ts +494 -0
- package/package.json +3 -23
- package/dist/chunk-3MCMONVP.mjs +0 -115
- package/dist/chunk-74UN4DIE.mjs +0 -14
- package/dist/chunk-FB2U2Q3Y.mjs +0 -21
- package/dist/chunk-GS7T5YMI.mjs +0 -51
- package/dist/chunk-PXKAKK2V.mjs +0 -124
- package/dist/index-CLdNrcPz.d.ts +0 -16
- package/dist/types-BFSsG77t.d.ts +0 -29
- package/dist/types-D-74JiXb.d.ts +0 -265
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { deserializeError } from './chunk-DJLX6CAE.mjs';
|
|
2
|
+
import { BackupManager, BackupScheduler } from './chunk-VLTICJOD.mjs';
|
|
3
|
+
import { SirannonError } from './chunk-YPYVQJ4C.mjs';
|
|
4
|
+
import { Worker } from 'worker_threads';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { resolve, dirname, join, sep } from 'path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
8
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
9
|
+
|
|
10
|
+
// src/core/driver/statement-cache.ts
|
|
11
|
+
var STATEMENT_CACHE_CAPACITY = 128;
|
|
12
|
+
function createStatementCache(prepare) {
|
|
13
|
+
const cache = /* @__PURE__ */ new Map();
|
|
14
|
+
return (sql) => {
|
|
15
|
+
const cached = cache.get(sql);
|
|
16
|
+
if (cached !== void 0) {
|
|
17
|
+
cache.delete(sql);
|
|
18
|
+
cache.set(sql, cached);
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
const prepared = prepare(sql);
|
|
22
|
+
cache.set(sql, prepared);
|
|
23
|
+
if (cache.size > STATEMENT_CACHE_CAPACITY) {
|
|
24
|
+
const oldest = cache.keys().next().value;
|
|
25
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
26
|
+
}
|
|
27
|
+
return prepared;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/core/driver/values.ts
|
|
32
|
+
var SAFE_INT_BOUND = 9007199254740991n;
|
|
33
|
+
function narrowSafeBigInt(value) {
|
|
34
|
+
if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
|
|
35
|
+
return Number(value);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function narrowRowIntegers(row) {
|
|
40
|
+
if (row === null || typeof row !== "object") return row;
|
|
41
|
+
const obj = row;
|
|
42
|
+
for (const key of Object.keys(obj)) {
|
|
43
|
+
const value = obj[key];
|
|
44
|
+
if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
|
|
45
|
+
obj[key] = Number(value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return row;
|
|
49
|
+
}
|
|
50
|
+
function narrowRowsIntegers(rows) {
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
narrowRowIntegers(row);
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
function findPackageRoot(startDir) {
|
|
57
|
+
let dir = startDir;
|
|
58
|
+
for (; ; ) {
|
|
59
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
60
|
+
const parent = dirname(dir);
|
|
61
|
+
if (parent === dir) {
|
|
62
|
+
throw new SirannonError(
|
|
63
|
+
"Could not locate the package root to spawn the writer worker",
|
|
64
|
+
"WRITER_WORKER_UNRESOLVED"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
dir = parent;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function resolveWorkerScript() {
|
|
71
|
+
const selfPath = fileURLToPath(import.meta.url);
|
|
72
|
+
const root = findPackageRoot(dirname(selfPath));
|
|
73
|
+
const bootstrap = join(root, "src", "core", "worker", "dev-bootstrap.mjs");
|
|
74
|
+
const built = join(root, "dist", "core", "writer-worker.mjs");
|
|
75
|
+
const runningFromSource = selfPath.startsWith(`${join(root, "src")}${sep}`);
|
|
76
|
+
const preferred = runningFromSource ? bootstrap : built;
|
|
77
|
+
const fallback = runningFromSource ? built : bootstrap;
|
|
78
|
+
if (existsSync(preferred)) return { url: pathToFileURL(preferred), execArgv: [] };
|
|
79
|
+
if (existsSync(fallback)) return { url: pathToFileURL(fallback), execArgv: [] };
|
|
80
|
+
throw new SirannonError(
|
|
81
|
+
`Writer-worker script not found under '${root}'. Build the package or run from source.`,
|
|
82
|
+
"WRITER_WORKER_UNRESOLVED"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/core/worker/host.ts
|
|
87
|
+
var DEFAULT_WRITE_TIMEOUT_MS = 3e4;
|
|
88
|
+
var DEFAULT_MAX_RESTARTS = 5;
|
|
89
|
+
var CLOSE_GRACE_MS = 1e3;
|
|
90
|
+
var WriterWorker = class _WriterWorker {
|
|
91
|
+
constructor(openRequest, timeoutMs, maxRestarts) {
|
|
92
|
+
this.openRequest = openRequest;
|
|
93
|
+
this.timeoutMs = timeoutMs;
|
|
94
|
+
this.maxRestarts = maxRestarts;
|
|
95
|
+
this.connection = this.makeConnection();
|
|
96
|
+
}
|
|
97
|
+
worker = null;
|
|
98
|
+
pending = /* @__PURE__ */ new Map();
|
|
99
|
+
nextId = 1;
|
|
100
|
+
ready = Promise.resolve();
|
|
101
|
+
starting = true;
|
|
102
|
+
closed = false;
|
|
103
|
+
fatal = null;
|
|
104
|
+
restarts = 0;
|
|
105
|
+
connection;
|
|
106
|
+
static async start(entry, path, options, workerOptions) {
|
|
107
|
+
const worker = new _WriterWorker(
|
|
108
|
+
{ kind: "open", entry, path, options },
|
|
109
|
+
workerOptions?.writeTimeoutMs ?? DEFAULT_WRITE_TIMEOUT_MS,
|
|
110
|
+
workerOptions?.maxRestarts ?? DEFAULT_MAX_RESTARTS
|
|
111
|
+
);
|
|
112
|
+
worker.spawn();
|
|
113
|
+
try {
|
|
114
|
+
await worker.ready;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
await worker.close();
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
worker.starting = false;
|
|
120
|
+
return worker;
|
|
121
|
+
}
|
|
122
|
+
spawn() {
|
|
123
|
+
const script = resolveWorkerScript();
|
|
124
|
+
const worker = new Worker(script.url, { execArgv: script.execArgv });
|
|
125
|
+
this.worker = worker;
|
|
126
|
+
worker.on("message", (res) => {
|
|
127
|
+
if (this.worker === worker) this.onResponse(res);
|
|
128
|
+
});
|
|
129
|
+
worker.on("error", (err) => {
|
|
130
|
+
if (this.worker === worker) this.fault(err);
|
|
131
|
+
});
|
|
132
|
+
worker.on("exit", (code) => {
|
|
133
|
+
if (this.worker === worker && !this.closed) {
|
|
134
|
+
this.fault(new SirannonError(`Writer worker exited with code ${code}`, "WRITER_WORKER_EXIT"));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.ready = this.send(this.openRequest).then(() => void 0);
|
|
138
|
+
this.ready.catch(() => {
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
onResponse(res) {
|
|
142
|
+
const entry = this.pending.get(res.id);
|
|
143
|
+
if (!entry) return;
|
|
144
|
+
this.pending.delete(res.id);
|
|
145
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
146
|
+
if (res.ok) entry.resolve(res.value);
|
|
147
|
+
else entry.reject(deserializeError(res.error));
|
|
148
|
+
}
|
|
149
|
+
rejectPending(id, err) {
|
|
150
|
+
const entry = this.pending.get(id);
|
|
151
|
+
if (!entry) return;
|
|
152
|
+
this.pending.delete(id);
|
|
153
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
154
|
+
entry.reject(err);
|
|
155
|
+
}
|
|
156
|
+
fault(errLike) {
|
|
157
|
+
if (this.closed || this.fatal) return;
|
|
158
|
+
const err = errLike instanceof Error ? errLike : new SirannonError(String(errLike), "WRITER_WORKER_ERROR");
|
|
159
|
+
const dead = this.worker;
|
|
160
|
+
this.worker = null;
|
|
161
|
+
for (const entry of this.pending.values()) {
|
|
162
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
163
|
+
entry.reject(err);
|
|
164
|
+
}
|
|
165
|
+
this.pending.clear();
|
|
166
|
+
dead?.terminate().catch(() => {
|
|
167
|
+
});
|
|
168
|
+
if (this.starting) {
|
|
169
|
+
this.fatal = err;
|
|
170
|
+
this.ready = Promise.reject(err);
|
|
171
|
+
this.ready.catch(() => {
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.restarts++;
|
|
176
|
+
if (this.restarts > this.maxRestarts) {
|
|
177
|
+
this.fatal = new SirannonError(
|
|
178
|
+
`Writer worker failed ${this.restarts} times and will not restart: ${err.message}`,
|
|
179
|
+
"WRITER_WORKER_FATAL"
|
|
180
|
+
);
|
|
181
|
+
this.ready = Promise.reject(this.fatal);
|
|
182
|
+
this.ready.catch(() => {
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.spawn();
|
|
187
|
+
}
|
|
188
|
+
send(request) {
|
|
189
|
+
const worker = this.worker;
|
|
190
|
+
if (!worker) {
|
|
191
|
+
return Promise.reject(
|
|
192
|
+
this.fatal ?? new SirannonError("Writer worker is unavailable", "WRITER_WORKER_UNAVAILABLE")
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const id = this.nextId++;
|
|
196
|
+
const message = { ...request, id };
|
|
197
|
+
return new Promise((resolve2, reject) => {
|
|
198
|
+
let timer = null;
|
|
199
|
+
if (this.timeoutMs > 0) {
|
|
200
|
+
timer = setTimeout(() => {
|
|
201
|
+
this.rejectPending(
|
|
202
|
+
id,
|
|
203
|
+
new SirannonError(
|
|
204
|
+
`Writer worker did not respond within ${this.timeoutMs}ms; the operation's outcome is unknown`,
|
|
205
|
+
"WRITER_WORKER_TIMEOUT"
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
}, this.timeoutMs);
|
|
209
|
+
timer.unref?.();
|
|
210
|
+
}
|
|
211
|
+
this.pending.set(id, { resolve: resolve2, reject, timer });
|
|
212
|
+
try {
|
|
213
|
+
worker.postMessage(message);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
this.pending.delete(id);
|
|
216
|
+
if (timer) clearTimeout(timer);
|
|
217
|
+
reject(
|
|
218
|
+
new SirannonError(
|
|
219
|
+
`Failed to hand work to the writer worker: ${err instanceof Error ? err.message : String(err)}`,
|
|
220
|
+
"WRITER_WORKER_POST_FAILED"
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
request(request) {
|
|
227
|
+
if (this.fatal) return Promise.reject(this.fatal);
|
|
228
|
+
if (this.closed) return Promise.reject(new SirannonError("Writer worker is closed", "WRITER_WORKER_CLOSED"));
|
|
229
|
+
return this.ready.then(() => this.send(request)).then((value) => {
|
|
230
|
+
this.restarts = 0;
|
|
231
|
+
return value;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
makeConnection() {
|
|
235
|
+
const conn = {
|
|
236
|
+
exec: (sql) => this.request({ kind: "exec", sql }),
|
|
237
|
+
prepare: async (sql) => ({
|
|
238
|
+
all: (...params) => this.request({ kind: "all", sql, params }),
|
|
239
|
+
get: (...params) => this.request({ kind: "get", sql, params }),
|
|
240
|
+
run: (...params) => this.request({ kind: "run", sql, params }),
|
|
241
|
+
allRaw: (...params) => this.request({ kind: "allRaw", sql, params })
|
|
242
|
+
}),
|
|
243
|
+
runBatch: (sql, paramsBatch) => this.request({ kind: "runBatch", sql, paramsBatch }),
|
|
244
|
+
runBatchSummary: (sql, paramsBatch) => this.request({ kind: "runBatchSummary", sql, paramsBatch }),
|
|
245
|
+
runGroup: (units) => this.request({
|
|
246
|
+
kind: "runGroup",
|
|
247
|
+
units: units.map((unit) => ({
|
|
248
|
+
statements: unit.statements.map((statement) => ({
|
|
249
|
+
sql: statement.sql,
|
|
250
|
+
params: statement.params ? [...statement.params] : []
|
|
251
|
+
}))
|
|
252
|
+
}))
|
|
253
|
+
}),
|
|
254
|
+
transaction: async (fn) => {
|
|
255
|
+
await conn.exec("BEGIN");
|
|
256
|
+
try {
|
|
257
|
+
const result = await fn(conn);
|
|
258
|
+
await conn.exec("COMMIT");
|
|
259
|
+
return result;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
try {
|
|
262
|
+
await conn.exec("ROLLBACK");
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
close: () => this.close()
|
|
269
|
+
};
|
|
270
|
+
return conn;
|
|
271
|
+
}
|
|
272
|
+
async close() {
|
|
273
|
+
if (this.closed) return;
|
|
274
|
+
this.closed = true;
|
|
275
|
+
const worker = this.worker;
|
|
276
|
+
if (worker) {
|
|
277
|
+
try {
|
|
278
|
+
await Promise.race([
|
|
279
|
+
this.send({ kind: "close" }),
|
|
280
|
+
new Promise((_, reject) => {
|
|
281
|
+
const timer = setTimeout(() => reject(new Error("close timed out")), CLOSE_GRACE_MS);
|
|
282
|
+
timer.unref?.();
|
|
283
|
+
})
|
|
284
|
+
]);
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
await worker.terminate().catch(() => {
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
this.worker = null;
|
|
291
|
+
for (const entry of this.pending.values()) {
|
|
292
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
293
|
+
entry.reject(new SirannonError("Writer worker is closed", "WRITER_WORKER_CLOSED"));
|
|
294
|
+
}
|
|
295
|
+
this.pending.clear();
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
function nodeWriterContext() {
|
|
299
|
+
const held = new AsyncLocalStorage();
|
|
300
|
+
return {
|
|
301
|
+
run: (operation) => held.run(true, operation),
|
|
302
|
+
isActive: () => held.getStore() === true,
|
|
303
|
+
exit: (operation) => held.exit(operation)
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function nodeResolveExtensionPath(extensionPath) {
|
|
307
|
+
return resolve(extensionPath);
|
|
308
|
+
}
|
|
309
|
+
function nodeBackupEngine() {
|
|
310
|
+
const manager = new BackupManager();
|
|
311
|
+
const scheduler = new BackupScheduler(manager);
|
|
312
|
+
return {
|
|
313
|
+
backup: (conn, destPath) => manager.backup(conn, destPath),
|
|
314
|
+
schedule: (conn, options, runExclusive) => scheduler.schedule(conn, options, runExclusive)
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export { WriterWorker, createStatementCache, narrowRowIntegers, narrowRowsIntegers, narrowSafeBigInt, nodeBackupEngine, nodeResolveExtensionPath, nodeWriterContext };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SirannonError } from './chunk-YPYVQJ4C.mjs';
|
|
2
|
+
|
|
3
|
+
// src/core/driver/define.ts
|
|
4
|
+
function defineDriver(config) {
|
|
5
|
+
if (!config.capabilities || typeof config.open !== "function") {
|
|
6
|
+
throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
|
|
7
|
+
}
|
|
8
|
+
if (config.worker !== void 0 && typeof config.worker.specifier !== "string") {
|
|
9
|
+
throw new SirannonError("Driver worker entry must define a string specifier", "INVALID_DRIVER");
|
|
10
|
+
}
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
capabilities: Object.freeze({ ...config.capabilities }),
|
|
13
|
+
open: config.open,
|
|
14
|
+
...config.worker ? { worker: Object.freeze({ ...config.worker }) } : {},
|
|
15
|
+
...config.startWriterHost ? { startWriterHost: config.startWriterHost } : {},
|
|
16
|
+
...config.createWriterContext ? { createWriterContext: config.createWriterContext } : {},
|
|
17
|
+
...config.createBackupEngine ? { createBackupEngine: config.createBackupEngine } : {},
|
|
18
|
+
...config.resolveExtensionPath ? { resolveExtensionPath: config.resolveExtensionPath } : {}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { defineDriver };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SirannonError } from './chunk-YPYVQJ4C.mjs';
|
|
2
|
+
|
|
3
|
+
// src/core/driver/synchronous.ts
|
|
4
|
+
var DEFAULT_SYNCHRONOUS = "normal";
|
|
5
|
+
var SYNCHRONOUS_PRAGMA_VALUES = {
|
|
6
|
+
off: "OFF",
|
|
7
|
+
normal: "NORMAL",
|
|
8
|
+
full: "FULL",
|
|
9
|
+
extra: "EXTRA"
|
|
10
|
+
};
|
|
11
|
+
function synchronousPragmaValue(level) {
|
|
12
|
+
const resolved = level ?? DEFAULT_SYNCHRONOUS;
|
|
13
|
+
const value = SYNCHRONOUS_PRAGMA_VALUES[resolved];
|
|
14
|
+
if (value === void 0) {
|
|
15
|
+
throw new SirannonError(
|
|
16
|
+
`Invalid synchronous level '${String(resolved)}': expected 'off', 'normal', 'full', or 'extra'`,
|
|
17
|
+
"INVALID_SYNCHRONOUS"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function isSynchronousLevel(value) {
|
|
23
|
+
return value === "off" || value === "normal" || value === "full" || value === "extra";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { DEFAULT_SYNCHRONOUS, isSynchronousLevel, synchronousPragmaValue };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { assertSqlAllowed, encodeWireRowsInPlace } from './chunk-GEZUUIKV.mjs';
|
|
2
|
+
import { SirannonError, QueryError } from './chunk-YPYVQJ4C.mjs';
|
|
3
|
+
|
|
4
|
+
// src/core/query-executor.ts
|
|
5
|
+
var STATEMENT_CACHE_CAPACITY = 128;
|
|
6
|
+
var statementCaches = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
async function getStatement(conn, sql) {
|
|
8
|
+
let cache = statementCaches.get(conn);
|
|
9
|
+
if (!cache) {
|
|
10
|
+
cache = /* @__PURE__ */ new Map();
|
|
11
|
+
statementCaches.set(conn, cache);
|
|
12
|
+
}
|
|
13
|
+
const cached = cache.get(sql);
|
|
14
|
+
if (cached) {
|
|
15
|
+
cache.delete(sql);
|
|
16
|
+
cache.set(sql, cached);
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
19
|
+
const pending = conn.prepare(sql);
|
|
20
|
+
cache.set(sql, pending);
|
|
21
|
+
if (cache.size > STATEMENT_CACHE_CAPACITY) {
|
|
22
|
+
const oldest = cache.keys().next().value;
|
|
23
|
+
if (oldest !== void 0) {
|
|
24
|
+
cache.delete(oldest);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return await pending;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
cache.delete(sql);
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function bindParams(params) {
|
|
35
|
+
if (params === void 0) return [];
|
|
36
|
+
if (Array.isArray(params)) return params;
|
|
37
|
+
return [params];
|
|
38
|
+
}
|
|
39
|
+
function asQueryError(err, sql) {
|
|
40
|
+
if (err instanceof SirannonError) return err;
|
|
41
|
+
return new QueryError(err instanceof Error ? err.message : String(err), sql);
|
|
42
|
+
}
|
|
43
|
+
async function query(conn, sql, params) {
|
|
44
|
+
assertSqlAllowed(sql);
|
|
45
|
+
try {
|
|
46
|
+
const stmt = await getStatement(conn, sql);
|
|
47
|
+
return await stmt.all(...bindParams(params));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw asQueryError(err, sql);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function queryForWire(conn, sql, params) {
|
|
53
|
+
assertSqlAllowed(sql);
|
|
54
|
+
try {
|
|
55
|
+
const stmt = await getStatement(conn, sql);
|
|
56
|
+
const bound = bindParams(params);
|
|
57
|
+
const rows = stmt.allRaw ? await stmt.allRaw(...bound) : await stmt.all(...bound);
|
|
58
|
+
return encodeWireRowsInPlace(rows);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
throw asQueryError(err, sql);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function queryOne(conn, sql, params) {
|
|
64
|
+
assertSqlAllowed(sql);
|
|
65
|
+
try {
|
|
66
|
+
const stmt = await getStatement(conn, sql);
|
|
67
|
+
return await stmt.get(...bindParams(params));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
throw asQueryError(err, sql);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function execute(conn, sql, params) {
|
|
73
|
+
assertSqlAllowed(sql);
|
|
74
|
+
try {
|
|
75
|
+
const stmt = await getStatement(conn, sql);
|
|
76
|
+
const result = await stmt.run(...bindParams(params));
|
|
77
|
+
return {
|
|
78
|
+
changes: result.changes,
|
|
79
|
+
lastInsertRowId: result.lastInsertRowId
|
|
80
|
+
};
|
|
81
|
+
} catch (err) {
|
|
82
|
+
throw asQueryError(err, sql);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function executeGroup(conn, units) {
|
|
86
|
+
const outcomes = new Array(units.length);
|
|
87
|
+
try {
|
|
88
|
+
await conn.exec("BEGIN");
|
|
89
|
+
for (let i = 0; i < units.length; i++) {
|
|
90
|
+
outcomes[i] = { ok: true, values: await runUnit(conn, units[i]) };
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
await rollbackQuietly(conn);
|
|
94
|
+
return executeGroupIsolated(conn, units);
|
|
95
|
+
}
|
|
96
|
+
const failure = await execControl(conn, "COMMIT");
|
|
97
|
+
if (!failure) return outcomes;
|
|
98
|
+
await rollbackQuietly(conn);
|
|
99
|
+
return outcomes.map(() => ({ ok: false, error: failure }));
|
|
100
|
+
}
|
|
101
|
+
async function runUnit(conn, unit) {
|
|
102
|
+
const values = new Array(unit.statements.length);
|
|
103
|
+
for (let i = 0; i < unit.statements.length; i++) {
|
|
104
|
+
const statement = unit.statements[i];
|
|
105
|
+
values[i] = await execute(conn, statement.sql, statement.params);
|
|
106
|
+
}
|
|
107
|
+
return values;
|
|
108
|
+
}
|
|
109
|
+
function controlError(err, sql) {
|
|
110
|
+
return asQueryError(err, sql);
|
|
111
|
+
}
|
|
112
|
+
async function execControl(conn, sql) {
|
|
113
|
+
try {
|
|
114
|
+
await conn.exec(sql);
|
|
115
|
+
return null;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return controlError(err, sql);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function rollbackQuietly(conn) {
|
|
121
|
+
try {
|
|
122
|
+
await conn.exec("ROLLBACK");
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function executeGroupIsolated(conn, units) {
|
|
127
|
+
const outcomes = new Array(units.length);
|
|
128
|
+
await conn.exec("BEGIN");
|
|
129
|
+
for (let i = 0; i < units.length; i++) {
|
|
130
|
+
const attempt = await runIsolatedUnit(conn, units[i], `sirannon_gc_${i}`);
|
|
131
|
+
outcomes[i] = attempt.outcome;
|
|
132
|
+
if (!attempt.contained) return rerunSurvivorsAlone(conn, units, outcomes, i);
|
|
133
|
+
}
|
|
134
|
+
const failure = await execControl(conn, "COMMIT");
|
|
135
|
+
if (!failure) return outcomes;
|
|
136
|
+
await rollbackQuietly(conn);
|
|
137
|
+
return outcomes.map((outcome) => outcome.ok ? { ok: false, error: failure } : outcome);
|
|
138
|
+
}
|
|
139
|
+
async function runIsolatedUnit(conn, unit, savepoint) {
|
|
140
|
+
const notOpened = await execControl(conn, `SAVEPOINT ${savepoint}`);
|
|
141
|
+
if (notOpened) return { outcome: { ok: false, error: notOpened }, contained: false };
|
|
142
|
+
let values;
|
|
143
|
+
try {
|
|
144
|
+
values = await runUnit(conn, unit);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return { outcome: { ok: false, error: err }, contained: await unwind(conn, savepoint) };
|
|
147
|
+
}
|
|
148
|
+
const notReleased = await execControl(conn, `RELEASE ${savepoint}`);
|
|
149
|
+
if (notReleased) return { outcome: { ok: false, error: notReleased }, contained: false };
|
|
150
|
+
return { outcome: { ok: true, values }, contained: true };
|
|
151
|
+
}
|
|
152
|
+
async function unwind(conn, savepoint) {
|
|
153
|
+
if (await execControl(conn, `ROLLBACK TO ${savepoint}`)) return false;
|
|
154
|
+
return await execControl(conn, `RELEASE ${savepoint}`) === null;
|
|
155
|
+
}
|
|
156
|
+
async function rerunSurvivorsAlone(conn, units, outcomes, failed) {
|
|
157
|
+
await rollbackQuietly(conn);
|
|
158
|
+
for (let i = 0; i < units.length; i++) {
|
|
159
|
+
if (i === failed) continue;
|
|
160
|
+
outcomes[i] = await runUnitAlone(conn, units[i]);
|
|
161
|
+
}
|
|
162
|
+
return outcomes;
|
|
163
|
+
}
|
|
164
|
+
async function runUnitAlone(conn, unit) {
|
|
165
|
+
const notBegun = await execControl(conn, "BEGIN");
|
|
166
|
+
if (notBegun) return { ok: false, error: notBegun };
|
|
167
|
+
let values;
|
|
168
|
+
try {
|
|
169
|
+
values = await runUnit(conn, unit);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
await rollbackQuietly(conn);
|
|
172
|
+
return { ok: false, error: err };
|
|
173
|
+
}
|
|
174
|
+
const notCommitted = await execControl(conn, "COMMIT");
|
|
175
|
+
if (!notCommitted) return { ok: true, values };
|
|
176
|
+
await rollbackQuietly(conn);
|
|
177
|
+
return { ok: false, error: notCommitted };
|
|
178
|
+
}
|
|
179
|
+
async function forEachBatchRow(conn, sql, paramsBatch, sink) {
|
|
180
|
+
assertSqlAllowed(sql);
|
|
181
|
+
try {
|
|
182
|
+
const stmt = await getStatement(conn, sql);
|
|
183
|
+
for (const params of paramsBatch) {
|
|
184
|
+
const result = await stmt.run(...bindParams(params));
|
|
185
|
+
sink(result.changes, result.lastInsertRowId);
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
throw asQueryError(err, sql);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async function executeBatch(conn, sql, paramsBatch) {
|
|
192
|
+
if (conn.runBatch) {
|
|
193
|
+
assertSqlAllowed(sql);
|
|
194
|
+
try {
|
|
195
|
+
return await conn.runBatch(sql, paramsBatch.map(bindParams));
|
|
196
|
+
} catch (err) {
|
|
197
|
+
throw asQueryError(err, sql);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const results = [];
|
|
201
|
+
await forEachBatchRow(conn, sql, paramsBatch, (changes, lastInsertRowId) => {
|
|
202
|
+
results.push({ changes, lastInsertRowId });
|
|
203
|
+
});
|
|
204
|
+
return results;
|
|
205
|
+
}
|
|
206
|
+
async function executeBatchSummary(conn, sql, paramsBatch) {
|
|
207
|
+
if (conn.runBatchSummary) {
|
|
208
|
+
assertSqlAllowed(sql);
|
|
209
|
+
try {
|
|
210
|
+
return await conn.runBatchSummary(sql, paramsBatch.map(bindParams));
|
|
211
|
+
} catch (err) {
|
|
212
|
+
throw asQueryError(err, sql);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let changes = 0;
|
|
216
|
+
await forEachBatchRow(conn, sql, paramsBatch, (rowChanges) => {
|
|
217
|
+
changes += rowChanges;
|
|
218
|
+
});
|
|
219
|
+
return { rowsLoaded: paramsBatch.length, changes };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { bindParams, execute, executeBatch, executeBatchSummary, executeGroup, query, queryForWire, queryOne };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/core/worker/protocol.ts
|
|
2
|
+
function serializeError(err) {
|
|
3
|
+
if (err instanceof Error) {
|
|
4
|
+
const code = err.code;
|
|
5
|
+
return {
|
|
6
|
+
message: err.message,
|
|
7
|
+
name: err.name,
|
|
8
|
+
...typeof code === "string" ? { code } : {}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return { message: String(err) };
|
|
12
|
+
}
|
|
13
|
+
function deserializeError(error) {
|
|
14
|
+
const err = new Error(error.message);
|
|
15
|
+
if (error.name) err.name = error.name;
|
|
16
|
+
if (error.code) err.code = error.code;
|
|
17
|
+
return err;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { deserializeError, serializeError };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// src/core/errors.ts
|
|
2
|
+
var SirannonError = class extends Error {
|
|
3
|
+
constructor(message, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = "SirannonError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/core/driver/define.ts
|
|
11
|
+
function defineDriver(config) {
|
|
12
|
+
if (!config.capabilities || typeof config.open !== "function") {
|
|
13
|
+
throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
|
|
14
|
+
}
|
|
15
|
+
if (config.worker !== void 0 && typeof config.worker.specifier !== "string") {
|
|
16
|
+
throw new SirannonError("Driver worker entry must define a string specifier", "INVALID_DRIVER");
|
|
17
|
+
}
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
capabilities: Object.freeze({ ...config.capabilities }),
|
|
20
|
+
open: config.open,
|
|
21
|
+
...config.worker ? { worker: Object.freeze({ ...config.worker }) } : {},
|
|
22
|
+
...config.startWriterHost ? { startWriterHost: config.startWriterHost } : {},
|
|
23
|
+
...config.createWriterContext ? { createWriterContext: config.createWriterContext } : {},
|
|
24
|
+
...config.createBackupEngine ? { createBackupEngine: config.createBackupEngine } : {},
|
|
25
|
+
...config.resolveExtensionPath ? { resolveExtensionPath: config.resolveExtensionPath } : {}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/core/driver/synchronous.ts
|
|
30
|
+
var DEFAULT_SYNCHRONOUS = "normal";
|
|
31
|
+
var SYNCHRONOUS_PRAGMA_VALUES = {
|
|
32
|
+
off: "OFF",
|
|
33
|
+
normal: "NORMAL",
|
|
34
|
+
full: "FULL",
|
|
35
|
+
extra: "EXTRA"
|
|
36
|
+
};
|
|
37
|
+
function synchronousPragmaValue(level) {
|
|
38
|
+
const resolved = level ?? DEFAULT_SYNCHRONOUS;
|
|
39
|
+
const value = SYNCHRONOUS_PRAGMA_VALUES[resolved];
|
|
40
|
+
if (value === void 0) {
|
|
41
|
+
throw new SirannonError(
|
|
42
|
+
`Invalid synchronous level '${String(resolved)}': expected 'off', 'normal', 'full', or 'extra'`,
|
|
43
|
+
"INVALID_SYNCHRONOUS"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { defineDriver, synchronousPragmaValue };
|