@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +110 -0
  3. package/LICENSE.workerd +176 -0
  4. package/NOTICE +7 -0
  5. package/README.md +282 -0
  6. package/dist/backends/node-sqlite.d.ts +38 -0
  7. package/dist/backends/node-sqlite.js +335 -0
  8. package/dist/backends/node-sqlite.js.map +1 -0
  9. package/dist/backends/sqlite-wasm.d.ts +130 -0
  10. package/dist/backends/sqlite-wasm.js +259 -0
  11. package/dist/backends/sqlite-wasm.js.map +1 -0
  12. package/dist/chunks/sqlite-DFg92Tgt.js +498 -0
  13. package/dist/chunks/sqlite-DFg92Tgt.js.map +1 -0
  14. package/dist/cloudflare-workers.js +351 -0
  15. package/dist/cloudflare-workers.js.map +1 -0
  16. package/dist/conformance/host.d.ts +58 -0
  17. package/dist/conformance.js +18 -0
  18. package/dist/conformance.js.map +1 -0
  19. package/dist/index.js +7184 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/server/alarm-scheduler.js +513 -0
  22. package/dist/server/alarm-scheduler.js.map +1 -0
  23. package/dist/src/api/actor-state.d.ts +396 -0
  24. package/dist/src/api/actor.d.ts +306 -0
  25. package/dist/src/api/cloudflare-workers.d.ts +259 -0
  26. package/dist/src/api/export-loopback.d.ts +264 -0
  27. package/dist/src/api/global-scope.d.ts +262 -0
  28. package/dist/src/api/http.d.ts +52 -0
  29. package/dist/src/api/sql.d.ts +188 -0
  30. package/dist/src/api/sync-kv.d.ts +51 -0
  31. package/dist/src/api/web-socket.d.ts +93 -0
  32. package/dist/src/api/worker-loader.d.ts +354 -0
  33. package/dist/src/index.d.ts +130 -0
  34. package/dist/src/io/actor-cache.d.ts +203 -0
  35. package/dist/src/io/actor-id.d.ts +74 -0
  36. package/dist/src/io/actor-sqlite.d.ts +298 -0
  37. package/dist/src/io/io-channels.d.ts +191 -0
  38. package/dist/src/io/io-context.d.ts +451 -0
  39. package/dist/src/io/io-gate.d.ts +298 -0
  40. package/dist/src/io/worker-source.d.ts +108 -0
  41. package/dist/src/io/worker.d.ts +88 -0
  42. package/dist/src/server/actor-container.d.ts +525 -0
  43. package/dist/src/server/actor-id-impl.d.ts +118 -0
  44. package/dist/src/server/alarm-scheduler.d.ts +201 -0
  45. package/dist/src/server/facet-deletion.d.ts +156 -0
  46. package/dist/src/server/facet-tree-index.d.ts +94 -0
  47. package/dist/src/server/sha256.d.ts +39 -0
  48. package/dist/src/transport/rpc-session.d.ts +34 -0
  49. package/dist/src/util/sqlite-kv.d.ts +98 -0
  50. package/dist/src/util/sqlite-metadata.d.ts +46 -0
  51. package/dist/src/util/sqlite.d.ts +291 -0
  52. 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
+ }