@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,259 @@
|
|
|
1
|
+
import { d as requireValidSqlDatabaseSnapshot, l as requireSafeDatabaseName, n as SQL_WRONG_BINDINGS_MESSAGE, t as SQLITE_LENGTH_LIMIT, u as requireSqliteLength } from "../chunks/sqlite-DFg92Tgt.js";
|
|
2
|
+
//#region backends/sqlite-wasm.ts
|
|
3
|
+
/**
|
|
4
|
+
* ← workerd `NO upstream correspondence (storage-backend adaptation)`
|
|
5
|
+
*
|
|
6
|
+
* `SqlDatabaseProvider` over the browser's OPFS SAH pool.
|
|
7
|
+
*
|
|
8
|
+
* The pool is a parameter, not something this module goes and gets. Installing
|
|
9
|
+
* the VFS is the host's job — `installOpfsSAHPoolVfs` decides the OPFS
|
|
10
|
+
* directory, the pool capacity and whether to clear on init, all of which are
|
|
11
|
+
* layout questions this package deliberately knows nothing about. What arrives
|
|
12
|
+
* here is the already-installed pool, and with it the two things a backend
|
|
13
|
+
* needs that a bare `sqlite3` module cannot give: a database constructor bound
|
|
14
|
+
* to that VFS, plus the pool's file export/import/unlink operations used by
|
|
15
|
+
* snapshots and `reset()`.
|
|
16
|
+
*
|
|
17
|
+
* The pool is structurally typed rather than imported from
|
|
18
|
+
* `@sqlite.org/sqlite-wasm`, so this package takes no dependency on the driver
|
|
19
|
+
* and the caller is free to pass a pool from any build of it. The shape below
|
|
20
|
+
* is the subset of `SAHPoolUtil` and `oo1.DB` that is used, copied from the
|
|
21
|
+
* driver's own `.d.mts`.
|
|
22
|
+
*
|
|
23
|
+
* NOT exercised by the unit lane: it needs OPFS, which means a browser. It is
|
|
24
|
+
* exercised twice in the browser lane — by `sqlite-wasm.smoke.spec.ts`, which
|
|
25
|
+
* drives this file directly, and by the conformance suite, which runs the whole
|
|
26
|
+
* package over it.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* One actor's named databases and their file lifecycle inside an OPFS SAH pool.
|
|
30
|
+
*
|
|
31
|
+
* A root or facet container only needs the `SqlDatabaseProvider` surface. Its
|
|
32
|
+
* host also has to close every connection when that placement dies, remove the
|
|
33
|
+
* prefix on delete, and copy every database on clone. Those file operations
|
|
34
|
+
* belong here because SAH-pool files are virtual and can only be reached through
|
|
35
|
+
* the pool that owns them.
|
|
36
|
+
*/
|
|
37
|
+
var SqliteWasmActorStorage = class {
|
|
38
|
+
#host;
|
|
39
|
+
#prefix;
|
|
40
|
+
#provider;
|
|
41
|
+
constructor(host, prefix) {
|
|
42
|
+
this.#host = host;
|
|
43
|
+
this.#prefix = prefix;
|
|
44
|
+
this.#provider = createSqliteWasmProvider(host, { prefix });
|
|
45
|
+
}
|
|
46
|
+
open(name) {
|
|
47
|
+
return this.#provider.open(name);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Drop every handle. Leaving one behind per respawn or facet abort would
|
|
51
|
+
* accumulate concurrent writers inside a VFS that expects to own its files.
|
|
52
|
+
*/
|
|
53
|
+
close() {
|
|
54
|
+
this.#provider.close();
|
|
55
|
+
}
|
|
56
|
+
/** Close every handle, then physically remove every database under this prefix. */
|
|
57
|
+
deleteAll() {
|
|
58
|
+
this.close();
|
|
59
|
+
for (const file of this.#ownedFiles()) if (!this.#host.pool.unlink(file)) throw new Error(`SAH pool did not unlink ${file}`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Replace this prefix with every database under `source`, including files
|
|
63
|
+
* from an earlier placement that this session never opened.
|
|
64
|
+
*
|
|
65
|
+
* The source may still be running, so this uses the pool's file operations
|
|
66
|
+
* rather than the snapshot API, which correctly refuses open handles. A
|
|
67
|
+
* recovery sidecar means the bytes are not a stable database image and is
|
|
68
|
+
* refused before the destination is touched. Every source image is also
|
|
69
|
+
* exported before replacement starts, so a failed read preserves the target.
|
|
70
|
+
*/
|
|
71
|
+
async copyFrom(source) {
|
|
72
|
+
const files = source.#ownedFiles();
|
|
73
|
+
const sidecar = files.find((file) => !file.endsWith(".sqlite"));
|
|
74
|
+
if (sidecar !== void 0) throw new Error(`Cannot clone actor storage with a SQLite recovery sidecar: ${sidecar}`);
|
|
75
|
+
const images = [];
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const name = file.slice(source.#prefix.length + 1, -7);
|
|
78
|
+
requireSafeDatabaseName(name);
|
|
79
|
+
images.push({
|
|
80
|
+
name,
|
|
81
|
+
bytes: new Uint8Array(await source.#host.pool.exportFile(file))
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
this.deleteAll();
|
|
85
|
+
for (const { name, bytes } of images) await this.#host.pool.importDb(`${this.#prefix}.${name}.sqlite`, bytes);
|
|
86
|
+
}
|
|
87
|
+
#ownedFiles() {
|
|
88
|
+
return this.#host.pool.getFileNames().filter((name) => name.startsWith(`${this.#prefix}.`));
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function createSqliteWasmProvider(host, options) {
|
|
92
|
+
const { prefix } = options;
|
|
93
|
+
if (!prefix.startsWith("/")) throw new Error(`SAH pool names are absolute; prefix must start with "/": ${prefix}`);
|
|
94
|
+
const openDatabases = /* @__PURE__ */ new Set();
|
|
95
|
+
const ownedFiles = () => host.pool.getFileNames().filter((name) => name.startsWith(`${prefix}.`));
|
|
96
|
+
return {
|
|
97
|
+
async open(name) {
|
|
98
|
+
requireSafeDatabaseName(name);
|
|
99
|
+
let database;
|
|
100
|
+
database = new SqliteWasmDatabase(host, `${prefix}.${name}.sqlite`, () => openDatabases.delete(database));
|
|
101
|
+
openDatabases.add(database);
|
|
102
|
+
return database;
|
|
103
|
+
},
|
|
104
|
+
close() {
|
|
105
|
+
for (const database of [...openDatabases]) database.close();
|
|
106
|
+
},
|
|
107
|
+
async exportSnapshot() {
|
|
108
|
+
requireClosed(openDatabases);
|
|
109
|
+
const files = ownedFiles();
|
|
110
|
+
requireNoRecoverySidecars(files);
|
|
111
|
+
const snapshot = {
|
|
112
|
+
version: 1,
|
|
113
|
+
databases: await Promise.all(files.filter((file) => file.endsWith(".sqlite")).sort().map(async (file) => {
|
|
114
|
+
const name = file.slice(prefix.length + 1, -7);
|
|
115
|
+
requireSafeDatabaseName(name);
|
|
116
|
+
return {
|
|
117
|
+
name,
|
|
118
|
+
image: new Uint8Array(await host.pool.exportFile(file))
|
|
119
|
+
};
|
|
120
|
+
}))
|
|
121
|
+
};
|
|
122
|
+
requireValidSqlDatabaseSnapshot(snapshot);
|
|
123
|
+
return snapshot;
|
|
124
|
+
},
|
|
125
|
+
async importSnapshot(snapshot) {
|
|
126
|
+
requireClosed(openDatabases);
|
|
127
|
+
requireValidSqlDatabaseSnapshot(snapshot);
|
|
128
|
+
for (const file of ownedFiles()) host.pool.unlink(file);
|
|
129
|
+
for (const { name, image } of snapshot.databases) await host.pool.importDb(`${prefix}.${name}.sqlite`, new Uint8Array(image));
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
var SqliteWasmDatabase = class {
|
|
134
|
+
onClose;
|
|
135
|
+
#host;
|
|
136
|
+
#filename;
|
|
137
|
+
#database;
|
|
138
|
+
#closed = false;
|
|
139
|
+
constructor(host, filename, onClose = () => {}) {
|
|
140
|
+
this.onClose = onClose;
|
|
141
|
+
this.#host = host;
|
|
142
|
+
this.#filename = filename;
|
|
143
|
+
this.#database = new host.pool.OpfsSAHPoolDb(filename);
|
|
144
|
+
this.#setLengthLimit();
|
|
145
|
+
}
|
|
146
|
+
prepare(sql) {
|
|
147
|
+
const source = firstCompleteStatement(this.#host.capi, sql);
|
|
148
|
+
return new WasmSqlStatement(this.#database, this.#database.prepare(source), source);
|
|
149
|
+
}
|
|
150
|
+
exec(sql, params) {
|
|
151
|
+
const statement = this.prepare(sql);
|
|
152
|
+
try {
|
|
153
|
+
return statement.execute(params);
|
|
154
|
+
} finally {
|
|
155
|
+
statement.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
get databaseSize() {
|
|
159
|
+
return this.#pragma("page_count") * this.#pragma("page_size");
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `oo1.DB` wraps no equivalent, so this is the one place the backend reaches
|
|
163
|
+
* past it into the C API. `DbPtr` accepts the database object itself.
|
|
164
|
+
*/
|
|
165
|
+
get inTransaction() {
|
|
166
|
+
const pointer = this.#database.pointer;
|
|
167
|
+
if (pointer === void 0) throw new Error("The database handle is closed.");
|
|
168
|
+
return this.#host.capi.sqlite3_get_autocommit(pointer) === 0;
|
|
169
|
+
}
|
|
170
|
+
reset() {
|
|
171
|
+
this.#database.close();
|
|
172
|
+
if (!this.#host.pool.unlink(this.#filename)) throw new Error(`SAH pool did not unlink ${this.#filename}`);
|
|
173
|
+
this.#database = new this.#host.pool.OpfsSAHPoolDb(this.#filename);
|
|
174
|
+
this.#setLengthLimit();
|
|
175
|
+
}
|
|
176
|
+
close() {
|
|
177
|
+
if (this.#closed) return;
|
|
178
|
+
this.#database.close();
|
|
179
|
+
this.#closed = true;
|
|
180
|
+
this.onClose();
|
|
181
|
+
}
|
|
182
|
+
#setLengthLimit() {
|
|
183
|
+
const pointer = this.#database.pointer;
|
|
184
|
+
if (pointer === void 0) throw new Error("The database handle is closed.");
|
|
185
|
+
this.#host.capi.sqlite3_limit(pointer, this.#host.capi.SQLITE_LIMIT_LENGTH, SQLITE_LENGTH_LIMIT);
|
|
186
|
+
}
|
|
187
|
+
#pragma(name) {
|
|
188
|
+
const value = this.exec(`PRAGMA ${name}`, []).rawRows[0]?.[0];
|
|
189
|
+
if (typeof value !== "number") throw new Error(`PRAGMA ${name} did not return a number.`);
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function requireClosed(openDatabases) {
|
|
194
|
+
if (openDatabases.size > 0) throw new Error("Cannot snapshot or restore while database handles are open.");
|
|
195
|
+
}
|
|
196
|
+
function requireNoRecoverySidecars(files) {
|
|
197
|
+
const sidecar = files.find((file) => !file.endsWith(".sqlite"));
|
|
198
|
+
if (sidecar !== void 0) throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);
|
|
199
|
+
}
|
|
200
|
+
var WasmSqlStatement = class {
|
|
201
|
+
sql;
|
|
202
|
+
#database;
|
|
203
|
+
#statement;
|
|
204
|
+
#closed = false;
|
|
205
|
+
constructor(database, statement, sql) {
|
|
206
|
+
this.sql = sql;
|
|
207
|
+
this.#database = database;
|
|
208
|
+
this.#statement = statement;
|
|
209
|
+
}
|
|
210
|
+
get parameterCount() {
|
|
211
|
+
return this.#statement.parameterCount;
|
|
212
|
+
}
|
|
213
|
+
execute(params) {
|
|
214
|
+
if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);
|
|
215
|
+
params.forEach(requireSqliteLength);
|
|
216
|
+
if (params.length > 0) this.#statement.bind(params);
|
|
217
|
+
const columnCount = this.#statement.columnCount;
|
|
218
|
+
if (columnCount === 0) {
|
|
219
|
+
this.#statement.step();
|
|
220
|
+
return {
|
|
221
|
+
columnNames: [],
|
|
222
|
+
rawRows: [],
|
|
223
|
+
rowsWritten: this.#database.changes(false)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const changesBefore = this.#database.changes(true);
|
|
227
|
+
const columnNames = this.#statement.getColumnNames();
|
|
228
|
+
const rawRows = [];
|
|
229
|
+
while (this.#statement.step()) {
|
|
230
|
+
const row = [];
|
|
231
|
+
for (let column = 0; column < columnCount; column += 1) row.push(this.#statement.get(column));
|
|
232
|
+
rawRows.push(row);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
columnNames,
|
|
236
|
+
rawRows,
|
|
237
|
+
rowsWritten: this.#database.changes(true) - changesBefore
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
close() {
|
|
241
|
+
if (this.#closed) return;
|
|
242
|
+
this.#closed = true;
|
|
243
|
+
this.#statement.finalize();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
/** Finds the first complete statement without reimplementing SQLite's trigger grammar. */
|
|
247
|
+
function firstCompleteStatement(capi, sql) {
|
|
248
|
+
let semicolon = sql.indexOf(";");
|
|
249
|
+
while (semicolon !== -1) {
|
|
250
|
+
const candidate = sql.slice(0, semicolon + 1);
|
|
251
|
+
if (capi.sqlite3_complete(candidate) === 1) return candidate;
|
|
252
|
+
semicolon = sql.indexOf(";", semicolon + 1);
|
|
253
|
+
}
|
|
254
|
+
return sql;
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
export { SqliteWasmActorStorage, SqliteWasmDatabase, createSqliteWasmProvider };
|
|
258
|
+
|
|
259
|
+
//# sourceMappingURL=sqlite-wasm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-wasm.js","names":["#host","#prefix","#provider","#ownedFiles","#filename","#database","#setLengthLimit","#pragma","#closed","#statement"],"sources":["../../backends/sqlite-wasm.ts"],"sourcesContent":["/**\n * ← workerd `NO upstream correspondence (storage-backend adaptation)`\n *\n * `SqlDatabaseProvider` over the browser's OPFS SAH pool.\n *\n * The pool is a parameter, not something this module goes and gets. Installing\n * the VFS is the host's job — `installOpfsSAHPoolVfs` decides the OPFS\n * directory, the pool capacity and whether to clear on init, all of which are\n * layout questions this package deliberately knows nothing about. What arrives\n * here is the already-installed pool, and with it the two things a backend\n * needs that a bare `sqlite3` module cannot give: a database constructor bound\n * to that VFS, plus the pool's file export/import/unlink operations used by\n * snapshots and `reset()`.\n *\n * The pool is structurally typed rather than imported from\n * `@sqlite.org/sqlite-wasm`, so this package takes no dependency on the driver\n * and the caller is free to pass a pool from any build of it. The shape below\n * is the subset of `SAHPoolUtil` and `oo1.DB` that is used, copied from the\n * driver's own `.d.mts`.\n *\n * NOT exercised by the unit lane: it needs OPFS, which means a browser. It is\n * exercised twice in the browser lane — by `sqlite-wasm.smoke.spec.ts`, which\n * drives this file directly, and by the conformance suite, which runs the whole\n * package over it.\n */\n\nimport {\n requireSqliteLength,\n requireSafeDatabaseName,\n requireValidSqlDatabaseSnapshot,\n SQLITE_LENGTH_LIMIT,\n SQL_WRONG_BINDINGS_MESSAGE,\n type SqlDatabase,\n type SqlDatabaseProvider,\n type SqlDatabaseSnapshot,\n type SqlDatabaseSnapshotProvider,\n type SqlDatabaseStatement,\n type SqlResult,\n type SqlValue,\n} from \"../src/util/sqlite\";\n\n/** ← `PreparedStatement`, the members used here. */\nexport interface SqliteWasmStatement {\n readonly columnCount: number;\n readonly parameterCount: number;\n bind(bindings: readonly (string | number | bigint | null | Uint8Array)[]): unknown;\n step(): boolean;\n get(index: number): unknown;\n getColumnNames(target?: string[]): string[];\n finalize(): number | undefined;\n}\n\n/** ← `oo1.DB` / `OpfsSAHPoolDatabase`, the members used here. */\nexport interface SqliteWasmDatabaseHandle {\n /** ← `oo1.DB.pointer`, which is absent once the handle is closed. */\n readonly pointer?: number | undefined;\n prepare(sql: string): SqliteWasmStatement;\n changes(total?: boolean, sixtyFour?: false): number;\n close(): void;\n}\n\n/** ← `SAHPoolUtil`, the members used here. */\nexport interface OpfsSahPool {\n /** Constructs a database inside this pool's VFS. Names are absolute, so they start with \"/\". */\n readonly OpfsSAHPoolDb: new (filename: string) => SqliteWasmDatabaseHandle;\n exportFile(filename: string): Uint8Array | Promise<Uint8Array>;\n importDb(filename: string, image: Uint8Array): number | Promise<number>;\n getFileNames(): string[];\n /** Disassociates a virtual file from the pool. Results are undefined if it is in active use. */\n unlink(filename: string): boolean;\n}\n\n/**\n * ← `Sqlite3Static[\"capi\"]`, restricted to the one C function `oo1.DB` does not\n * wrap.\n *\n * Takes the pointer rather than the handle, even though upstream's `DbPtr`\n * accepts either: a structural subset of `oo1.DB` is not assignable to the\n * `Database` class, so asking for the handle would make the real `capi` fail to\n * satisfy this interface.\n */\nexport interface SqliteWasmCapi {\n readonly SQLITE_LIMIT_LENGTH: number;\n sqlite3_complete(sql: string): 0 | 1;\n sqlite3_get_autocommit(db: number): number;\n sqlite3_limit(db: number, id: number, newValue: number): number;\n}\n\n/**\n * What the host hands over: the pool it installed, and the C-API namespace it\n * already holds. Both come off the same `sqlite3` object the caller used to\n * call `installOpfsSAHPoolVfs`, so this asks for nothing it does not have.\n */\nexport interface SqliteWasmHost {\n readonly pool: OpfsSahPool;\n readonly capi: SqliteWasmCapi;\n}\n\nexport type SqliteWasmProviderOptions = {\n /** Absolute path prefix inside the pool, e.g. `/actor-<id>`. Must start with \"/\". */\n prefix: string;\n};\n\n/**\n * One actor's named databases and their file lifecycle inside an OPFS SAH pool.\n *\n * A root or facet container only needs the `SqlDatabaseProvider` surface. Its\n * host also has to close every connection when that placement dies, remove the\n * prefix on delete, and copy every database on clone. Those file operations\n * belong here because SAH-pool files are virtual and can only be reached through\n * the pool that owns them.\n */\nexport class SqliteWasmActorStorage implements SqlDatabaseProvider {\n readonly #host: SqliteWasmHost;\n readonly #prefix: string;\n readonly #provider: SqlDatabaseSnapshotProvider;\n\n constructor(host: SqliteWasmHost, prefix: string) {\n this.#host = host;\n this.#prefix = prefix;\n this.#provider = createSqliteWasmProvider(host, { prefix });\n }\n\n open(name: string): Promise<SqlDatabase> {\n return this.#provider.open(name);\n }\n\n /**\n * Drop every handle. Leaving one behind per respawn or facet abort would\n * accumulate concurrent writers inside a VFS that expects to own its files.\n */\n close(): void {\n this.#provider.close();\n }\n\n /** Close every handle, then physically remove every database under this prefix. */\n deleteAll(): void {\n this.close();\n for (const file of this.#ownedFiles()) {\n if (!this.#host.pool.unlink(file)) throw new Error(`SAH pool did not unlink ${file}`);\n }\n }\n\n /**\n * Replace this prefix with every database under `source`, including files\n * from an earlier placement that this session never opened.\n *\n * The source may still be running, so this uses the pool's file operations\n * rather than the snapshot API, which correctly refuses open handles. A\n * recovery sidecar means the bytes are not a stable database image and is\n * refused before the destination is touched. Every source image is also\n * exported before replacement starts, so a failed read preserves the target.\n */\n async copyFrom(source: SqliteWasmActorStorage): Promise<void> {\n const files = source.#ownedFiles();\n const sidecar = files.find((file) => !file.endsWith(\".sqlite\"));\n if (sidecar !== undefined) {\n throw new Error(`Cannot clone actor storage with a SQLite recovery sidecar: ${sidecar}`);\n }\n const images: Array<{ name: string; bytes: Uint8Array }> = [];\n for (const file of files) {\n const name = file.slice(source.#prefix.length + 1, -\".sqlite\".length);\n requireSafeDatabaseName(name);\n images.push({\n name,\n bytes: new Uint8Array(await source.#host.pool.exportFile(file)),\n });\n }\n this.deleteAll();\n for (const { name, bytes } of images) {\n await this.#host.pool.importDb(\n `${this.#prefix}.${name}.sqlite`,\n bytes,\n );\n }\n }\n\n #ownedFiles(): string[] {\n return this.#host.pool\n .getFileNames()\n .filter((name) => name.startsWith(`${this.#prefix}.`));\n }\n}\n\nexport function createSqliteWasmProvider(\n host: SqliteWasmHost,\n options: SqliteWasmProviderOptions,\n): SqlDatabaseSnapshotProvider {\n const { prefix } = options;\n if (!prefix.startsWith(\"/\")) {\n throw new Error(`SAH pool names are absolute; prefix must start with \"/\": ${prefix}`);\n }\n const openDatabases = new Set<SqliteWasmDatabase>();\n const ownedFiles = (): string[] =>\n host.pool.getFileNames().filter((name) => name.startsWith(`${prefix}.`));\n return {\n async open(name: string): Promise<SqlDatabase> {\n // Names come from inside the package, so this is defence in depth — but\n // it is the one place a name becomes a pool file name.\n requireSafeDatabaseName(name);\n let database: SqliteWasmDatabase;\n database = new SqliteWasmDatabase(host, `${prefix}.${name}.sqlite`, () =>\n openDatabases.delete(database),\n );\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 requireClosed(openDatabases);\n const files = ownedFiles();\n requireNoRecoverySidecars(files);\n const databases = await Promise.all(\n files\n .filter((file) => file.endsWith(\".sqlite\"))\n .sort()\n .map(async (file) => {\n const name = file.slice(prefix.length + 1, -\".sqlite\".length);\n requireSafeDatabaseName(name);\n return { name, image: new Uint8Array(await host.pool.exportFile(file)) };\n }),\n );\n const snapshot: SqlDatabaseSnapshot = { version: 1, databases };\n requireValidSqlDatabaseSnapshot(snapshot);\n return snapshot;\n },\n async importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void> {\n requireClosed(openDatabases);\n requireValidSqlDatabaseSnapshot(snapshot);\n for (const file of ownedFiles()) host.pool.unlink(file);\n for (const { name, image } of snapshot.databases) {\n await host.pool.importDb(`${prefix}.${name}.sqlite`, new Uint8Array(image));\n }\n },\n };\n}\n\nexport class SqliteWasmDatabase implements SqlDatabase {\n readonly #host: SqliteWasmHost;\n readonly #filename: string;\n #database: SqliteWasmDatabaseHandle;\n #closed = false;\n\n constructor(\n host: SqliteWasmHost,\n filename: string,\n private readonly onClose: () => void = () => {},\n ) {\n this.#host = host;\n this.#filename = filename;\n this.#database = new host.pool.OpfsSAHPoolDb(filename);\n this.#setLengthLimit();\n }\n\n prepare(sql: string): SqlDatabaseStatement {\n const source = firstCompleteStatement(this.#host.capi, sql);\n return new WasmSqlStatement(this.#database, this.#database.prepare(source), source);\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 /**\n * `oo1.DB` wraps no equivalent, so this is the one place the backend reaches\n * past it into the C API. `DbPtr` accepts the database object itself.\n */\n get inTransaction(): boolean {\n const pointer = this.#database.pointer;\n if (pointer === undefined) throw new Error(\"The database handle is closed.\");\n return this.#host.capi.sqlite3_get_autocommit(pointer) === 0;\n }\n\n reset(): void {\n // The pool's files are not visible in OPFS under these names, so deleting\n // one goes through the pool rather than through the filesystem. The handle\n // has to be closed first: `unlink`'s results are undefined for a file in\n // active use.\n this.#database.close();\n if (!this.#host.pool.unlink(this.#filename)) {\n throw new Error(`SAH pool did not unlink ${this.#filename}`);\n }\n this.#database = new this.#host.pool.OpfsSAHPoolDb(this.#filename);\n this.#setLengthLimit();\n }\n\n close(): void {\n if (this.#closed) return;\n this.#database.close();\n this.#closed = true;\n this.onClose();\n }\n\n #setLengthLimit(): void {\n const pointer = this.#database.pointer;\n if (pointer === undefined) throw new Error(\"The database handle is closed.\");\n this.#host.capi.sqlite3_limit(\n pointer,\n this.#host.capi.SQLITE_LIMIT_LENGTH,\n SQLITE_LENGTH_LIMIT,\n );\n }\n\n #pragma(name: string): number {\n const row = this.exec(`PRAGMA ${name}`, []).rawRows[0];\n const value = row?.[0];\n if (typeof value !== \"number\") {\n throw new Error(`PRAGMA ${name} did not return a number.`);\n }\n return value;\n }\n}\n\nfunction requireClosed(openDatabases: ReadonlySet<SqliteWasmDatabase>): 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) => !file.endsWith(\".sqlite\"));\n if (sidecar !== undefined) {\n throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);\n }\n}\n\nclass WasmSqlStatement implements SqlDatabaseStatement {\n readonly #database: SqliteWasmDatabaseHandle;\n readonly #statement: SqliteWasmStatement;\n #closed = false;\n\n constructor(\n database: SqliteWasmDatabaseHandle,\n statement: SqliteWasmStatement,\n readonly sql: string,\n ) {\n this.#database = database;\n this.#statement = statement;\n }\n\n get parameterCount(): number {\n return this.#statement.parameterCount;\n }\n\n execute(params: readonly SqlValue[]): SqlResult {\n if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n params.forEach(requireSqliteLength);\n if (params.length > 0) this.#statement.bind(params);\n\n const columnCount = this.#statement.columnCount;\n if (columnCount === 0) {\n this.#statement.step();\n return { columnNames: [], rawRows: [], rowsWritten: this.#database.changes(false) };\n }\n\n const changesBefore = this.#database.changes(true);\n const columnNames = this.#statement.getColumnNames();\n const rawRows: unknown[][] = [];\n while (this.#statement.step()) {\n const row: unknown[] = [];\n for (let column = 0; column < columnCount; column += 1) {\n row.push(this.#statement.get(column));\n }\n rawRows.push(row);\n }\n // A SELECT leaves total_changes() untouched; DML RETURNING advances it.\n // The delta avoids a SQL classifier and matches the public cursor contract.\n return {\n columnNames,\n rawRows,\n rowsWritten: this.#database.changes(true) - changesBefore,\n };\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n this.#statement.finalize();\n }\n}\n\n/** Finds the first complete statement without reimplementing SQLite's trigger grammar. */\nfunction firstCompleteStatement(capi: SqliteWasmCapi, sql: string): string {\n let semicolon = sql.indexOf(\";\");\n while (semicolon !== -1) {\n const candidate = sql.slice(0, semicolon + 1);\n if (capi.sqlite3_complete(candidate) === 1) return candidate;\n semicolon = sql.indexOf(\";\", semicolon + 1);\n }\n return sql;\n}\n"],"mappings":"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"}
|