@rivetkit/sqlite-vfs 2.1.4 → 2.1.6-rc.1
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/dist/tsup/index.cjs +739 -101
- package/dist/tsup/index.cjs.map +1 -1
- package/dist/tsup/index.d.cts +136 -6
- package/dist/tsup/index.d.ts +136 -6
- package/dist/tsup/index.js +737 -99
- package/dist/tsup/index.js.map +1 -1
- package/package.json +4 -3
- package/src/generated/empty-db-page.ts +23 -0
- package/src/index.ts +3 -0
- package/src/kv.ts +18 -3
- package/src/pool.ts +495 -0
- package/src/vfs.ts +604 -131
- package/src/wasm.d.ts +60 -0
package/dist/tsup/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } var _class;// ../../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.53.2_@types+node@22.19.10__@swc+core@1.15.11_@swc_c97c1b0c6eca1589eb738d8730776c3e/node_modules/tsup/assets/cjs_shims.js
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;// ../../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.53.2_@types+node@22.19.10__@swc+core@1.15.11_@swc_c97c1b0c6eca1589eb738d8730776c3e/node_modules/tsup/assets/cjs_shims.js
|
|
2
2
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
3
3
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
4
4
|
|
|
@@ -44,6 +44,14 @@ function getChunkKey(fileTag, chunkIndex) {
|
|
|
44
44
|
return key;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// src/generated/empty-db-page.ts
|
|
48
|
+
var HEADER_PREFIX = new Uint8Array([83, 81, 76, 105, 116, 101, 32, 102, 111, 114, 109, 97, 116, 32, 51, 0, 16, 0, 1, 1, 0, 64, 32, 32, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 46, 138, 17, 13, 0, 0, 0, 0, 16, 0, 0]);
|
|
49
|
+
var EMPTY_DB_PAGE = (() => {
|
|
50
|
+
const page = new Uint8Array(4096);
|
|
51
|
+
page.set(HEADER_PREFIX);
|
|
52
|
+
return page;
|
|
53
|
+
})();
|
|
54
|
+
|
|
47
55
|
// schemas/file-meta/versioned.ts
|
|
48
56
|
var _vbare = require('vbare');
|
|
49
57
|
|
|
@@ -107,6 +115,11 @@ var MAX_CHUNK_INDEX = 4294967295;
|
|
|
107
115
|
var MAX_FILE_SIZE_BYTES = (MAX_CHUNK_INDEX + 1) * CHUNK_SIZE;
|
|
108
116
|
var MAX_FILE_SIZE_HI32 = Math.floor(MAX_FILE_SIZE_BYTES / UINT32_SIZE);
|
|
109
117
|
var MAX_FILE_SIZE_LO32 = MAX_FILE_SIZE_BYTES % UINT32_SIZE;
|
|
118
|
+
var KV_MAX_BATCH_KEYS = 128;
|
|
119
|
+
var SQLITE_IOCAP_BATCH_ATOMIC = 16384;
|
|
120
|
+
var SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;
|
|
121
|
+
var SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;
|
|
122
|
+
var SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;
|
|
110
123
|
var SQLITE_ASYNC_METHODS = /* @__PURE__ */ new Set([
|
|
111
124
|
"xOpen",
|
|
112
125
|
"xClose",
|
|
@@ -116,7 +129,8 @@ var SQLITE_ASYNC_METHODS = /* @__PURE__ */ new Set([
|
|
|
116
129
|
"xSync",
|
|
117
130
|
"xFileSize",
|
|
118
131
|
"xDelete",
|
|
119
|
-
"xAccess"
|
|
132
|
+
"xAccess",
|
|
133
|
+
"xFileControl"
|
|
120
134
|
]);
|
|
121
135
|
function isSqliteEsmFactory(value) {
|
|
122
136
|
return typeof value === "function";
|
|
@@ -128,18 +142,34 @@ function isSQLiteModule(value) {
|
|
|
128
142
|
const candidate = value;
|
|
129
143
|
return typeof candidate.UTF8ToString === "function" && candidate.HEAPU8 instanceof Uint8Array;
|
|
130
144
|
}
|
|
131
|
-
async function loadSqliteRuntime() {
|
|
132
|
-
const specifier = ["@rivetkit/sqlite", "dist", "wa-sqlite-async.mjs"].join(
|
|
145
|
+
async function loadSqliteRuntime(wasmModule) {
|
|
146
|
+
const specifier = ["@rivetkit/sqlite", "dist", "wa-sqlite-async.mjs"].join(
|
|
147
|
+
"/"
|
|
148
|
+
);
|
|
133
149
|
const sqliteModule = await Promise.resolve().then(() => _interopRequireWildcard(require(specifier)));
|
|
134
150
|
if (!isSqliteEsmFactory(sqliteModule.default)) {
|
|
135
151
|
throw new Error("Invalid SQLite ESM factory export");
|
|
136
152
|
}
|
|
137
153
|
const sqliteEsmFactory = sqliteModule.default;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
let module;
|
|
155
|
+
if (wasmModule) {
|
|
156
|
+
module = await sqliteEsmFactory({
|
|
157
|
+
instantiateWasm(imports, receiveInstance) {
|
|
158
|
+
WebAssembly.instantiate(wasmModule, imports).then((instance) => {
|
|
159
|
+
receiveInstance(instance);
|
|
160
|
+
});
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
} else {
|
|
165
|
+
const require2 = _module.createRequire.call(void 0, importMetaUrl);
|
|
166
|
+
const sqliteDistPath = "@rivetkit/sqlite/dist/";
|
|
167
|
+
const wasmPath = require2.resolve(
|
|
168
|
+
sqliteDistPath + "wa-sqlite-async.wasm"
|
|
169
|
+
);
|
|
170
|
+
const wasmBinary = _fs.readFileSync.call(void 0, wasmPath);
|
|
171
|
+
module = await sqliteEsmFactory({ wasmBinary });
|
|
172
|
+
}
|
|
143
173
|
if (!isSQLiteModule(module)) {
|
|
144
174
|
throw new Error("Invalid SQLite runtime module");
|
|
145
175
|
}
|
|
@@ -193,6 +223,7 @@ var Database = class {
|
|
|
193
223
|
#fileName;
|
|
194
224
|
#onClose;
|
|
195
225
|
#sqliteMutex;
|
|
226
|
+
#closed = false;
|
|
196
227
|
constructor(sqlite3, handle, fileName, onClose, sqliteMutex) {
|
|
197
228
|
this.#sqlite3 = sqlite3;
|
|
198
229
|
this.#handle = handle;
|
|
@@ -217,7 +248,10 @@ var Database = class {
|
|
|
217
248
|
*/
|
|
218
249
|
async run(sql, params) {
|
|
219
250
|
await this.#sqliteMutex.run(async () => {
|
|
220
|
-
for await (const stmt of this.#sqlite3.statements(
|
|
251
|
+
for await (const stmt of this.#sqlite3.statements(
|
|
252
|
+
this.#handle,
|
|
253
|
+
sql
|
|
254
|
+
)) {
|
|
221
255
|
if (params) {
|
|
222
256
|
this.#sqlite3.bind_collection(stmt, params);
|
|
223
257
|
}
|
|
@@ -236,7 +270,10 @@ var Database = class {
|
|
|
236
270
|
return this.#sqliteMutex.run(async () => {
|
|
237
271
|
const rows = [];
|
|
238
272
|
let columns = [];
|
|
239
|
-
for await (const stmt of this.#sqlite3.statements(
|
|
273
|
+
for await (const stmt of this.#sqlite3.statements(
|
|
274
|
+
this.#handle,
|
|
275
|
+
sql
|
|
276
|
+
)) {
|
|
240
277
|
if (params) {
|
|
241
278
|
this.#sqlite3.bind_collection(stmt, params);
|
|
242
279
|
}
|
|
@@ -254,10 +291,20 @@ var Database = class {
|
|
|
254
291
|
* Close the database
|
|
255
292
|
*/
|
|
256
293
|
async close() {
|
|
294
|
+
if (this.#closed) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.#closed = true;
|
|
257
298
|
await this.#sqliteMutex.run(async () => {
|
|
258
299
|
await this.#sqlite3.close(this.#handle);
|
|
259
300
|
});
|
|
260
|
-
this.#onClose();
|
|
301
|
+
await this.#onClose();
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get the database file name
|
|
305
|
+
*/
|
|
306
|
+
get fileName() {
|
|
307
|
+
return this.#fileName;
|
|
261
308
|
}
|
|
262
309
|
/**
|
|
263
310
|
* Get the raw @rivetkit/sqlite API (for advanced usage)
|
|
@@ -280,8 +327,11 @@ var SqliteVfs = class {
|
|
|
280
327
|
#sqliteMutex = new AsyncMutex();
|
|
281
328
|
#instanceId;
|
|
282
329
|
#destroyed = false;
|
|
283
|
-
|
|
330
|
+
#openDatabases = /* @__PURE__ */ new Set();
|
|
331
|
+
#wasmModule;
|
|
332
|
+
constructor(wasmModule) {
|
|
284
333
|
this.#instanceId = crypto.randomUUID().replace(/-/g, "").slice(0, 8);
|
|
334
|
+
this.#wasmModule = wasmModule;
|
|
285
335
|
}
|
|
286
336
|
/**
|
|
287
337
|
* Initialize @rivetkit/sqlite and VFS (called once per instance)
|
|
@@ -295,7 +345,9 @@ var SqliteVfs = class {
|
|
|
295
345
|
}
|
|
296
346
|
if (!this.#initPromise) {
|
|
297
347
|
this.#initPromise = (async () => {
|
|
298
|
-
const { sqlite3, module } = await loadSqliteRuntime(
|
|
348
|
+
const { sqlite3, module } = await loadSqliteRuntime(
|
|
349
|
+
this.#wasmModule
|
|
350
|
+
);
|
|
299
351
|
if (this.#destroyed) {
|
|
300
352
|
return;
|
|
301
353
|
}
|
|
@@ -328,6 +380,13 @@ var SqliteVfs = class {
|
|
|
328
380
|
}
|
|
329
381
|
await this.#openMutex.acquire();
|
|
330
382
|
try {
|
|
383
|
+
for (const db2 of this.#openDatabases) {
|
|
384
|
+
if (db2.fileName === fileName) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`SqliteVfs: fileName "${fileName}" is already open on this instance`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
331
390
|
await this.#ensureInitialized();
|
|
332
391
|
if (!this.#sqlite3 || !this.#sqliteSystem) {
|
|
333
392
|
throw new Error("Failed to initialize SQLite");
|
|
@@ -342,20 +401,79 @@ var SqliteVfs = class {
|
|
|
342
401
|
sqliteSystem.name
|
|
343
402
|
)
|
|
344
403
|
);
|
|
345
|
-
|
|
346
|
-
|
|
404
|
+
await this.#sqliteMutex.run(async () => {
|
|
405
|
+
await sqlite3.exec(db, "PRAGMA page_size = 4096");
|
|
406
|
+
await sqlite3.exec(db, "PRAGMA journal_mode = DELETE");
|
|
407
|
+
await sqlite3.exec(db, "PRAGMA synchronous = NORMAL");
|
|
408
|
+
await sqlite3.exec(db, "PRAGMA temp_store = MEMORY");
|
|
409
|
+
await sqlite3.exec(db, "PRAGMA auto_vacuum = NONE");
|
|
410
|
+
await sqlite3.exec(db, "PRAGMA locking_mode = EXCLUSIVE");
|
|
411
|
+
});
|
|
412
|
+
const onClose = async () => {
|
|
413
|
+
this.#openDatabases.delete(database);
|
|
414
|
+
await this.#openMutex.run(async () => {
|
|
415
|
+
sqliteSystem.unregisterFile(fileName);
|
|
416
|
+
});
|
|
347
417
|
};
|
|
348
|
-
|
|
418
|
+
const database = new Database(
|
|
349
419
|
sqlite3,
|
|
350
420
|
db,
|
|
351
421
|
fileName,
|
|
352
422
|
onClose,
|
|
353
423
|
this.#sqliteMutex
|
|
354
424
|
);
|
|
425
|
+
this.#openDatabases.add(database);
|
|
426
|
+
return database;
|
|
355
427
|
} finally {
|
|
356
428
|
this.#openMutex.release();
|
|
357
429
|
}
|
|
358
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Force-close all Database handles whose fileName exactly matches the
|
|
433
|
+
* given name. Snapshots the set to an array before iterating to avoid
|
|
434
|
+
* mutation during async iteration.
|
|
435
|
+
*
|
|
436
|
+
* Uses exact file name match because short names are numeric strings
|
|
437
|
+
* ('0', '1', ..., '10', '11', ...) and a prefix match like
|
|
438
|
+
* startsWith('1') would incorrectly match '10', '11', etc., causing
|
|
439
|
+
* cross-actor corruption. Sidecar files (-journal, -wal, -shm) are not
|
|
440
|
+
* tracked as separate Database handles, so prefix matching for sidecars
|
|
441
|
+
* is not needed.
|
|
442
|
+
*/
|
|
443
|
+
async forceCloseByFileName(fileName) {
|
|
444
|
+
const snapshot = [...this.#openDatabases];
|
|
445
|
+
let allSucceeded = true;
|
|
446
|
+
for (const db of snapshot) {
|
|
447
|
+
if (db.fileName === fileName) {
|
|
448
|
+
try {
|
|
449
|
+
await db.close();
|
|
450
|
+
} catch (e) {
|
|
451
|
+
allSucceeded = false;
|
|
452
|
+
this.#openDatabases.delete(db);
|
|
453
|
+
const sqliteSystem = this.#sqliteSystem;
|
|
454
|
+
if (sqliteSystem) {
|
|
455
|
+
await this.#openMutex.run(async () => {
|
|
456
|
+
sqliteSystem.unregisterFile(db.fileName);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return { allSucceeded };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Force-close all open Database handles. Best-effort: errors are
|
|
466
|
+
* swallowed so this is safe to call during instance teardown.
|
|
467
|
+
*/
|
|
468
|
+
async forceCloseAll() {
|
|
469
|
+
const snapshot = [...this.#openDatabases];
|
|
470
|
+
for (const db of snapshot) {
|
|
471
|
+
try {
|
|
472
|
+
await db.close();
|
|
473
|
+
} catch (e2) {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
359
477
|
/**
|
|
360
478
|
* Tears down this VFS instance and releases internal references.
|
|
361
479
|
*/
|
|
@@ -368,7 +486,7 @@ var SqliteVfs = class {
|
|
|
368
486
|
if (initPromise) {
|
|
369
487
|
try {
|
|
370
488
|
await initPromise;
|
|
371
|
-
} catch (
|
|
489
|
+
} catch (e3) {
|
|
372
490
|
}
|
|
373
491
|
}
|
|
374
492
|
if (this.#sqliteSystem) {
|
|
@@ -389,8 +507,7 @@ var SqliteSystem = (_class = class {
|
|
|
389
507
|
|
|
390
508
|
__init() {this.mxPathName = SQLITE_MAX_PATHNAME_BYTES}
|
|
391
509
|
__init2() {this.mxPathname = SQLITE_MAX_PATHNAME_BYTES}
|
|
392
|
-
#
|
|
393
|
-
#mainFileOptions = null;
|
|
510
|
+
#registeredFiles = /* @__PURE__ */ new Map();
|
|
394
511
|
#openFiles = /* @__PURE__ */ new Map();
|
|
395
512
|
#sqlite3;
|
|
396
513
|
#module;
|
|
@@ -405,8 +522,7 @@ var SqliteSystem = (_class = class {
|
|
|
405
522
|
}
|
|
406
523
|
async close() {
|
|
407
524
|
this.#openFiles.clear();
|
|
408
|
-
this.#
|
|
409
|
-
this.#mainFileOptions = null;
|
|
525
|
+
this.#registeredFiles.clear();
|
|
410
526
|
}
|
|
411
527
|
isReady() {
|
|
412
528
|
return true;
|
|
@@ -421,48 +537,45 @@ var SqliteSystem = (_class = class {
|
|
|
421
537
|
this.#sqlite3.vfs_register(this, false);
|
|
422
538
|
}
|
|
423
539
|
/**
|
|
424
|
-
* Registers a file with its KV options (before opening)
|
|
540
|
+
* Registers a file with its KV options (before opening).
|
|
425
541
|
*/
|
|
426
542
|
registerFile(fileName, options) {
|
|
427
|
-
|
|
428
|
-
this.#mainFileName = fileName;
|
|
429
|
-
this.#mainFileOptions = options;
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
if (this.#mainFileName !== fileName) {
|
|
433
|
-
throw new Error(
|
|
434
|
-
`SqliteSystem is actor-scoped and expects one main file. Got ${fileName}, expected ${this.#mainFileName}.`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
this.#mainFileOptions = options;
|
|
543
|
+
this.#registeredFiles.set(fileName, options);
|
|
438
544
|
}
|
|
439
545
|
/**
|
|
440
|
-
* Unregisters a file's KV options (after closing)
|
|
546
|
+
* Unregisters a file's KV options (after closing).
|
|
441
547
|
*/
|
|
442
548
|
unregisterFile(fileName) {
|
|
443
|
-
|
|
444
|
-
this.#mainFileName = null;
|
|
445
|
-
this.#mainFileOptions = null;
|
|
446
|
-
}
|
|
549
|
+
this.#registeredFiles.delete(fileName);
|
|
447
550
|
}
|
|
448
551
|
/**
|
|
449
|
-
* Resolve file path to
|
|
552
|
+
* Resolve file path to a registered database file or one of its SQLite
|
|
553
|
+
* sidecars (-journal, -wal, -shm). File tags are reused across files
|
|
554
|
+
* because each file's KvVfsOptions routes to a separate KV namespace.
|
|
450
555
|
*/
|
|
451
556
|
#resolveFile(path) {
|
|
452
|
-
|
|
453
|
-
|
|
557
|
+
const directOptions = this.#registeredFiles.get(path);
|
|
558
|
+
if (directOptions) {
|
|
559
|
+
return { options: directOptions, fileTag: FILE_TAG_MAIN };
|
|
454
560
|
}
|
|
455
|
-
if (path
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (path
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
561
|
+
if (path.endsWith("-journal")) {
|
|
562
|
+
const baseName = path.slice(0, -8);
|
|
563
|
+
const options = this.#registeredFiles.get(baseName);
|
|
564
|
+
if (options) {
|
|
565
|
+
return { options, fileTag: FILE_TAG_JOURNAL };
|
|
566
|
+
}
|
|
567
|
+
} else if (path.endsWith("-wal")) {
|
|
568
|
+
const baseName = path.slice(0, -4);
|
|
569
|
+
const options = this.#registeredFiles.get(baseName);
|
|
570
|
+
if (options) {
|
|
571
|
+
return { options, fileTag: FILE_TAG_WAL };
|
|
572
|
+
}
|
|
573
|
+
} else if (path.endsWith("-shm")) {
|
|
574
|
+
const baseName = path.slice(0, -4);
|
|
575
|
+
const options = this.#registeredFiles.get(baseName);
|
|
576
|
+
if (options) {
|
|
577
|
+
return { options, fileTag: FILE_TAG_SHM };
|
|
578
|
+
}
|
|
466
579
|
}
|
|
467
580
|
return null;
|
|
468
581
|
}
|
|
@@ -471,11 +584,12 @@ var SqliteSystem = (_class = class {
|
|
|
471
584
|
if (resolved) {
|
|
472
585
|
return resolved;
|
|
473
586
|
}
|
|
474
|
-
if (
|
|
587
|
+
if (this.#registeredFiles.size === 0) {
|
|
475
588
|
throw new Error(`No KV options registered for file: ${path}`);
|
|
476
589
|
}
|
|
590
|
+
const registered = Array.from(this.#registeredFiles.keys()).join(", ");
|
|
477
591
|
throw new Error(
|
|
478
|
-
`Unsupported SQLite file path ${path}.
|
|
592
|
+
`Unsupported SQLite file path ${path}. Registered base names: ${registered}.`
|
|
479
593
|
);
|
|
480
594
|
}
|
|
481
595
|
#chunkKey(file, chunkIndex) {
|
|
@@ -488,7 +602,12 @@ var SqliteSystem = (_class = class {
|
|
|
488
602
|
}
|
|
489
603
|
const { options, fileTag } = this.#resolveFileOrThrow(path);
|
|
490
604
|
const metaKey = getMetaKey(fileTag);
|
|
491
|
-
|
|
605
|
+
let sizeData;
|
|
606
|
+
try {
|
|
607
|
+
sizeData = await options.get(metaKey);
|
|
608
|
+
} catch (e4) {
|
|
609
|
+
return VFS.SQLITE_CANTOPEN;
|
|
610
|
+
}
|
|
492
611
|
let size;
|
|
493
612
|
if (sizeData) {
|
|
494
613
|
size = decodeFileMeta2(sizeData);
|
|
@@ -496,8 +615,25 @@ var SqliteSystem = (_class = class {
|
|
|
496
615
|
return VFS.SQLITE_IOERR;
|
|
497
616
|
}
|
|
498
617
|
} else if (flags & VFS.SQLITE_OPEN_CREATE) {
|
|
499
|
-
|
|
500
|
-
|
|
618
|
+
if (fileTag === FILE_TAG_MAIN) {
|
|
619
|
+
const chunkKey = getChunkKey(fileTag, 0);
|
|
620
|
+
size = EMPTY_DB_PAGE.length;
|
|
621
|
+
try {
|
|
622
|
+
await options.putBatch([
|
|
623
|
+
[chunkKey, EMPTY_DB_PAGE],
|
|
624
|
+
[metaKey, encodeFileMeta2(size)]
|
|
625
|
+
]);
|
|
626
|
+
} catch (e5) {
|
|
627
|
+
return VFS.SQLITE_CANTOPEN;
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
size = 0;
|
|
631
|
+
try {
|
|
632
|
+
await options.put(metaKey, encodeFileMeta2(size));
|
|
633
|
+
} catch (e6) {
|
|
634
|
+
return VFS.SQLITE_CANTOPEN;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
501
637
|
} else {
|
|
502
638
|
return VFS.SQLITE_CANTOPEN;
|
|
503
639
|
}
|
|
@@ -508,7 +644,10 @@ var SqliteSystem = (_class = class {
|
|
|
508
644
|
size,
|
|
509
645
|
metaDirty: false,
|
|
510
646
|
flags,
|
|
511
|
-
options
|
|
647
|
+
options,
|
|
648
|
+
batchMode: false,
|
|
649
|
+
dirtyBuffer: null,
|
|
650
|
+
savedFileSize: 0
|
|
512
651
|
});
|
|
513
652
|
this.#writeInt32(pOutFlags, flags);
|
|
514
653
|
return VFS.SQLITE_OK;
|
|
@@ -518,14 +657,19 @@ var SqliteSystem = (_class = class {
|
|
|
518
657
|
if (!file) {
|
|
519
658
|
return VFS.SQLITE_OK;
|
|
520
659
|
}
|
|
521
|
-
|
|
522
|
-
|
|
660
|
+
try {
|
|
661
|
+
if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
|
662
|
+
await this.#delete(file.path);
|
|
663
|
+
} else if (file.metaDirty) {
|
|
664
|
+
await file.options.put(
|
|
665
|
+
file.metaKey,
|
|
666
|
+
encodeFileMeta2(file.size)
|
|
667
|
+
);
|
|
668
|
+
file.metaDirty = false;
|
|
669
|
+
}
|
|
670
|
+
} catch (e7) {
|
|
523
671
|
this.#openFiles.delete(fileId);
|
|
524
|
-
return VFS.
|
|
525
|
-
}
|
|
526
|
-
if (file.metaDirty) {
|
|
527
|
-
await file.options.put(file.metaKey, encodeFileMeta2(file.size));
|
|
528
|
-
file.metaDirty = false;
|
|
672
|
+
return VFS.SQLITE_IOERR;
|
|
529
673
|
}
|
|
530
674
|
this.#openFiles.delete(fileId);
|
|
531
675
|
return VFS.SQLITE_OK;
|
|
@@ -538,7 +682,7 @@ var SqliteSystem = (_class = class {
|
|
|
538
682
|
if (!file) {
|
|
539
683
|
return VFS.SQLITE_IOERR_READ;
|
|
540
684
|
}
|
|
541
|
-
|
|
685
|
+
let data = this.#module.HEAPU8.subarray(pData, pData + iAmt);
|
|
542
686
|
const options = file.options;
|
|
543
687
|
const requestedLength = iAmt;
|
|
544
688
|
const iOffset = delegalize(iOffsetLo, iOffsetHi);
|
|
@@ -551,14 +695,31 @@ var SqliteSystem = (_class = class {
|
|
|
551
695
|
return VFS.SQLITE_IOERR_SHORT_READ;
|
|
552
696
|
}
|
|
553
697
|
const startChunk = Math.floor(iOffset / CHUNK_SIZE);
|
|
554
|
-
const endChunk = Math.floor(
|
|
698
|
+
const endChunk = Math.floor(
|
|
699
|
+
(iOffset + requestedLength - 1) / CHUNK_SIZE
|
|
700
|
+
);
|
|
555
701
|
const chunkKeys = [];
|
|
702
|
+
const chunkIndexToBuffered = /* @__PURE__ */ new Map();
|
|
556
703
|
for (let i = startChunk; i <= endChunk; i++) {
|
|
704
|
+
if (file.batchMode && file.dirtyBuffer) {
|
|
705
|
+
const buffered = file.dirtyBuffer.get(i);
|
|
706
|
+
if (buffered) {
|
|
707
|
+
chunkIndexToBuffered.set(i, buffered);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
557
711
|
chunkKeys.push(this.#chunkKey(file, i));
|
|
558
712
|
}
|
|
559
|
-
|
|
713
|
+
let kvChunks;
|
|
714
|
+
try {
|
|
715
|
+
kvChunks = chunkKeys.length > 0 ? await options.getBatch(chunkKeys) : [];
|
|
716
|
+
} catch (e8) {
|
|
717
|
+
return VFS.SQLITE_IOERR_READ;
|
|
718
|
+
}
|
|
719
|
+
data = this.#module.HEAPU8.subarray(pData, pData + iAmt);
|
|
720
|
+
let kvIdx = 0;
|
|
560
721
|
for (let i = startChunk; i <= endChunk; i++) {
|
|
561
|
-
const chunkData =
|
|
722
|
+
const chunkData = _nullishCoalesce(chunkIndexToBuffered.get(i), () => ( kvChunks[kvIdx++]));
|
|
562
723
|
const chunkOffset = i * CHUNK_SIZE;
|
|
563
724
|
const readStart = Math.max(0, iOffset - chunkOffset);
|
|
564
725
|
const readEnd = Math.min(
|
|
@@ -570,7 +731,10 @@ var SqliteSystem = (_class = class {
|
|
|
570
731
|
const sourceEnd = Math.min(readEnd, chunkData.length);
|
|
571
732
|
const destStart = chunkOffset + readStart - iOffset;
|
|
572
733
|
if (sourceEnd > sourceStart) {
|
|
573
|
-
data.set(
|
|
734
|
+
data.set(
|
|
735
|
+
chunkData.subarray(sourceStart, sourceEnd),
|
|
736
|
+
destStart
|
|
737
|
+
);
|
|
574
738
|
}
|
|
575
739
|
if (sourceEnd < readEnd) {
|
|
576
740
|
const zeroStart = destStart + (sourceEnd - sourceStart);
|
|
@@ -598,7 +762,7 @@ var SqliteSystem = (_class = class {
|
|
|
598
762
|
if (!file) {
|
|
599
763
|
return VFS.SQLITE_IOERR_WRITE;
|
|
600
764
|
}
|
|
601
|
-
|
|
765
|
+
let data = this.#module.HEAPU8.subarray(pData, pData + iAmt);
|
|
602
766
|
const iOffset = delegalize(iOffsetLo, iOffsetHi);
|
|
603
767
|
if (iOffset < 0) {
|
|
604
768
|
return VFS.SQLITE_IOERR_WRITE;
|
|
@@ -611,6 +775,26 @@ var SqliteSystem = (_class = class {
|
|
|
611
775
|
}
|
|
612
776
|
const startChunk = Math.floor(iOffset / CHUNK_SIZE);
|
|
613
777
|
const endChunk = Math.floor((iOffset + writeLength - 1) / CHUNK_SIZE);
|
|
778
|
+
if (file.batchMode && file.dirtyBuffer) {
|
|
779
|
+
for (let i = startChunk; i <= endChunk; i++) {
|
|
780
|
+
const chunkOffset = i * CHUNK_SIZE;
|
|
781
|
+
const sourceStart = Math.max(0, chunkOffset - iOffset);
|
|
782
|
+
const sourceEnd = Math.min(
|
|
783
|
+
writeLength,
|
|
784
|
+
chunkOffset + CHUNK_SIZE - iOffset
|
|
785
|
+
);
|
|
786
|
+
file.dirtyBuffer.set(
|
|
787
|
+
i,
|
|
788
|
+
data.subarray(sourceStart, sourceEnd).slice()
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
const newSize2 = Math.max(file.size, writeEndOffset);
|
|
792
|
+
if (newSize2 !== file.size) {
|
|
793
|
+
file.size = newSize2;
|
|
794
|
+
file.metaDirty = true;
|
|
795
|
+
}
|
|
796
|
+
return VFS.SQLITE_OK;
|
|
797
|
+
}
|
|
614
798
|
const plans = [];
|
|
615
799
|
const chunkKeysToFetch = [];
|
|
616
800
|
for (let i = startChunk; i <= endChunk; i++) {
|
|
@@ -639,23 +823,35 @@ var SqliteSystem = (_class = class {
|
|
|
639
823
|
existingChunkIndex
|
|
640
824
|
});
|
|
641
825
|
}
|
|
642
|
-
|
|
826
|
+
let existingChunks;
|
|
827
|
+
try {
|
|
828
|
+
existingChunks = chunkKeysToFetch.length > 0 ? await options.getBatch(chunkKeysToFetch) : [];
|
|
829
|
+
} catch (e9) {
|
|
830
|
+
return VFS.SQLITE_IOERR_WRITE;
|
|
831
|
+
}
|
|
832
|
+
data = this.#module.HEAPU8.subarray(pData, pData + iAmt);
|
|
643
833
|
const entriesToWrite = [];
|
|
644
834
|
for (const plan of plans) {
|
|
645
835
|
const existingChunk = plan.existingChunkIndex >= 0 ? existingChunks[plan.existingChunkIndex] : null;
|
|
646
836
|
let newChunk;
|
|
647
837
|
if (existingChunk) {
|
|
648
|
-
newChunk = new Uint8Array(
|
|
838
|
+
newChunk = new Uint8Array(
|
|
839
|
+
Math.max(existingChunk.length, plan.writeEnd)
|
|
840
|
+
);
|
|
649
841
|
newChunk.set(existingChunk);
|
|
650
842
|
} else {
|
|
651
843
|
newChunk = new Uint8Array(plan.writeEnd);
|
|
652
844
|
}
|
|
653
845
|
const sourceStart = plan.chunkOffset + plan.writeStart - iOffset;
|
|
654
846
|
const sourceEnd = sourceStart + (plan.writeEnd - plan.writeStart);
|
|
655
|
-
newChunk.set(
|
|
847
|
+
newChunk.set(
|
|
848
|
+
data.subarray(sourceStart, sourceEnd),
|
|
849
|
+
plan.writeStart
|
|
850
|
+
);
|
|
656
851
|
entriesToWrite.push([plan.chunkKey, newChunk]);
|
|
657
852
|
}
|
|
658
853
|
const previousSize = file.size;
|
|
854
|
+
const previousMetaDirty = file.metaDirty;
|
|
659
855
|
const newSize = Math.max(file.size, writeEndOffset);
|
|
660
856
|
if (newSize !== previousSize) {
|
|
661
857
|
file.size = newSize;
|
|
@@ -664,7 +860,13 @@ var SqliteSystem = (_class = class {
|
|
|
664
860
|
if (file.metaDirty) {
|
|
665
861
|
entriesToWrite.push([file.metaKey, encodeFileMeta2(file.size)]);
|
|
666
862
|
}
|
|
667
|
-
|
|
863
|
+
try {
|
|
864
|
+
await options.putBatch(entriesToWrite);
|
|
865
|
+
} catch (e10) {
|
|
866
|
+
file.size = previousSize;
|
|
867
|
+
file.metaDirty = previousMetaDirty;
|
|
868
|
+
return VFS.SQLITE_IOERR_WRITE;
|
|
869
|
+
}
|
|
668
870
|
if (file.metaDirty) {
|
|
669
871
|
file.metaDirty = false;
|
|
670
872
|
}
|
|
@@ -682,34 +884,57 @@ var SqliteSystem = (_class = class {
|
|
|
682
884
|
const options = file.options;
|
|
683
885
|
if (size >= file.size) {
|
|
684
886
|
if (size > file.size) {
|
|
887
|
+
const previousSize2 = file.size;
|
|
888
|
+
const previousMetaDirty2 = file.metaDirty;
|
|
685
889
|
file.size = size;
|
|
686
890
|
file.metaDirty = true;
|
|
687
|
-
|
|
891
|
+
try {
|
|
892
|
+
await options.put(file.metaKey, encodeFileMeta2(file.size));
|
|
893
|
+
} catch (e11) {
|
|
894
|
+
file.size = previousSize2;
|
|
895
|
+
file.metaDirty = previousMetaDirty2;
|
|
896
|
+
return VFS.SQLITE_IOERR_TRUNCATE;
|
|
897
|
+
}
|
|
688
898
|
file.metaDirty = false;
|
|
689
899
|
}
|
|
690
900
|
return VFS.SQLITE_OK;
|
|
691
901
|
}
|
|
692
902
|
const lastChunkToKeep = Math.floor((size - 1) / CHUNK_SIZE);
|
|
693
903
|
const lastExistingChunk = Math.floor((file.size - 1) / CHUNK_SIZE);
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
keysToDelete.push(this.#chunkKey(file, i));
|
|
697
|
-
}
|
|
698
|
-
if (keysToDelete.length > 0) {
|
|
699
|
-
await options.deleteBatch(keysToDelete);
|
|
700
|
-
}
|
|
701
|
-
if (size > 0 && size % CHUNK_SIZE !== 0) {
|
|
702
|
-
const lastChunkKey = this.#chunkKey(file, lastChunkToKeep);
|
|
703
|
-
const lastChunkData = await options.get(lastChunkKey);
|
|
704
|
-
if (lastChunkData && lastChunkData.length > size % CHUNK_SIZE) {
|
|
705
|
-
const truncatedChunk = lastChunkData.subarray(0, size % CHUNK_SIZE);
|
|
706
|
-
await options.put(lastChunkKey, truncatedChunk);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
904
|
+
const previousSize = file.size;
|
|
905
|
+
const previousMetaDirty = file.metaDirty;
|
|
709
906
|
file.size = size;
|
|
710
907
|
file.metaDirty = true;
|
|
711
|
-
|
|
908
|
+
try {
|
|
909
|
+
await options.put(file.metaKey, encodeFileMeta2(file.size));
|
|
910
|
+
} catch (e12) {
|
|
911
|
+
file.size = previousSize;
|
|
912
|
+
file.metaDirty = previousMetaDirty;
|
|
913
|
+
return VFS.SQLITE_IOERR_TRUNCATE;
|
|
914
|
+
}
|
|
712
915
|
file.metaDirty = false;
|
|
916
|
+
try {
|
|
917
|
+
if (size > 0 && size % CHUNK_SIZE !== 0) {
|
|
918
|
+
const lastChunkKey = this.#chunkKey(file, lastChunkToKeep);
|
|
919
|
+
const lastChunkData = await options.get(lastChunkKey);
|
|
920
|
+
if (lastChunkData && lastChunkData.length > size % CHUNK_SIZE) {
|
|
921
|
+
const truncatedChunk = lastChunkData.subarray(
|
|
922
|
+
0,
|
|
923
|
+
size % CHUNK_SIZE
|
|
924
|
+
);
|
|
925
|
+
await options.put(lastChunkKey, truncatedChunk);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
const keysToDelete = [];
|
|
929
|
+
for (let i = lastChunkToKeep + 1; i <= lastExistingChunk; i++) {
|
|
930
|
+
keysToDelete.push(this.#chunkKey(file, i));
|
|
931
|
+
}
|
|
932
|
+
for (let b = 0; b < keysToDelete.length; b += KV_MAX_BATCH_KEYS) {
|
|
933
|
+
await options.deleteBatch(keysToDelete.slice(b, b + KV_MAX_BATCH_KEYS));
|
|
934
|
+
}
|
|
935
|
+
} catch (e13) {
|
|
936
|
+
return VFS.SQLITE_IOERR_TRUNCATE;
|
|
937
|
+
}
|
|
713
938
|
return VFS.SQLITE_OK;
|
|
714
939
|
}
|
|
715
940
|
async xSync(fileId, _flags) {
|
|
@@ -717,7 +942,11 @@ var SqliteSystem = (_class = class {
|
|
|
717
942
|
if (!file || !file.metaDirty) {
|
|
718
943
|
return VFS.SQLITE_OK;
|
|
719
944
|
}
|
|
720
|
-
|
|
945
|
+
try {
|
|
946
|
+
await file.options.put(file.metaKey, encodeFileMeta2(file.size));
|
|
947
|
+
} catch (e14) {
|
|
948
|
+
return VFS.SQLITE_IOERR_FSYNC;
|
|
949
|
+
}
|
|
721
950
|
file.metaDirty = false;
|
|
722
951
|
return VFS.SQLITE_OK;
|
|
723
952
|
}
|
|
@@ -730,7 +959,11 @@ var SqliteSystem = (_class = class {
|
|
|
730
959
|
return VFS.SQLITE_OK;
|
|
731
960
|
}
|
|
732
961
|
async xDelete(_pVfs, zName, _syncDir) {
|
|
733
|
-
|
|
962
|
+
try {
|
|
963
|
+
await this.#delete(this.#module.UTF8ToString(zName));
|
|
964
|
+
} catch (e15) {
|
|
965
|
+
return VFS.SQLITE_IOERR_DELETE;
|
|
966
|
+
}
|
|
734
967
|
return VFS.SQLITE_OK;
|
|
735
968
|
}
|
|
736
969
|
/**
|
|
@@ -749,7 +982,9 @@ var SqliteSystem = (_class = class {
|
|
|
749
982
|
for (let i = 0; i < numChunks; i++) {
|
|
750
983
|
keysToDelete.push(getChunkKey(fileTag, i));
|
|
751
984
|
}
|
|
752
|
-
|
|
985
|
+
for (let b = 0; b < keysToDelete.length; b += KV_MAX_BATCH_KEYS) {
|
|
986
|
+
await options.deleteBatch(keysToDelete.slice(b, b + KV_MAX_BATCH_KEYS));
|
|
987
|
+
}
|
|
753
988
|
}
|
|
754
989
|
async xAccess(_pVfs, zName, _flags, pResOut) {
|
|
755
990
|
const path = this.#module.UTF8ToString(zName);
|
|
@@ -759,7 +994,12 @@ var SqliteSystem = (_class = class {
|
|
|
759
994
|
return VFS.SQLITE_OK;
|
|
760
995
|
}
|
|
761
996
|
const compactMetaKey = getMetaKey(resolved.fileTag);
|
|
762
|
-
|
|
997
|
+
let metaData;
|
|
998
|
+
try {
|
|
999
|
+
metaData = await resolved.options.get(compactMetaKey);
|
|
1000
|
+
} catch (e16) {
|
|
1001
|
+
return VFS.SQLITE_IOERR_ACCESS;
|
|
1002
|
+
}
|
|
763
1003
|
this.#writeInt32(pResOut, metaData ? 1 : 0);
|
|
764
1004
|
return VFS.SQLITE_OK;
|
|
765
1005
|
}
|
|
@@ -773,11 +1013,72 @@ var SqliteSystem = (_class = class {
|
|
|
773
1013
|
xUnlock(_fileId, _flags) {
|
|
774
1014
|
return VFS.SQLITE_OK;
|
|
775
1015
|
}
|
|
776
|
-
xFileControl(
|
|
777
|
-
|
|
1016
|
+
async xFileControl(fileId, flags, _pArg) {
|
|
1017
|
+
switch (flags) {
|
|
1018
|
+
case SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: {
|
|
1019
|
+
const file = this.#openFiles.get(fileId);
|
|
1020
|
+
if (!file) return VFS.SQLITE_NOTFOUND;
|
|
1021
|
+
file.savedFileSize = file.size;
|
|
1022
|
+
file.batchMode = true;
|
|
1023
|
+
file.metaDirty = false;
|
|
1024
|
+
file.dirtyBuffer = /* @__PURE__ */ new Map();
|
|
1025
|
+
return VFS.SQLITE_OK;
|
|
1026
|
+
}
|
|
1027
|
+
case SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: {
|
|
1028
|
+
const file = this.#openFiles.get(fileId);
|
|
1029
|
+
if (!file) return VFS.SQLITE_NOTFOUND;
|
|
1030
|
+
const { dirtyBuffer, options } = file;
|
|
1031
|
+
const maxDirtyPages = file.metaDirty ? KV_MAX_BATCH_KEYS - 1 : KV_MAX_BATCH_KEYS;
|
|
1032
|
+
if (dirtyBuffer && dirtyBuffer.size > maxDirtyPages) {
|
|
1033
|
+
dirtyBuffer.clear();
|
|
1034
|
+
file.dirtyBuffer = null;
|
|
1035
|
+
file.size = file.savedFileSize;
|
|
1036
|
+
file.metaDirty = false;
|
|
1037
|
+
file.batchMode = false;
|
|
1038
|
+
return VFS.SQLITE_IOERR;
|
|
1039
|
+
}
|
|
1040
|
+
const entries = [];
|
|
1041
|
+
if (dirtyBuffer) {
|
|
1042
|
+
for (const [chunkIndex, data] of dirtyBuffer) {
|
|
1043
|
+
entries.push([this.#chunkKey(file, chunkIndex), data]);
|
|
1044
|
+
}
|
|
1045
|
+
dirtyBuffer.clear();
|
|
1046
|
+
}
|
|
1047
|
+
if (file.metaDirty) {
|
|
1048
|
+
entries.push([file.metaKey, encodeFileMeta2(file.size)]);
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
await options.putBatch(entries);
|
|
1052
|
+
} catch (e17) {
|
|
1053
|
+
file.dirtyBuffer = null;
|
|
1054
|
+
file.size = file.savedFileSize;
|
|
1055
|
+
file.metaDirty = false;
|
|
1056
|
+
file.batchMode = false;
|
|
1057
|
+
return VFS.SQLITE_IOERR;
|
|
1058
|
+
}
|
|
1059
|
+
file.dirtyBuffer = null;
|
|
1060
|
+
file.metaDirty = false;
|
|
1061
|
+
file.batchMode = false;
|
|
1062
|
+
return VFS.SQLITE_OK;
|
|
1063
|
+
}
|
|
1064
|
+
case SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: {
|
|
1065
|
+
const file = this.#openFiles.get(fileId);
|
|
1066
|
+
if (!file || !file.batchMode) return VFS.SQLITE_OK;
|
|
1067
|
+
if (file.dirtyBuffer) {
|
|
1068
|
+
file.dirtyBuffer.clear();
|
|
1069
|
+
file.dirtyBuffer = null;
|
|
1070
|
+
}
|
|
1071
|
+
file.size = file.savedFileSize;
|
|
1072
|
+
file.metaDirty = false;
|
|
1073
|
+
file.batchMode = false;
|
|
1074
|
+
return VFS.SQLITE_OK;
|
|
1075
|
+
}
|
|
1076
|
+
default:
|
|
1077
|
+
return VFS.SQLITE_NOTFOUND;
|
|
1078
|
+
}
|
|
778
1079
|
}
|
|
779
1080
|
xDeviceCharacteristics(_fileId) {
|
|
780
|
-
return
|
|
1081
|
+
return SQLITE_IOCAP_BATCH_ATOMIC;
|
|
781
1082
|
}
|
|
782
1083
|
xFullPathname(_pVfs, zName, nOut, zOut) {
|
|
783
1084
|
const path = this.#module.UTF8ToString(zName);
|
|
@@ -855,7 +1156,344 @@ function delegalize(lo32, hi32) {
|
|
|
855
1156
|
return hi * UINT32_SIZE + lo;
|
|
856
1157
|
}
|
|
857
1158
|
|
|
1159
|
+
// src/pool.ts
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
var SqliteVfsPool = class {
|
|
1163
|
+
#config;
|
|
1164
|
+
#modulePromise = null;
|
|
1165
|
+
#instances = /* @__PURE__ */ new Set();
|
|
1166
|
+
#actorToInstance = /* @__PURE__ */ new Map();
|
|
1167
|
+
#actorToHandle = /* @__PURE__ */ new Map();
|
|
1168
|
+
#shuttingDown = false;
|
|
1169
|
+
constructor(config2) {
|
|
1170
|
+
if (!Number.isInteger(config2.actorsPerInstance) || config2.actorsPerInstance < 1) {
|
|
1171
|
+
throw new Error(
|
|
1172
|
+
`actorsPerInstance must be a positive integer, got ${config2.actorsPerInstance}`
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
this.#config = config2;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Compile the WASM module once and cache the promise. Subsequent calls
|
|
1179
|
+
* return the same promise, avoiding redundant compilation.
|
|
1180
|
+
*/
|
|
1181
|
+
#getModule() {
|
|
1182
|
+
if (!this.#modulePromise) {
|
|
1183
|
+
this.#modulePromise = (async () => {
|
|
1184
|
+
const require2 = _module.createRequire.call(void 0, importMetaUrl);
|
|
1185
|
+
const wasmPath = require2.resolve(
|
|
1186
|
+
"@rivetkit/sqlite/dist/wa-sqlite-async.wasm"
|
|
1187
|
+
);
|
|
1188
|
+
const wasmBinary = _fs.readFileSync.call(void 0, wasmPath);
|
|
1189
|
+
return WebAssembly.compile(wasmBinary);
|
|
1190
|
+
})();
|
|
1191
|
+
this.#modulePromise.catch(() => {
|
|
1192
|
+
this.#modulePromise = null;
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return this.#modulePromise;
|
|
1196
|
+
}
|
|
1197
|
+
/** Number of live WASM instances in the pool. */
|
|
1198
|
+
get instanceCount() {
|
|
1199
|
+
return this.#instances.size;
|
|
1200
|
+
}
|
|
1201
|
+
/** Number of actors currently assigned to pool instances. */
|
|
1202
|
+
get actorCount() {
|
|
1203
|
+
return this.#actorToInstance.size;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Acquire a pooled VFS handle for the given actor. Returns a
|
|
1207
|
+
* PooledSqliteHandle with sticky assignment. If the actor is already
|
|
1208
|
+
* assigned, the existing handle is returned.
|
|
1209
|
+
*
|
|
1210
|
+
* Bin-packing: picks the instance with the most actors that still has
|
|
1211
|
+
* capacity. If all instances are full, creates a new one using the
|
|
1212
|
+
* cached WASM module.
|
|
1213
|
+
*/
|
|
1214
|
+
async acquire(actorId) {
|
|
1215
|
+
if (this.#shuttingDown) {
|
|
1216
|
+
throw new Error("SqliteVfsPool is shutting down");
|
|
1217
|
+
}
|
|
1218
|
+
const existingHandle = this.#actorToHandle.get(actorId);
|
|
1219
|
+
if (existingHandle) {
|
|
1220
|
+
return existingHandle;
|
|
1221
|
+
}
|
|
1222
|
+
let bestInstance = null;
|
|
1223
|
+
let bestCount = -1;
|
|
1224
|
+
for (const instance of this.#instances) {
|
|
1225
|
+
if (instance.destroying) continue;
|
|
1226
|
+
const count = instance.actors.size;
|
|
1227
|
+
if (count < this.#config.actorsPerInstance && count > bestCount) {
|
|
1228
|
+
bestInstance = instance;
|
|
1229
|
+
bestCount = count;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
if (!bestInstance) {
|
|
1233
|
+
const wasmModule = await this.#getModule();
|
|
1234
|
+
if (this.#shuttingDown) {
|
|
1235
|
+
throw new Error("SqliteVfsPool is shutting down");
|
|
1236
|
+
}
|
|
1237
|
+
const existingHandleAfterAwait = this.#actorToHandle.get(actorId);
|
|
1238
|
+
if (existingHandleAfterAwait) {
|
|
1239
|
+
return existingHandleAfterAwait;
|
|
1240
|
+
}
|
|
1241
|
+
for (const instance of this.#instances) {
|
|
1242
|
+
if (instance.destroying) continue;
|
|
1243
|
+
const count = instance.actors.size;
|
|
1244
|
+
if (count < this.#config.actorsPerInstance && count > bestCount) {
|
|
1245
|
+
bestInstance = instance;
|
|
1246
|
+
bestCount = count;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (!bestInstance) {
|
|
1250
|
+
const vfs = new SqliteVfs(wasmModule);
|
|
1251
|
+
bestInstance = {
|
|
1252
|
+
vfs,
|
|
1253
|
+
actors: /* @__PURE__ */ new Set(),
|
|
1254
|
+
shortNameCounter: 0,
|
|
1255
|
+
actorShortNames: /* @__PURE__ */ new Map(),
|
|
1256
|
+
availableShortNames: /* @__PURE__ */ new Set(),
|
|
1257
|
+
poisonedShortNames: /* @__PURE__ */ new Set(),
|
|
1258
|
+
opsInFlight: 0,
|
|
1259
|
+
idleTimer: null,
|
|
1260
|
+
destroying: false
|
|
1261
|
+
};
|
|
1262
|
+
this.#instances.add(bestInstance);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
this.#cancelIdleTimer(bestInstance);
|
|
1266
|
+
let shortName;
|
|
1267
|
+
const recycled = bestInstance.availableShortNames.values().next();
|
|
1268
|
+
if (!recycled.done) {
|
|
1269
|
+
shortName = recycled.value;
|
|
1270
|
+
bestInstance.availableShortNames.delete(shortName);
|
|
1271
|
+
} else {
|
|
1272
|
+
shortName = String(bestInstance.shortNameCounter++);
|
|
1273
|
+
}
|
|
1274
|
+
bestInstance.actors.add(actorId);
|
|
1275
|
+
bestInstance.actorShortNames.set(actorId, shortName);
|
|
1276
|
+
this.#actorToInstance.set(actorId, bestInstance);
|
|
1277
|
+
const handle = new PooledSqliteHandle(
|
|
1278
|
+
shortName,
|
|
1279
|
+
actorId,
|
|
1280
|
+
this
|
|
1281
|
+
);
|
|
1282
|
+
this.#actorToHandle.set(actorId, handle);
|
|
1283
|
+
return handle;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Release an actor's assignment from the pool. Force-closes all database
|
|
1287
|
+
* handles for the actor, recycles or poisons the short name, and
|
|
1288
|
+
* decrements the instance refcount.
|
|
1289
|
+
*/
|
|
1290
|
+
async release(actorId) {
|
|
1291
|
+
const instance = this.#actorToInstance.get(actorId);
|
|
1292
|
+
if (!instance) {
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const shortName = instance.actorShortNames.get(actorId);
|
|
1296
|
+
if (shortName === void 0) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const { allSucceeded } = await instance.vfs.forceCloseByFileName(shortName);
|
|
1300
|
+
if (allSucceeded) {
|
|
1301
|
+
instance.availableShortNames.add(shortName);
|
|
1302
|
+
} else {
|
|
1303
|
+
instance.poisonedShortNames.add(shortName);
|
|
1304
|
+
}
|
|
1305
|
+
instance.actors.delete(actorId);
|
|
1306
|
+
instance.actorShortNames.delete(actorId);
|
|
1307
|
+
this.#actorToInstance.delete(actorId);
|
|
1308
|
+
this.#actorToHandle.delete(actorId);
|
|
1309
|
+
if (instance.actors.size === 0 && instance.opsInFlight === 0 && !this.#shuttingDown) {
|
|
1310
|
+
this.#startIdleTimer(instance);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Track an in-flight operation on an instance. Increments opsInFlight
|
|
1315
|
+
* before running fn, decrements after using try/finally to prevent
|
|
1316
|
+
* drift from exceptions. If the decrement brings opsInFlight to 0
|
|
1317
|
+
* with refcount also 0, starts the idle timer.
|
|
1318
|
+
*/
|
|
1319
|
+
async #trackOp(instance, fn) {
|
|
1320
|
+
instance.opsInFlight++;
|
|
1321
|
+
try {
|
|
1322
|
+
return await fn();
|
|
1323
|
+
} finally {
|
|
1324
|
+
instance.opsInFlight--;
|
|
1325
|
+
if (instance.actors.size === 0 && instance.opsInFlight === 0 && !instance.destroying && !this.#shuttingDown) {
|
|
1326
|
+
this.#startIdleTimer(instance);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Open a database on behalf of an actor, tracked as an in-flight
|
|
1332
|
+
* operation. Used by PooledSqliteHandle to avoid exposing PoolInstance.
|
|
1333
|
+
*/
|
|
1334
|
+
async openForActor(actorId, shortName, options) {
|
|
1335
|
+
const instance = this.#actorToInstance.get(actorId);
|
|
1336
|
+
if (!instance) {
|
|
1337
|
+
throw new Error(`Actor ${actorId} is not assigned to any pool instance`);
|
|
1338
|
+
}
|
|
1339
|
+
return this.#trackOp(
|
|
1340
|
+
instance,
|
|
1341
|
+
() => instance.vfs.open(shortName, options)
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Track an in-flight database operation for the given actor. Resolves the
|
|
1346
|
+
* actor's pool instance and wraps the operation with opsInFlight tracking.
|
|
1347
|
+
* If the actor has already been released, the operation runs without
|
|
1348
|
+
* tracking since the instance may already be destroyed.
|
|
1349
|
+
*/
|
|
1350
|
+
async trackOpForActor(actorId, fn) {
|
|
1351
|
+
const instance = this.#actorToInstance.get(actorId);
|
|
1352
|
+
if (!instance) {
|
|
1353
|
+
return fn();
|
|
1354
|
+
}
|
|
1355
|
+
return this.#trackOp(instance, fn);
|
|
1356
|
+
}
|
|
1357
|
+
#startIdleTimer(instance) {
|
|
1358
|
+
if (instance.idleTimer || instance.destroying) return;
|
|
1359
|
+
const idleDestroyMs = _nullishCoalesce(this.#config.idleDestroyMs, () => ( 3e4));
|
|
1360
|
+
instance.idleTimer = setTimeout(() => {
|
|
1361
|
+
instance.idleTimer = null;
|
|
1362
|
+
if (instance.actors.size === 0 && instance.opsInFlight === 0 && !instance.destroying) {
|
|
1363
|
+
this.#destroyInstance(instance);
|
|
1364
|
+
}
|
|
1365
|
+
}, idleDestroyMs);
|
|
1366
|
+
}
|
|
1367
|
+
#cancelIdleTimer(instance) {
|
|
1368
|
+
if (instance.idleTimer) {
|
|
1369
|
+
clearTimeout(instance.idleTimer);
|
|
1370
|
+
instance.idleTimer = null;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
async #destroyInstance(instance) {
|
|
1374
|
+
instance.destroying = true;
|
|
1375
|
+
this.#cancelIdleTimer(instance);
|
|
1376
|
+
this.#instances.delete(instance);
|
|
1377
|
+
try {
|
|
1378
|
+
await instance.vfs.forceCloseAll();
|
|
1379
|
+
await instance.vfs.destroy();
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
console.warn("SqliteVfsPool: failed to destroy instance", error);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Graceful shutdown. Rejects new acquire() calls, cancels idle timers,
|
|
1386
|
+
* force-closes all databases, destroys all VFS instances, and clears pool
|
|
1387
|
+
* state.
|
|
1388
|
+
*/
|
|
1389
|
+
async shutdown() {
|
|
1390
|
+
this.#shuttingDown = true;
|
|
1391
|
+
const instances = [...this.#instances];
|
|
1392
|
+
for (const instance of instances) {
|
|
1393
|
+
this.#cancelIdleTimer(instance);
|
|
1394
|
+
this.#instances.delete(instance);
|
|
1395
|
+
if (instance.opsInFlight > 0) {
|
|
1396
|
+
console.warn(
|
|
1397
|
+
`SqliteVfsPool: shutting down instance with ${instance.opsInFlight} in-flight operation(s). Concurrent close is safe due to Database.close() idempotency.`
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
try {
|
|
1401
|
+
await instance.vfs.forceCloseAll();
|
|
1402
|
+
await instance.vfs.destroy();
|
|
1403
|
+
} catch (error) {
|
|
1404
|
+
console.warn("SqliteVfsPool: failed to destroy instance during shutdown", error);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
this.#actorToInstance.clear();
|
|
1408
|
+
this.#actorToHandle.clear();
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
var TrackedDatabase = class {
|
|
1412
|
+
#inner;
|
|
1413
|
+
#pool;
|
|
1414
|
+
#actorId;
|
|
1415
|
+
constructor(inner, pool, actorId) {
|
|
1416
|
+
this.#inner = inner;
|
|
1417
|
+
this.#pool = pool;
|
|
1418
|
+
this.#actorId = actorId;
|
|
1419
|
+
}
|
|
1420
|
+
async exec(...args) {
|
|
1421
|
+
return this.#pool.trackOpForActor(
|
|
1422
|
+
this.#actorId,
|
|
1423
|
+
() => this.#inner.exec(...args)
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
async run(...args) {
|
|
1427
|
+
return this.#pool.trackOpForActor(
|
|
1428
|
+
this.#actorId,
|
|
1429
|
+
() => this.#inner.run(...args)
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
async query(...args) {
|
|
1433
|
+
return this.#pool.trackOpForActor(
|
|
1434
|
+
this.#actorId,
|
|
1435
|
+
() => this.#inner.query(...args)
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
async close() {
|
|
1439
|
+
return this.#pool.trackOpForActor(
|
|
1440
|
+
this.#actorId,
|
|
1441
|
+
() => this.#inner.close()
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
get fileName() {
|
|
1445
|
+
return this.#inner.fileName;
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
var PooledSqliteHandle = class {
|
|
1449
|
+
#shortName;
|
|
1450
|
+
#actorId;
|
|
1451
|
+
#pool;
|
|
1452
|
+
#released = false;
|
|
1453
|
+
constructor(shortName, actorId, pool) {
|
|
1454
|
+
this.#shortName = shortName;
|
|
1455
|
+
this.#actorId = actorId;
|
|
1456
|
+
this.#pool = pool;
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Open a database on the shared instance. Uses the pool-assigned short
|
|
1460
|
+
* name as the VFS file path, with the caller's KvVfsOptions for KV
|
|
1461
|
+
* routing. The open call itself is tracked as an in-flight operation,
|
|
1462
|
+
* and the returned Database is wrapped so that exec(), run(), query(),
|
|
1463
|
+
* and close() are also tracked via opsInFlight.
|
|
1464
|
+
*/
|
|
1465
|
+
async open(_fileName, options) {
|
|
1466
|
+
if (this.#released) {
|
|
1467
|
+
throw new Error("PooledSqliteHandle has been released");
|
|
1468
|
+
}
|
|
1469
|
+
const db = await this.#pool.openForActor(
|
|
1470
|
+
this.#actorId,
|
|
1471
|
+
this.#shortName,
|
|
1472
|
+
options
|
|
1473
|
+
);
|
|
1474
|
+
return new TrackedDatabase(
|
|
1475
|
+
db,
|
|
1476
|
+
this.#pool,
|
|
1477
|
+
this.#actorId
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Release this actor's assignment back to the pool. Idempotent: calling
|
|
1482
|
+
* destroy() more than once is a no-op, preventing double-release from
|
|
1483
|
+
* decrementing the instance refcount below actual.
|
|
1484
|
+
*/
|
|
1485
|
+
async destroy() {
|
|
1486
|
+
if (this.#released) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
this.#released = true;
|
|
1490
|
+
await this.#pool.release(this.#actorId);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
|
|
858
1496
|
|
|
859
1497
|
|
|
860
|
-
exports.Database = Database; exports.SqliteVfs = SqliteVfs;
|
|
1498
|
+
exports.Database = Database; exports.PooledSqliteHandle = PooledSqliteHandle; exports.SqliteVfs = SqliteVfs; exports.SqliteVfsPool = SqliteVfsPool;
|
|
861
1499
|
//# sourceMappingURL=index.cjs.map
|