@mcp-b/do-runtime 0.1.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 +14 -0
- package/LICENSE +110 -0
- package/LICENSE.workerd +176 -0
- package/NOTICE +7 -0
- package/README.md +282 -0
- package/dist/backends/node-sqlite.d.ts +38 -0
- package/dist/backends/node-sqlite.js +335 -0
- package/dist/backends/node-sqlite.js.map +1 -0
- package/dist/backends/sqlite-wasm.d.ts +130 -0
- package/dist/backends/sqlite-wasm.js +259 -0
- package/dist/backends/sqlite-wasm.js.map +1 -0
- package/dist/chunks/sqlite-DFg92Tgt.js +498 -0
- package/dist/chunks/sqlite-DFg92Tgt.js.map +1 -0
- package/dist/cloudflare-workers.js +351 -0
- package/dist/cloudflare-workers.js.map +1 -0
- package/dist/conformance/host.d.ts +58 -0
- package/dist/conformance.js +18 -0
- package/dist/conformance.js.map +1 -0
- package/dist/index.js +7184 -0
- package/dist/index.js.map +1 -0
- package/dist/server/alarm-scheduler.js +513 -0
- package/dist/server/alarm-scheduler.js.map +1 -0
- package/dist/src/api/actor-state.d.ts +396 -0
- package/dist/src/api/actor.d.ts +306 -0
- package/dist/src/api/cloudflare-workers.d.ts +259 -0
- package/dist/src/api/export-loopback.d.ts +264 -0
- package/dist/src/api/global-scope.d.ts +262 -0
- package/dist/src/api/http.d.ts +52 -0
- package/dist/src/api/sql.d.ts +188 -0
- package/dist/src/api/sync-kv.d.ts +51 -0
- package/dist/src/api/web-socket.d.ts +93 -0
- package/dist/src/api/worker-loader.d.ts +354 -0
- package/dist/src/index.d.ts +130 -0
- package/dist/src/io/actor-cache.d.ts +203 -0
- package/dist/src/io/actor-id.d.ts +74 -0
- package/dist/src/io/actor-sqlite.d.ts +298 -0
- package/dist/src/io/io-channels.d.ts +191 -0
- package/dist/src/io/io-context.d.ts +451 -0
- package/dist/src/io/io-gate.d.ts +298 -0
- package/dist/src/io/worker-source.d.ts +108 -0
- package/dist/src/io/worker.d.ts +88 -0
- package/dist/src/server/actor-container.d.ts +525 -0
- package/dist/src/server/actor-id-impl.d.ts +118 -0
- package/dist/src/server/alarm-scheduler.d.ts +201 -0
- package/dist/src/server/facet-deletion.d.ts +156 -0
- package/dist/src/server/facet-tree-index.d.ts +94 -0
- package/dist/src/server/sha256.d.ts +39 -0
- package/dist/src/transport/rpc-session.d.ts +34 -0
- package/dist/src/util/sqlite-kv.d.ts +98 -0
- package/dist/src/util/sqlite-metadata.d.ts +46 -0
- package/dist/src/util/sqlite.d.ts +291 -0
- package/package.json +111 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { d as requireValidSqlDatabaseSnapshot, l as requireSafeDatabaseName, n as SQL_WRONG_BINDINGS_MESSAGE, u as requireSqliteLength } from "../chunks/sqlite-DFg92Tgt.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { rmSync } from "node:fs";
|
|
4
|
+
import { readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { DatabaseSync } from "node:sqlite";
|
|
7
|
+
//#region backends/node-sqlite.ts
|
|
8
|
+
/**
|
|
9
|
+
* ← workerd `NO upstream correspondence (storage-backend adaptation)`
|
|
10
|
+
*
|
|
11
|
+
* `SqlDatabaseProvider` over `node:sqlite`. Promoted out of
|
|
12
|
+
* `host/fixtures/storage-node.ts`, which is already this adapter.
|
|
13
|
+
*
|
|
14
|
+
* Upstream's equivalent is `SqliteDatabase`'s binding to the SQLite C API plus
|
|
15
|
+
* its kj-filesystem VFS — 3,768 lines this package deliberately does not port,
|
|
16
|
+
* because `node:sqlite` and sqlite-wasm play that role underneath us. What has
|
|
17
|
+
* to match is the layer above: the SQL that `sqlite-kv` and `sqlite-metadata`
|
|
18
|
+
* write, and the four operations they need from a database.
|
|
19
|
+
*
|
|
20
|
+
* This is the substrate the unit lane runs on. It is also decision 11's Node
|
|
21
|
+
* conformance lane, and `fixtures/storage-node.ts` already proves the seam
|
|
22
|
+
* across 20 of the extension's 24 Node-lane test files.
|
|
23
|
+
*/
|
|
24
|
+
function createNodeSqlProvider(options = {}) {
|
|
25
|
+
const directory = options.directory;
|
|
26
|
+
const openDatabases = /* @__PURE__ */ new Set();
|
|
27
|
+
return {
|
|
28
|
+
async open(name) {
|
|
29
|
+
requireSafeDatabaseName(name);
|
|
30
|
+
const path = directory === void 0 ? ":memory:" : join(directory, `${name}.sqlite`);
|
|
31
|
+
let database;
|
|
32
|
+
database = new NodeSqlDatabase(path, () => openDatabases.delete(database));
|
|
33
|
+
openDatabases.add(database);
|
|
34
|
+
return database;
|
|
35
|
+
},
|
|
36
|
+
close() {
|
|
37
|
+
for (const database of [...openDatabases]) database.close();
|
|
38
|
+
},
|
|
39
|
+
async exportSnapshot() {
|
|
40
|
+
const path = requireSnapshotDirectory(directory);
|
|
41
|
+
requireClosed(openDatabases);
|
|
42
|
+
const files = await readdir(path);
|
|
43
|
+
requireNoRecoverySidecars(files);
|
|
44
|
+
const names = files.filter((file) => file.endsWith(".sqlite")).map((file) => file.slice(0, -7)).sort();
|
|
45
|
+
names.forEach(requireSafeDatabaseName);
|
|
46
|
+
const snapshot = {
|
|
47
|
+
version: 1,
|
|
48
|
+
databases: await Promise.all(names.map(async (name) => ({
|
|
49
|
+
name,
|
|
50
|
+
image: new Uint8Array(await readFile(join(path, `${name}.sqlite`)))
|
|
51
|
+
})))
|
|
52
|
+
};
|
|
53
|
+
requireValidSqlDatabaseSnapshot(snapshot);
|
|
54
|
+
return snapshot;
|
|
55
|
+
},
|
|
56
|
+
async importSnapshot(snapshot) {
|
|
57
|
+
const path = requireSnapshotDirectory(directory);
|
|
58
|
+
requireClosed(openDatabases);
|
|
59
|
+
requireValidSqlDatabaseSnapshot(snapshot);
|
|
60
|
+
const databases = snapshot.databases.map(({ name, image }) => ({
|
|
61
|
+
name,
|
|
62
|
+
image: new Uint8Array(image)
|
|
63
|
+
}));
|
|
64
|
+
const temporary = [];
|
|
65
|
+
try {
|
|
66
|
+
for (const { name, image } of databases) {
|
|
67
|
+
const file = join(path, `.${name}.${randomUUID()}.restore`);
|
|
68
|
+
await writeFile(file, image);
|
|
69
|
+
temporary.push({
|
|
70
|
+
name,
|
|
71
|
+
file
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const existing = await readdir(path);
|
|
75
|
+
existing.filter((file) => file.endsWith(".sqlite")).map((file) => file.slice(0, -7)).forEach(requireSafeDatabaseName);
|
|
76
|
+
for (const file of existing) if (/\.sqlite-(?:journal|wal|shm)$/.test(file)) await rm(join(path, file), { force: true });
|
|
77
|
+
for (const { name, file } of temporary) await rename(file, join(path, `${name}.sqlite`));
|
|
78
|
+
const restored = new Set(databases.map(({ name }) => `${name}.sqlite`));
|
|
79
|
+
for (const file of existing) if (file.endsWith(".sqlite") && !restored.has(file)) await rm(join(path, file), { force: true });
|
|
80
|
+
} finally {
|
|
81
|
+
await Promise.all(temporary.map(({ file }) => rm(file, { force: true })));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
var NodeSqlDatabase = class {
|
|
87
|
+
onClose;
|
|
88
|
+
#path;
|
|
89
|
+
#database;
|
|
90
|
+
#closed = false;
|
|
91
|
+
constructor(path, onClose = () => {}) {
|
|
92
|
+
this.onClose = onClose;
|
|
93
|
+
this.#path = path;
|
|
94
|
+
this.#database = new DatabaseSync(path);
|
|
95
|
+
}
|
|
96
|
+
prepare(sql) {
|
|
97
|
+
const statement = this.#database.prepare(sql);
|
|
98
|
+
const source = statement.sourceSQL;
|
|
99
|
+
return new NodeSqlStatement(statement, source, parameterLayout(source), () => this.#totalChanges());
|
|
100
|
+
}
|
|
101
|
+
exec(sql, params) {
|
|
102
|
+
const statement = this.prepare(sql);
|
|
103
|
+
try {
|
|
104
|
+
return statement.execute(params);
|
|
105
|
+
} finally {
|
|
106
|
+
statement.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
get databaseSize() {
|
|
110
|
+
return this.#pragma("page_count") * this.#pragma("page_size");
|
|
111
|
+
}
|
|
112
|
+
/** `node:sqlite`'s own name for `sqlite3_get_autocommit(db) == 0`. */
|
|
113
|
+
get inTransaction() {
|
|
114
|
+
return this.#database.isTransaction;
|
|
115
|
+
}
|
|
116
|
+
reset() {
|
|
117
|
+
this.#database.close();
|
|
118
|
+
if (this.#path !== ":memory:") for (const suffix of [
|
|
119
|
+
"",
|
|
120
|
+
"-journal",
|
|
121
|
+
"-wal",
|
|
122
|
+
"-shm"
|
|
123
|
+
]) rmSync(`${this.#path}${suffix}`, { force: true });
|
|
124
|
+
this.#database = new DatabaseSync(this.#path);
|
|
125
|
+
}
|
|
126
|
+
close() {
|
|
127
|
+
if (this.#closed) return;
|
|
128
|
+
this.#database.close();
|
|
129
|
+
this.#closed = true;
|
|
130
|
+
this.onClose();
|
|
131
|
+
}
|
|
132
|
+
#pragma(name) {
|
|
133
|
+
const value = this.#database.prepare(`PRAGMA ${name}`).get()?.[name];
|
|
134
|
+
if (typeof value !== "number") throw new Error(`PRAGMA ${name} did not return a number.`);
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
#totalChanges() {
|
|
138
|
+
const value = this.#database.prepare("SELECT total_changes() AS value").get()?.value;
|
|
139
|
+
if (typeof value !== "number" && typeof value !== "bigint") throw new Error("total_changes() did not return a number.");
|
|
140
|
+
return Number(value);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var NodeSqlStatement = class {
|
|
144
|
+
sql;
|
|
145
|
+
totalChanges;
|
|
146
|
+
#statement;
|
|
147
|
+
#layout;
|
|
148
|
+
constructor(statement, sql, layout, totalChanges) {
|
|
149
|
+
this.sql = sql;
|
|
150
|
+
this.totalChanges = totalChanges;
|
|
151
|
+
this.#statement = statement;
|
|
152
|
+
this.#layout = layout;
|
|
153
|
+
}
|
|
154
|
+
get parameterCount() {
|
|
155
|
+
return this.#layout.count;
|
|
156
|
+
}
|
|
157
|
+
execute(params) {
|
|
158
|
+
if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);
|
|
159
|
+
params.forEach(requireSqliteLength);
|
|
160
|
+
const { named, anonymous } = bindParameters(params, this.#layout);
|
|
161
|
+
const statement = this.#statement;
|
|
162
|
+
const columns = statement.columns();
|
|
163
|
+
if (columns.length === 0) {
|
|
164
|
+
const { changes } = named === void 0 ? statement.run(...anonymous) : statement.run(named, ...anonymous);
|
|
165
|
+
return {
|
|
166
|
+
columnNames: [],
|
|
167
|
+
rawRows: [],
|
|
168
|
+
rowsWritten: Number(changes)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
statement.setReadBigInts(true);
|
|
172
|
+
statement.setReturnArrays(true);
|
|
173
|
+
const changesBefore = this.totalChanges();
|
|
174
|
+
const rows = named === void 0 ? statement.all(...anonymous) : statement.all(named, ...anonymous);
|
|
175
|
+
return {
|
|
176
|
+
columnNames: columns.map((column) => column.name),
|
|
177
|
+
rawRows: rows.map(asRow),
|
|
178
|
+
rowsWritten: this.totalChanges() - changesBefore
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
close() {}
|
|
182
|
+
};
|
|
183
|
+
function bindParameters(params, layout) {
|
|
184
|
+
let named;
|
|
185
|
+
const anonymous = [];
|
|
186
|
+
for (let index = 1; index <= layout.count; index += 1) {
|
|
187
|
+
const value = params[index - 1];
|
|
188
|
+
if (value === void 0) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);
|
|
189
|
+
const name = layout.namesByIndex.get(index);
|
|
190
|
+
if (name === void 0) anonymous.push(value);
|
|
191
|
+
else {
|
|
192
|
+
named ??= {};
|
|
193
|
+
named[name] = value;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
named,
|
|
198
|
+
anonymous
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* `StatementSync` does not expose `sqlite3_bind_parameter_count()`. This is the
|
|
203
|
+
* one lexical adaptation left in the Node backend: it recognizes only SQLite
|
|
204
|
+
* parameter tokens and lets `DatabaseSync.prepare()` validate every other bit
|
|
205
|
+
* of SQL, including statement boundaries.
|
|
206
|
+
*/
|
|
207
|
+
function parameterLayout(sql) {
|
|
208
|
+
let nextIndex = 1;
|
|
209
|
+
const indexByName = /* @__PURE__ */ new Map();
|
|
210
|
+
const namesByIndex = /* @__PURE__ */ new Map();
|
|
211
|
+
for (let index = 0; index < sql.length;) {
|
|
212
|
+
const char = sql.charAt(index);
|
|
213
|
+
if (char === "'" || char === "\"" || char === "`") {
|
|
214
|
+
index = skipQuoted(sql, index, char, true);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (char === "[") {
|
|
218
|
+
index = skipQuoted(sql, index, "]", false);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (char === "-" && sql[index + 1] === "-") {
|
|
222
|
+
const newline = sql.indexOf("\n", index + 2);
|
|
223
|
+
index = newline === -1 ? sql.length : newline + 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (char === "/" && sql[index + 1] === "*") {
|
|
227
|
+
const close = sql.indexOf("*/", index + 2);
|
|
228
|
+
index = close === -1 ? sql.length : close + 2;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (char === "?") {
|
|
232
|
+
let end = index + 1;
|
|
233
|
+
while (isAsciiDigit(sql.charAt(end))) end += 1;
|
|
234
|
+
if (end === index + 1) nextIndex += 1;
|
|
235
|
+
else {
|
|
236
|
+
const explicit = Number(sql.slice(index + 1, end));
|
|
237
|
+
nextIndex = Math.max(nextIndex, explicit + 1);
|
|
238
|
+
}
|
|
239
|
+
index = end;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if ((char === ":" || char === "@" || char === "$") && isParameterChar(sql.charAt(index + 1))) {
|
|
243
|
+
const end = parameterNameEnd(sql, index);
|
|
244
|
+
const name = sql.slice(index, end);
|
|
245
|
+
let parameterIndex = indexByName.get(name);
|
|
246
|
+
if (parameterIndex === void 0) {
|
|
247
|
+
parameterIndex = nextIndex;
|
|
248
|
+
nextIndex += 1;
|
|
249
|
+
indexByName.set(name, parameterIndex);
|
|
250
|
+
namesByIndex.set(parameterIndex, name);
|
|
251
|
+
}
|
|
252
|
+
index = end;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
index += 1;
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
count: nextIndex - 1,
|
|
259
|
+
namesByIndex
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function parameterNameEnd(sql, start) {
|
|
263
|
+
let index = start + 1;
|
|
264
|
+
while (isParameterChar(sql.charAt(index))) index += 1;
|
|
265
|
+
if (sql[start] === "$") {
|
|
266
|
+
while (sql.slice(index, index + 2) === "::" && isParameterChar(sql.charAt(index + 2))) {
|
|
267
|
+
index += 2;
|
|
268
|
+
while (isParameterChar(sql.charAt(index))) index += 1;
|
|
269
|
+
}
|
|
270
|
+
if (sql[index] === "(") {
|
|
271
|
+
const close = sql.indexOf(")", index + 1);
|
|
272
|
+
if (close !== -1) index = close + 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return index;
|
|
276
|
+
}
|
|
277
|
+
function isAsciiDigit(char) {
|
|
278
|
+
return char >= "0" && char <= "9";
|
|
279
|
+
}
|
|
280
|
+
function isParameterChar(char) {
|
|
281
|
+
return char >= "A" && char <= "Z" || char >= "a" && char <= "z" || isAsciiDigit(char) || char === "_" || char.charCodeAt(0) >= 128;
|
|
282
|
+
}
|
|
283
|
+
function skipQuoted(sql, open, close, doubled) {
|
|
284
|
+
let index = open + 1;
|
|
285
|
+
while (index < sql.length) {
|
|
286
|
+
if (sql[index] === close) {
|
|
287
|
+
if (doubled && sql[index + 1] === close) {
|
|
288
|
+
index += 2;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
return index + 1;
|
|
292
|
+
}
|
|
293
|
+
index += 1;
|
|
294
|
+
}
|
|
295
|
+
return sql.length;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `setReturnArrays(true)` is what makes a row a `rawRow`, and the driver's types
|
|
299
|
+
* describe the object shape either way. Checking rather than asserting keeps a
|
|
300
|
+
* future driver that ignores the flag from handing objects to `getText`.
|
|
301
|
+
*/
|
|
302
|
+
function asRow(row) {
|
|
303
|
+
if (Array.isArray(row)) {
|
|
304
|
+
row.forEach(requireSqliteLength);
|
|
305
|
+
return row.map(normalizeInteger);
|
|
306
|
+
}
|
|
307
|
+
throw new Error("node:sqlite returned a non-array row despite setReturnArrays(true).");
|
|
308
|
+
}
|
|
309
|
+
/** Preserve ordinary numeric rows while keeping the full int64 range until the public API. */
|
|
310
|
+
function normalizeInteger(value) {
|
|
311
|
+
if (typeof value !== "bigint") return value;
|
|
312
|
+
const number = Number(value);
|
|
313
|
+
return Number.isSafeInteger(number) && BigInt(number) === value ? number : value;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Names come from inside the package (`"root"`, `` `facet-${facetId}` ``), so
|
|
317
|
+
* this is defence in depth rather than input validation — but it is the one
|
|
318
|
+
* place a name becomes a path, and a silent traversal here writes an actor's
|
|
319
|
+
* storage somewhere nobody will look for it.
|
|
320
|
+
*/
|
|
321
|
+
function requireSnapshotDirectory(directory) {
|
|
322
|
+
if (directory === void 0) throw new Error("SQLite snapshots require a directory-backed Node provider.");
|
|
323
|
+
return directory;
|
|
324
|
+
}
|
|
325
|
+
function requireClosed(openDatabases) {
|
|
326
|
+
if (openDatabases.size > 0) throw new Error("Cannot snapshot or restore while database handles are open.");
|
|
327
|
+
}
|
|
328
|
+
function requireNoRecoverySidecars(files) {
|
|
329
|
+
const sidecar = files.find((file) => /\.sqlite-(?:journal|wal|shm)$/.test(file));
|
|
330
|
+
if (sidecar !== void 0) throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);
|
|
331
|
+
}
|
|
332
|
+
//#endregion
|
|
333
|
+
export { NodeSqlDatabase, createNodeSqlProvider };
|
|
334
|
+
|
|
335
|
+
//# sourceMappingURL=node-sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-sqlite.js","names":["#path","#database","#totalChanges","#pragma","#closed","#statement","#layout"],"sources":["../../backends/node-sqlite.ts"],"sourcesContent":["/**\n * ← workerd `NO upstream correspondence (storage-backend adaptation)`\n *\n * `SqlDatabaseProvider` over `node:sqlite`. Promoted out of\n * `host/fixtures/storage-node.ts`, which is already this adapter.\n *\n * Upstream's equivalent is `SqliteDatabase`'s binding to the SQLite C API plus\n * its kj-filesystem VFS — 3,768 lines this package deliberately does not port,\n * because `node:sqlite` and sqlite-wasm play that role underneath us. What has\n * to match is the layer above: the SQL that `sqlite-kv` and `sqlite-metadata`\n * write, and the four operations they need from a database.\n *\n * This is the substrate the unit lane runs on. It is also decision 11's Node\n * conformance lane, and `fixtures/storage-node.ts` already proves the seam\n * across 20 of the extension's 24 Node-lane test files.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { rmSync } from \"node:fs\";\nimport { readFile, readdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SQLInputValue, StatementSync } from \"node:sqlite\";\nimport {\n requireSqliteLength,\n requireSafeDatabaseName,\n requireValidSqlDatabaseSnapshot,\n SQL_WRONG_BINDINGS_MESSAGE,\n type SqlDatabase,\n type SqlDatabaseSnapshot,\n type SqlDatabaseSnapshotProvider,\n type SqlDatabaseStatement,\n type SqlResult,\n type SqlValue,\n} from \"../src/util/sqlite\";\n\nexport type NodeSqlProviderOptions = {\n /**\n * Dedicated directory for one actor's database files. Omit for in-memory\n * databases, which cannot be snapshotted and are what the unit lane and\n * upstream's own tests get from `kj::newInMemoryDirectory`.\n */\n directory?: string;\n};\n\nexport function createNodeSqlProvider(\n options: NodeSqlProviderOptions = {},\n): SqlDatabaseSnapshotProvider {\n const directory = options.directory;\n const openDatabases = new Set<NodeSqlDatabase>();\n return {\n async open(name: string): Promise<SqlDatabase> {\n requireSafeDatabaseName(name);\n const path = directory === undefined ? \":memory:\" : join(directory, `${name}.sqlite`);\n let database: NodeSqlDatabase;\n database = new NodeSqlDatabase(path, () => openDatabases.delete(database));\n openDatabases.add(database);\n return database;\n },\n close(): void {\n for (const database of [...openDatabases]) database.close();\n },\n async exportSnapshot(): Promise<SqlDatabaseSnapshot> {\n const path = requireSnapshotDirectory(directory);\n requireClosed(openDatabases);\n const files = await readdir(path);\n requireNoRecoverySidecars(files);\n const names = files\n .filter((file) => file.endsWith(\".sqlite\"))\n .map((file) => file.slice(0, -\".sqlite\".length))\n .sort();\n names.forEach(requireSafeDatabaseName);\n const snapshot: SqlDatabaseSnapshot = {\n version: 1,\n databases: await Promise.all(\n names.map(async (name) => ({\n name,\n image: new Uint8Array(await readFile(join(path, `${name}.sqlite`))),\n })),\n ),\n };\n requireValidSqlDatabaseSnapshot(snapshot);\n return snapshot;\n },\n async importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void> {\n const path = requireSnapshotDirectory(directory);\n requireClosed(openDatabases);\n requireValidSqlDatabaseSnapshot(snapshot);\n\n const databases = snapshot.databases.map(({ name, image }) => ({\n name,\n image: new Uint8Array(image),\n }));\n const temporary: { name: string; file: string }[] = [];\n try {\n for (const { name, image } of databases) {\n const file = join(path, `.${name}.${randomUUID()}.restore`);\n await writeFile(file, image);\n temporary.push({ name, file });\n }\n const existing = await readdir(path);\n existing\n .filter((file) => file.endsWith(\".sqlite\"))\n .map((file) => file.slice(0, -\".sqlite\".length))\n .forEach(requireSafeDatabaseName);\n for (const file of existing) {\n if (/\\.sqlite-(?:journal|wal|shm)$/.test(file)) await rm(join(path, file), { force: true });\n }\n for (const { name, file } of temporary) {\n await rename(file, join(path, `${name}.sqlite`));\n }\n const restored = new Set(databases.map(({ name }) => `${name}.sqlite`));\n for (const file of existing) {\n if (file.endsWith(\".sqlite\") && !restored.has(file)) {\n await rm(join(path, file), { force: true });\n }\n }\n } finally {\n await Promise.all(temporary.map(({ file }) => rm(file, { force: true })));\n }\n },\n };\n}\n\nexport class NodeSqlDatabase implements SqlDatabase {\n readonly #path: string;\n #database: DatabaseSync;\n\n #closed = false;\n\n constructor(path: string, private readonly onClose: () => void = () => {}) {\n this.#path = path;\n this.#database = new DatabaseSync(path);\n }\n\n prepare(sql: string): SqlDatabaseStatement {\n const statement = this.#database.prepare(sql);\n const source = statement.sourceSQL;\n return new NodeSqlStatement(statement, source, parameterLayout(source), () =>\n this.#totalChanges(),\n );\n }\n\n exec(sql: string, params: readonly SqlValue[]): SqlResult {\n const statement = this.prepare(sql);\n try {\n return statement.execute(params);\n } finally {\n statement.close();\n }\n }\n\n get databaseSize(): number {\n const pageCount = this.#pragma(\"page_count\");\n const pageSize = this.#pragma(\"page_size\");\n return pageCount * pageSize;\n }\n\n /** `node:sqlite`'s own name for `sqlite3_get_autocommit(db) == 0`. */\n get inTransaction(): boolean {\n return this.#database.isTransaction;\n }\n\n reset(): void {\n this.#database.close();\n if (this.#path !== \":memory:\") {\n // The journal and WAL sidecars are part of the database; leaving one\n // behind would have the reopened file replay a transaction from the\n // database that was just deleted.\n for (const suffix of [\"\", \"-journal\", \"-wal\", \"-shm\"]) {\n rmSync(`${this.#path}${suffix}`, { force: true });\n }\n }\n this.#database = new DatabaseSync(this.#path);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#database.close();\n this.#closed = true;\n this.onClose();\n }\n\n #pragma(name: string): number {\n const row = this.#database.prepare(`PRAGMA ${name}`).get();\n const value = row?.[name];\n if (typeof value !== \"number\") {\n throw new Error(`PRAGMA ${name} did not return a number.`);\n }\n return value;\n }\n\n #totalChanges(): number {\n const row = this.#database.prepare(\"SELECT total_changes() AS value\").get();\n const value = row?.value;\n if (typeof value !== \"number\" && typeof value !== \"bigint\") {\n throw new Error(\"total_changes() did not return a number.\");\n }\n return Number(value);\n }\n}\n\ntype ParameterLayout = {\n readonly count: number;\n readonly namesByIndex: ReadonlyMap<number, string>;\n};\n\nclass NodeSqlStatement implements SqlDatabaseStatement {\n readonly #statement: StatementSync;\n readonly #layout: ParameterLayout;\n\n constructor(\n statement: StatementSync,\n readonly sql: string,\n layout: ParameterLayout,\n private readonly totalChanges: () => number,\n ) {\n this.#statement = statement;\n this.#layout = layout;\n }\n\n get parameterCount(): number {\n return this.#layout.count;\n }\n\n execute(params: readonly SqlValue[]): SqlResult {\n if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n // ponytail: node:sqlite exposes no sqlite3_limit(); guard JS inputs and outputs here,\n // and replace this with the native limit if Node exposes it.\n params.forEach(requireSqliteLength);\n\n const { named, anonymous } = bindParameters(params, this.#layout);\n const statement = this.#statement;\n const columns = statement.columns();\n\n if (columns.length === 0) {\n const { changes } =\n named === undefined ? statement.run(...anonymous) : statement.run(named, ...anonymous);\n return { columnNames: [], rawRows: [], rowsWritten: Number(changes) };\n }\n\n statement.setReadBigInts(true);\n statement.setReturnArrays(true);\n const changesBefore = this.totalChanges();\n const rows: unknown[] =\n named === undefined ? statement.all(...anonymous) : statement.all(named, ...anonymous);\n return {\n columnNames: columns.map((column) => column.name),\n rawRows: rows.map(asRow),\n // `node:sqlite` exposes no sqlite3_stmt_readonly() or per-statement write\n // counter. The total-change delta distinguishes SELECT from DML RETURNING\n // without parsing SQL or executing the statement twice.\n rowsWritten: this.totalChanges() - changesBefore,\n };\n }\n\n close(): void {\n // `StatementSync` exposes no finalize operation. Its native handle follows\n // the lifetime of this short-lived object instead.\n }\n}\n\nfunction bindParameters(\n params: readonly SqlValue[],\n layout: ParameterLayout,\n): {\n named: Record<string, SQLInputValue> | undefined;\n anonymous: SQLInputValue[];\n} {\n let named: Record<string, SQLInputValue> | undefined;\n const anonymous: SQLInputValue[] = [];\n for (let index = 1; index <= layout.count; index += 1) {\n const value = params[index - 1];\n if (value === undefined) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n const name = layout.namesByIndex.get(index);\n if (name === undefined) {\n anonymous.push(value);\n } else {\n named ??= {};\n named[name] = value;\n }\n }\n return { named, anonymous };\n}\n\n/**\n * `StatementSync` does not expose `sqlite3_bind_parameter_count()`. This is the\n * one lexical adaptation left in the Node backend: it recognizes only SQLite\n * parameter tokens and lets `DatabaseSync.prepare()` validate every other bit\n * of SQL, including statement boundaries.\n */\nfunction parameterLayout(sql: string): ParameterLayout {\n let nextIndex = 1;\n const indexByName = new Map<string, number>();\n const namesByIndex = new Map<number, string>();\n\n for (let index = 0; index < sql.length; ) {\n const char = sql.charAt(index);\n if (char === \"'\" || char === '\"' || char === \"`\") {\n index = skipQuoted(sql, index, char, true);\n continue;\n }\n if (char === \"[\") {\n index = skipQuoted(sql, index, \"]\", false);\n continue;\n }\n if (char === \"-\" && sql[index + 1] === \"-\") {\n const newline = sql.indexOf(\"\\n\", index + 2);\n index = newline === -1 ? sql.length : newline + 1;\n continue;\n }\n if (char === \"/\" && sql[index + 1] === \"*\") {\n const close = sql.indexOf(\"*/\", index + 2);\n index = close === -1 ? sql.length : close + 2;\n continue;\n }\n if (char === \"?\") {\n let end = index + 1;\n while (isAsciiDigit(sql.charAt(end))) end += 1;\n if (end === index + 1) {\n nextIndex += 1;\n } else {\n const explicit = Number(sql.slice(index + 1, end));\n nextIndex = Math.max(nextIndex, explicit + 1);\n }\n index = end;\n continue;\n }\n if ((char === \":\" || char === \"@\" || char === \"$\") && isParameterChar(sql.charAt(index + 1))) {\n const end = parameterNameEnd(sql, index);\n const name = sql.slice(index, end);\n let parameterIndex = indexByName.get(name);\n if (parameterIndex === undefined) {\n parameterIndex = nextIndex;\n nextIndex += 1;\n indexByName.set(name, parameterIndex);\n namesByIndex.set(parameterIndex, name);\n }\n index = end;\n continue;\n }\n index += 1;\n }\n\n return { count: nextIndex - 1, namesByIndex };\n}\n\nfunction parameterNameEnd(sql: string, start: number): number {\n let index = start + 1;\n while (isParameterChar(sql.charAt(index))) index += 1;\n\n // SQLite's `$name` form also accepts Tcl-style `::suffix` and `(suffix)`.\n if (sql[start] === \"$\") {\n while (sql.slice(index, index + 2) === \"::\" && isParameterChar(sql.charAt(index + 2))) {\n index += 2;\n while (isParameterChar(sql.charAt(index))) index += 1;\n }\n if (sql[index] === \"(\") {\n const close = sql.indexOf(\")\", index + 1);\n if (close !== -1) index = close + 1;\n }\n }\n return index;\n}\n\nfunction isAsciiDigit(char: string): boolean {\n return char >= \"0\" && char <= \"9\";\n}\n\nfunction isParameterChar(char: string): boolean {\n return (\n (char >= \"A\" && char <= \"Z\") ||\n (char >= \"a\" && char <= \"z\") ||\n isAsciiDigit(char) ||\n char === \"_\" ||\n char.charCodeAt(0) >= 0x80\n );\n}\n\nfunction skipQuoted(sql: string, open: number, close: string, doubled: boolean): number {\n let index = open + 1;\n while (index < sql.length) {\n if (sql[index] === close) {\n if (doubled && sql[index + 1] === close) {\n index += 2;\n continue;\n }\n return index + 1;\n }\n index += 1;\n }\n return sql.length;\n}\n\n/**\n * `setReturnArrays(true)` is what makes a row a `rawRow`, and the driver's types\n * describe the object shape either way. Checking rather than asserting keeps a\n * future driver that ignores the flag from handing objects to `getText`.\n */\nfunction asRow(row: unknown): readonly unknown[] {\n if (Array.isArray(row)) {\n row.forEach(requireSqliteLength);\n return row.map(normalizeInteger);\n }\n throw new Error(\"node:sqlite returned a non-array row despite setReturnArrays(true).\");\n}\n\n/** Preserve ordinary numeric rows while keeping the full int64 range until the public API. */\nfunction normalizeInteger(value: unknown): unknown {\n if (typeof value !== \"bigint\") return value;\n const number = Number(value);\n return Number.isSafeInteger(number) && BigInt(number) === value ? number : value;\n}\n\n/**\n * Names come from inside the package (`\"root\"`, `` `facet-${facetId}` ``), so\n * this is defence in depth rather than input validation — but it is the one\n * place a name becomes a path, and a silent traversal here writes an actor's\n * storage somewhere nobody will look for it.\n */\nfunction requireSnapshotDirectory(directory: string | undefined): string {\n if (directory === undefined) {\n throw new Error(\"SQLite snapshots require a directory-backed Node provider.\");\n }\n return directory;\n}\n\nfunction requireClosed(openDatabases: ReadonlySet<NodeSqlDatabase>): void {\n if (openDatabases.size > 0) {\n throw new Error(\"Cannot snapshot or restore while database handles are open.\");\n }\n}\n\nfunction requireNoRecoverySidecars(files: readonly string[]): void {\n const sidecar = files.find((file) => /\\.sqlite-(?:journal|wal|shm)$/.test(file));\n if (sidecar !== undefined) {\n throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `NO upstream correspondence (storage-backend adaptation)`
|
|
3
|
+
*
|
|
4
|
+
* `SqlDatabaseProvider` over the browser's OPFS SAH pool.
|
|
5
|
+
*
|
|
6
|
+
* The pool is a parameter, not something this module goes and gets. Installing
|
|
7
|
+
* the VFS is the host's job — `installOpfsSAHPoolVfs` decides the OPFS
|
|
8
|
+
* directory, the pool capacity and whether to clear on init, all of which are
|
|
9
|
+
* layout questions this package deliberately knows nothing about. What arrives
|
|
10
|
+
* here is the already-installed pool, and with it the two things a backend
|
|
11
|
+
* needs that a bare `sqlite3` module cannot give: a database constructor bound
|
|
12
|
+
* to that VFS, plus the pool's file export/import/unlink operations used by
|
|
13
|
+
* snapshots and `reset()`.
|
|
14
|
+
*
|
|
15
|
+
* The pool is structurally typed rather than imported from
|
|
16
|
+
* `@sqlite.org/sqlite-wasm`, so this package takes no dependency on the driver
|
|
17
|
+
* and the caller is free to pass a pool from any build of it. The shape below
|
|
18
|
+
* is the subset of `SAHPoolUtil` and `oo1.DB` that is used, copied from the
|
|
19
|
+
* driver's own `.d.mts`.
|
|
20
|
+
*
|
|
21
|
+
* NOT exercised by the unit lane: it needs OPFS, which means a browser. It is
|
|
22
|
+
* exercised twice in the browser lane — by `sqlite-wasm.smoke.spec.ts`, which
|
|
23
|
+
* drives this file directly, and by the conformance suite, which runs the whole
|
|
24
|
+
* package over it.
|
|
25
|
+
*/
|
|
26
|
+
import { type SqlDatabase, type SqlDatabaseProvider, type SqlDatabaseSnapshotProvider, type SqlDatabaseStatement, type SqlResult, type SqlValue } from "../src/util/sqlite.js";
|
|
27
|
+
/** ← `PreparedStatement`, the members used here. */
|
|
28
|
+
export interface SqliteWasmStatement {
|
|
29
|
+
readonly columnCount: number;
|
|
30
|
+
readonly parameterCount: number;
|
|
31
|
+
bind(bindings: readonly (string | number | bigint | null | Uint8Array)[]): unknown;
|
|
32
|
+
step(): boolean;
|
|
33
|
+
get(index: number): unknown;
|
|
34
|
+
getColumnNames(target?: string[]): string[];
|
|
35
|
+
finalize(): number | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** ← `oo1.DB` / `OpfsSAHPoolDatabase`, the members used here. */
|
|
38
|
+
export interface SqliteWasmDatabaseHandle {
|
|
39
|
+
/** ← `oo1.DB.pointer`, which is absent once the handle is closed. */
|
|
40
|
+
readonly pointer?: number | undefined;
|
|
41
|
+
prepare(sql: string): SqliteWasmStatement;
|
|
42
|
+
changes(total?: boolean, sixtyFour?: false): number;
|
|
43
|
+
close(): void;
|
|
44
|
+
}
|
|
45
|
+
/** ← `SAHPoolUtil`, the members used here. */
|
|
46
|
+
export interface OpfsSahPool {
|
|
47
|
+
/** Constructs a database inside this pool's VFS. Names are absolute, so they start with "/". */
|
|
48
|
+
readonly OpfsSAHPoolDb: new (filename: string) => SqliteWasmDatabaseHandle;
|
|
49
|
+
exportFile(filename: string): Uint8Array | Promise<Uint8Array>;
|
|
50
|
+
importDb(filename: string, image: Uint8Array): number | Promise<number>;
|
|
51
|
+
getFileNames(): string[];
|
|
52
|
+
/** Disassociates a virtual file from the pool. Results are undefined if it is in active use. */
|
|
53
|
+
unlink(filename: string): boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* ← `Sqlite3Static["capi"]`, restricted to the one C function `oo1.DB` does not
|
|
57
|
+
* wrap.
|
|
58
|
+
*
|
|
59
|
+
* Takes the pointer rather than the handle, even though upstream's `DbPtr`
|
|
60
|
+
* accepts either: a structural subset of `oo1.DB` is not assignable to the
|
|
61
|
+
* `Database` class, so asking for the handle would make the real `capi` fail to
|
|
62
|
+
* satisfy this interface.
|
|
63
|
+
*/
|
|
64
|
+
export interface SqliteWasmCapi {
|
|
65
|
+
readonly SQLITE_LIMIT_LENGTH: number;
|
|
66
|
+
sqlite3_complete(sql: string): 0 | 1;
|
|
67
|
+
sqlite3_get_autocommit(db: number): number;
|
|
68
|
+
sqlite3_limit(db: number, id: number, newValue: number): number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What the host hands over: the pool it installed, and the C-API namespace it
|
|
72
|
+
* already holds. Both come off the same `sqlite3` object the caller used to
|
|
73
|
+
* call `installOpfsSAHPoolVfs`, so this asks for nothing it does not have.
|
|
74
|
+
*/
|
|
75
|
+
export interface SqliteWasmHost {
|
|
76
|
+
readonly pool: OpfsSahPool;
|
|
77
|
+
readonly capi: SqliteWasmCapi;
|
|
78
|
+
}
|
|
79
|
+
export type SqliteWasmProviderOptions = {
|
|
80
|
+
/** Absolute path prefix inside the pool, e.g. `/actor-<id>`. Must start with "/". */
|
|
81
|
+
prefix: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* One actor's named databases and their file lifecycle inside an OPFS SAH pool.
|
|
85
|
+
*
|
|
86
|
+
* A root or facet container only needs the `SqlDatabaseProvider` surface. Its
|
|
87
|
+
* host also has to close every connection when that placement dies, remove the
|
|
88
|
+
* prefix on delete, and copy every database on clone. Those file operations
|
|
89
|
+
* belong here because SAH-pool files are virtual and can only be reached through
|
|
90
|
+
* the pool that owns them.
|
|
91
|
+
*/
|
|
92
|
+
export declare class SqliteWasmActorStorage implements SqlDatabaseProvider {
|
|
93
|
+
#private;
|
|
94
|
+
constructor(host: SqliteWasmHost, prefix: string);
|
|
95
|
+
open(name: string): Promise<SqlDatabase>;
|
|
96
|
+
/**
|
|
97
|
+
* Drop every handle. Leaving one behind per respawn or facet abort would
|
|
98
|
+
* accumulate concurrent writers inside a VFS that expects to own its files.
|
|
99
|
+
*/
|
|
100
|
+
close(): void;
|
|
101
|
+
/** Close every handle, then physically remove every database under this prefix. */
|
|
102
|
+
deleteAll(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Replace this prefix with every database under `source`, including files
|
|
105
|
+
* from an earlier placement that this session never opened.
|
|
106
|
+
*
|
|
107
|
+
* The source may still be running, so this uses the pool's file operations
|
|
108
|
+
* rather than the snapshot API, which correctly refuses open handles. A
|
|
109
|
+
* recovery sidecar means the bytes are not a stable database image and is
|
|
110
|
+
* refused before the destination is touched. Every source image is also
|
|
111
|
+
* exported before replacement starts, so a failed read preserves the target.
|
|
112
|
+
*/
|
|
113
|
+
copyFrom(source: SqliteWasmActorStorage): Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
export declare function createSqliteWasmProvider(host: SqliteWasmHost, options: SqliteWasmProviderOptions): SqlDatabaseSnapshotProvider;
|
|
116
|
+
export declare class SqliteWasmDatabase implements SqlDatabase {
|
|
117
|
+
#private;
|
|
118
|
+
private readonly onClose;
|
|
119
|
+
constructor(host: SqliteWasmHost, filename: string, onClose?: () => void);
|
|
120
|
+
prepare(sql: string): SqlDatabaseStatement;
|
|
121
|
+
exec(sql: string, params: readonly SqlValue[]): SqlResult;
|
|
122
|
+
get databaseSize(): number;
|
|
123
|
+
/**
|
|
124
|
+
* `oo1.DB` wraps no equivalent, so this is the one place the backend reaches
|
|
125
|
+
* past it into the C API. `DbPtr` accepts the database object itself.
|
|
126
|
+
*/
|
|
127
|
+
get inTransaction(): boolean;
|
|
128
|
+
reset(): void;
|
|
129
|
+
close(): void;
|
|
130
|
+
}
|