@rivetkit/sqlite-vfs 2.0.4-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.
@@ -0,0 +1,857 @@
1
+ // src/vfs.ts
2
+ import * as VFS from "@rivetkit/sqlite/src/VFS.js";
3
+ import {
4
+ Factory,
5
+ SQLITE_OPEN_CREATE as SQLITE_OPEN_CREATE2,
6
+ SQLITE_OPEN_READWRITE,
7
+ SQLITE_ROW
8
+ } from "@rivetkit/sqlite";
9
+ import { readFileSync } from "fs";
10
+ import { createRequire } from "module";
11
+
12
+ // src/kv.ts
13
+ var CHUNK_SIZE = 4096;
14
+ var SQLITE_PREFIX = 8;
15
+ var SQLITE_SCHEMA_VERSION = 1;
16
+ var META_PREFIX = 0;
17
+ var CHUNK_PREFIX = 1;
18
+ var FILE_TAG_MAIN = 0;
19
+ var FILE_TAG_JOURNAL = 1;
20
+ var FILE_TAG_WAL = 2;
21
+ var FILE_TAG_SHM = 3;
22
+ function getMetaKey(fileTag) {
23
+ const key = new Uint8Array(4);
24
+ key[0] = SQLITE_PREFIX;
25
+ key[1] = SQLITE_SCHEMA_VERSION;
26
+ key[2] = META_PREFIX;
27
+ key[3] = fileTag;
28
+ return key;
29
+ }
30
+ function getChunkKey(fileTag, chunkIndex) {
31
+ const key = new Uint8Array(8);
32
+ key[0] = SQLITE_PREFIX;
33
+ key[1] = SQLITE_SCHEMA_VERSION;
34
+ key[2] = CHUNK_PREFIX;
35
+ key[3] = fileTag;
36
+ key[4] = chunkIndex >>> 24 & 255;
37
+ key[5] = chunkIndex >>> 16 & 255;
38
+ key[6] = chunkIndex >>> 8 & 255;
39
+ key[7] = chunkIndex & 255;
40
+ return key;
41
+ }
42
+
43
+ // schemas/file-meta/versioned.ts
44
+ import { createVersionedDataHandler } from "vbare";
45
+
46
+ // dist/schemas/file-meta/v1.ts
47
+ import * as bare from "@rivetkit/bare-ts";
48
+ var config = /* @__PURE__ */ bare.Config({});
49
+ function readFileMeta(bc) {
50
+ return {
51
+ size: bare.readU64(bc)
52
+ };
53
+ }
54
+ function writeFileMeta(bc, x) {
55
+ bare.writeU64(bc, x.size);
56
+ }
57
+ function encodeFileMeta(x) {
58
+ const bc = new bare.ByteCursor(
59
+ new Uint8Array(config.initialBufferLength),
60
+ config
61
+ );
62
+ writeFileMeta(bc, x);
63
+ return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset);
64
+ }
65
+ function decodeFileMeta(bytes) {
66
+ const bc = new bare.ByteCursor(bytes, config);
67
+ const result = readFileMeta(bc);
68
+ if (bc.offset < bc.view.byteLength) {
69
+ throw new bare.BareError(bc.offset, "remaining bytes");
70
+ }
71
+ return result;
72
+ }
73
+
74
+ // schemas/file-meta/versioned.ts
75
+ var CURRENT_VERSION = 1;
76
+ var FILE_META_VERSIONED = createVersionedDataHandler({
77
+ deserializeVersion: (bytes, version) => {
78
+ switch (version) {
79
+ case 1:
80
+ return decodeFileMeta(bytes);
81
+ default:
82
+ throw new Error(`Unknown version ${version}`);
83
+ }
84
+ },
85
+ serializeVersion: (data, version) => {
86
+ switch (version) {
87
+ case 1:
88
+ return encodeFileMeta(data);
89
+ default:
90
+ throw new Error(`Unknown version ${version}`);
91
+ }
92
+ },
93
+ deserializeConverters: () => [],
94
+ serializeConverters: () => []
95
+ });
96
+
97
+ // src/vfs.ts
98
+ var TEXT_ENCODER = new TextEncoder();
99
+ var TEXT_DECODER = new TextDecoder();
100
+ var SQLITE_MAX_PATHNAME_BYTES = 64;
101
+ var UINT32_SIZE = 4294967296;
102
+ var MAX_CHUNK_INDEX = 4294967295;
103
+ var MAX_FILE_SIZE_BYTES = (MAX_CHUNK_INDEX + 1) * CHUNK_SIZE;
104
+ var MAX_FILE_SIZE_HI32 = Math.floor(MAX_FILE_SIZE_BYTES / UINT32_SIZE);
105
+ var MAX_FILE_SIZE_LO32 = MAX_FILE_SIZE_BYTES % UINT32_SIZE;
106
+ var SQLITE_ASYNC_METHODS = /* @__PURE__ */ new Set([
107
+ "xOpen",
108
+ "xClose",
109
+ "xRead",
110
+ "xWrite",
111
+ "xTruncate",
112
+ "xSync",
113
+ "xFileSize",
114
+ "xDelete",
115
+ "xAccess"
116
+ ]);
117
+ function isSqliteEsmFactory(value) {
118
+ return typeof value === "function";
119
+ }
120
+ function isSQLiteModule(value) {
121
+ if (!value || typeof value !== "object") {
122
+ return false;
123
+ }
124
+ const candidate = value;
125
+ return typeof candidate.UTF8ToString === "function" && candidate.HEAPU8 instanceof Uint8Array;
126
+ }
127
+ async function loadSqliteRuntime() {
128
+ const specifier = ["@rivetkit/sqlite", "dist", "wa-sqlite-async.mjs"].join("/");
129
+ const sqliteModule = await import(specifier);
130
+ if (!isSqliteEsmFactory(sqliteModule.default)) {
131
+ throw new Error("Invalid SQLite ESM factory export");
132
+ }
133
+ const sqliteEsmFactory = sqliteModule.default;
134
+ const require2 = createRequire(import.meta.url);
135
+ const sqliteDistPath = "@rivetkit/sqlite/dist/";
136
+ const wasmPath = require2.resolve(sqliteDistPath + "wa-sqlite-async.wasm");
137
+ const wasmBinary = readFileSync(wasmPath);
138
+ const module = await sqliteEsmFactory({ wasmBinary });
139
+ if (!isSQLiteModule(module)) {
140
+ throw new Error("Invalid SQLite runtime module");
141
+ }
142
+ return {
143
+ sqlite3: Factory(module),
144
+ module
145
+ };
146
+ }
147
+ function encodeFileMeta2(size) {
148
+ const meta = { size: BigInt(size) };
149
+ return FILE_META_VERSIONED.serializeWithEmbeddedVersion(
150
+ meta,
151
+ CURRENT_VERSION
152
+ );
153
+ }
154
+ function decodeFileMeta2(data) {
155
+ const meta = FILE_META_VERSIONED.deserializeWithEmbeddedVersion(data);
156
+ return Number(meta.size);
157
+ }
158
+ function isValidFileSize(size) {
159
+ return Number.isSafeInteger(size) && size >= 0 && size <= MAX_FILE_SIZE_BYTES;
160
+ }
161
+ var AsyncMutex = class {
162
+ #locked = false;
163
+ #waiting = [];
164
+ async acquire() {
165
+ while (this.#locked) {
166
+ await new Promise((resolve) => this.#waiting.push(resolve));
167
+ }
168
+ this.#locked = true;
169
+ }
170
+ release() {
171
+ this.#locked = false;
172
+ const next = this.#waiting.shift();
173
+ if (next) {
174
+ next();
175
+ }
176
+ }
177
+ async run(fn) {
178
+ await this.acquire();
179
+ try {
180
+ return await fn();
181
+ } finally {
182
+ this.release();
183
+ }
184
+ }
185
+ };
186
+ var Database = class {
187
+ #sqlite3;
188
+ #handle;
189
+ #fileName;
190
+ #onClose;
191
+ #sqliteMutex;
192
+ constructor(sqlite3, handle, fileName, onClose, sqliteMutex) {
193
+ this.#sqlite3 = sqlite3;
194
+ this.#handle = handle;
195
+ this.#fileName = fileName;
196
+ this.#onClose = onClose;
197
+ this.#sqliteMutex = sqliteMutex;
198
+ }
199
+ /**
200
+ * Execute SQL with optional row callback
201
+ * @param sql - SQL statement to execute
202
+ * @param callback - Called for each result row with (row, columns)
203
+ */
204
+ async exec(sql, callback) {
205
+ await this.#sqliteMutex.run(async () => {
206
+ await this.#sqlite3.exec(this.#handle, sql, callback);
207
+ });
208
+ }
209
+ /**
210
+ * Execute a parameterized SQL statement (no result rows)
211
+ * @param sql - SQL statement with ? placeholders
212
+ * @param params - Parameter values to bind
213
+ */
214
+ async run(sql, params) {
215
+ await this.#sqliteMutex.run(async () => {
216
+ for await (const stmt of this.#sqlite3.statements(this.#handle, sql)) {
217
+ if (params) {
218
+ this.#sqlite3.bind_collection(stmt, params);
219
+ }
220
+ while (await this.#sqlite3.step(stmt) === SQLITE_ROW) {
221
+ }
222
+ }
223
+ });
224
+ }
225
+ /**
226
+ * Execute a parameterized SQL query and return results
227
+ * @param sql - SQL query with ? placeholders
228
+ * @param params - Parameter values to bind
229
+ * @returns Object with rows (array of arrays) and columns (column names)
230
+ */
231
+ async query(sql, params) {
232
+ return this.#sqliteMutex.run(async () => {
233
+ const rows = [];
234
+ let columns = [];
235
+ for await (const stmt of this.#sqlite3.statements(this.#handle, sql)) {
236
+ if (params) {
237
+ this.#sqlite3.bind_collection(stmt, params);
238
+ }
239
+ while (await this.#sqlite3.step(stmt) === SQLITE_ROW) {
240
+ if (columns.length === 0) {
241
+ columns = this.#sqlite3.column_names(stmt);
242
+ }
243
+ rows.push(this.#sqlite3.row(stmt));
244
+ }
245
+ }
246
+ return { rows, columns };
247
+ });
248
+ }
249
+ /**
250
+ * Close the database
251
+ */
252
+ async close() {
253
+ await this.#sqliteMutex.run(async () => {
254
+ await this.#sqlite3.close(this.#handle);
255
+ });
256
+ this.#onClose();
257
+ }
258
+ /**
259
+ * Get the raw @rivetkit/sqlite API (for advanced usage)
260
+ */
261
+ get sqlite3() {
262
+ return this.#sqlite3;
263
+ }
264
+ /**
265
+ * Get the raw database handle (for advanced usage)
266
+ */
267
+ get handle() {
268
+ return this.#handle;
269
+ }
270
+ };
271
+ var SqliteVfs = class {
272
+ #sqlite3 = null;
273
+ #sqliteSystem = null;
274
+ #initPromise = null;
275
+ #openMutex = new AsyncMutex();
276
+ #sqliteMutex = new AsyncMutex();
277
+ #instanceId;
278
+ #destroyed = false;
279
+ constructor() {
280
+ this.#instanceId = crypto.randomUUID().replace(/-/g, "").slice(0, 8);
281
+ }
282
+ /**
283
+ * Initialize @rivetkit/sqlite and VFS (called once per instance)
284
+ */
285
+ async #ensureInitialized() {
286
+ if (this.#destroyed) {
287
+ throw new Error("SqliteVfs is closed");
288
+ }
289
+ if (this.#sqlite3 && this.#sqliteSystem) {
290
+ return;
291
+ }
292
+ if (!this.#initPromise) {
293
+ this.#initPromise = (async () => {
294
+ const { sqlite3, module } = await loadSqliteRuntime();
295
+ if (this.#destroyed) {
296
+ return;
297
+ }
298
+ this.#sqlite3 = sqlite3;
299
+ this.#sqliteSystem = new SqliteSystem(
300
+ sqlite3,
301
+ module,
302
+ `kv-vfs-${this.#instanceId}`
303
+ );
304
+ this.#sqliteSystem.register();
305
+ })();
306
+ }
307
+ try {
308
+ await this.#initPromise;
309
+ } catch (error) {
310
+ this.#initPromise = null;
311
+ throw error;
312
+ }
313
+ }
314
+ /**
315
+ * Open a SQLite database using KV storage backend
316
+ *
317
+ * @param fileName - The database file name (typically the actor ID)
318
+ * @param options - KV storage operations for this database
319
+ * @returns A Database instance
320
+ */
321
+ async open(fileName, options) {
322
+ if (this.#destroyed) {
323
+ throw new Error("SqliteVfs is closed");
324
+ }
325
+ await this.#openMutex.acquire();
326
+ try {
327
+ await this.#ensureInitialized();
328
+ if (!this.#sqlite3 || !this.#sqliteSystem) {
329
+ throw new Error("Failed to initialize SQLite");
330
+ }
331
+ const sqlite3 = this.#sqlite3;
332
+ const sqliteSystem = this.#sqliteSystem;
333
+ sqliteSystem.registerFile(fileName, options);
334
+ const db = await this.#sqliteMutex.run(
335
+ async () => sqlite3.open_v2(
336
+ fileName,
337
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE2,
338
+ sqliteSystem.name
339
+ )
340
+ );
341
+ const onClose = () => {
342
+ sqliteSystem.unregisterFile(fileName);
343
+ };
344
+ return new Database(
345
+ sqlite3,
346
+ db,
347
+ fileName,
348
+ onClose,
349
+ this.#sqliteMutex
350
+ );
351
+ } finally {
352
+ this.#openMutex.release();
353
+ }
354
+ }
355
+ /**
356
+ * Tears down this VFS instance and releases internal references.
357
+ */
358
+ async destroy() {
359
+ if (this.#destroyed) {
360
+ return;
361
+ }
362
+ this.#destroyed = true;
363
+ const initPromise = this.#initPromise;
364
+ if (initPromise) {
365
+ try {
366
+ await initPromise;
367
+ } catch {
368
+ }
369
+ }
370
+ if (this.#sqliteSystem) {
371
+ await this.#sqliteSystem.close();
372
+ }
373
+ this.#sqliteSystem = null;
374
+ this.#sqlite3 = null;
375
+ this.#initPromise = null;
376
+ }
377
+ /**
378
+ * Alias for destroy to align with DB-style lifecycle naming.
379
+ */
380
+ async close() {
381
+ await this.destroy();
382
+ }
383
+ };
384
+ var SqliteSystem = class {
385
+ name;
386
+ mxPathName = SQLITE_MAX_PATHNAME_BYTES;
387
+ mxPathname = SQLITE_MAX_PATHNAME_BYTES;
388
+ #mainFileName = null;
389
+ #mainFileOptions = null;
390
+ #openFiles = /* @__PURE__ */ new Map();
391
+ #sqlite3;
392
+ #module;
393
+ #heapDataView;
394
+ #heapDataViewBuffer;
395
+ constructor(sqlite3, module, name) {
396
+ this.name = name;
397
+ this.#sqlite3 = sqlite3;
398
+ this.#module = module;
399
+ this.#heapDataViewBuffer = module.HEAPU8.buffer;
400
+ this.#heapDataView = new DataView(this.#heapDataViewBuffer);
401
+ }
402
+ async close() {
403
+ this.#openFiles.clear();
404
+ this.#mainFileName = null;
405
+ this.#mainFileOptions = null;
406
+ }
407
+ isReady() {
408
+ return true;
409
+ }
410
+ hasAsyncMethod(methodName) {
411
+ return SQLITE_ASYNC_METHODS.has(methodName);
412
+ }
413
+ /**
414
+ * Registers the VFS with SQLite
415
+ */
416
+ register() {
417
+ this.#sqlite3.vfs_register(this, false);
418
+ }
419
+ /**
420
+ * Registers a file with its KV options (before opening)
421
+ */
422
+ registerFile(fileName, options) {
423
+ if (!this.#mainFileName) {
424
+ this.#mainFileName = fileName;
425
+ this.#mainFileOptions = options;
426
+ return;
427
+ }
428
+ if (this.#mainFileName !== fileName) {
429
+ throw new Error(
430
+ `SqliteSystem is actor-scoped and expects one main file. Got ${fileName}, expected ${this.#mainFileName}.`
431
+ );
432
+ }
433
+ this.#mainFileOptions = options;
434
+ }
435
+ /**
436
+ * Unregisters a file's KV options (after closing)
437
+ */
438
+ unregisterFile(fileName) {
439
+ if (this.#mainFileName === fileName) {
440
+ this.#mainFileName = null;
441
+ this.#mainFileOptions = null;
442
+ }
443
+ }
444
+ /**
445
+ * Resolve file path to the actor's main DB file or known SQLite sidecars.
446
+ */
447
+ #resolveFile(path) {
448
+ if (!this.#mainFileName || !this.#mainFileOptions) {
449
+ return null;
450
+ }
451
+ if (path === this.#mainFileName) {
452
+ return { options: this.#mainFileOptions, fileTag: FILE_TAG_MAIN };
453
+ }
454
+ if (path === `${this.#mainFileName}-journal`) {
455
+ return { options: this.#mainFileOptions, fileTag: FILE_TAG_JOURNAL };
456
+ }
457
+ if (path === `${this.#mainFileName}-wal`) {
458
+ return { options: this.#mainFileOptions, fileTag: FILE_TAG_WAL };
459
+ }
460
+ if (path === `${this.#mainFileName}-shm`) {
461
+ return { options: this.#mainFileOptions, fileTag: FILE_TAG_SHM };
462
+ }
463
+ return null;
464
+ }
465
+ #resolveFileOrThrow(path) {
466
+ const resolved = this.#resolveFile(path);
467
+ if (resolved) {
468
+ return resolved;
469
+ }
470
+ if (!this.#mainFileName) {
471
+ throw new Error(`No KV options registered for file: ${path}`);
472
+ }
473
+ throw new Error(
474
+ `Unsupported SQLite file path ${path}. Expected one of ${this.#mainFileName}, ${this.#mainFileName}-journal, ${this.#mainFileName}-wal, ${this.#mainFileName}-shm.`
475
+ );
476
+ }
477
+ #chunkKey(file, chunkIndex) {
478
+ return getChunkKey(file.fileTag, chunkIndex);
479
+ }
480
+ async xOpen(_pVfs, zName, fileId, flags, pOutFlags) {
481
+ const path = this.#decodeFilename(zName, flags);
482
+ if (!path) {
483
+ return VFS.SQLITE_CANTOPEN;
484
+ }
485
+ const { options, fileTag } = this.#resolveFileOrThrow(path);
486
+ const metaKey = getMetaKey(fileTag);
487
+ const sizeData = await options.get(metaKey);
488
+ let size;
489
+ if (sizeData) {
490
+ size = decodeFileMeta2(sizeData);
491
+ if (!isValidFileSize(size)) {
492
+ return VFS.SQLITE_IOERR;
493
+ }
494
+ } else if (flags & VFS.SQLITE_OPEN_CREATE) {
495
+ size = 0;
496
+ await options.put(metaKey, encodeFileMeta2(size));
497
+ } else {
498
+ return VFS.SQLITE_CANTOPEN;
499
+ }
500
+ this.#openFiles.set(fileId, {
501
+ path,
502
+ fileTag,
503
+ metaKey,
504
+ size,
505
+ metaDirty: false,
506
+ flags,
507
+ options
508
+ });
509
+ this.#writeInt32(pOutFlags, flags);
510
+ return VFS.SQLITE_OK;
511
+ }
512
+ async xClose(fileId) {
513
+ const file = this.#openFiles.get(fileId);
514
+ if (!file) {
515
+ return VFS.SQLITE_OK;
516
+ }
517
+ if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
518
+ await this.#delete(file.path);
519
+ this.#openFiles.delete(fileId);
520
+ return VFS.SQLITE_OK;
521
+ }
522
+ if (file.metaDirty) {
523
+ await file.options.put(file.metaKey, encodeFileMeta2(file.size));
524
+ file.metaDirty = false;
525
+ }
526
+ this.#openFiles.delete(fileId);
527
+ return VFS.SQLITE_OK;
528
+ }
529
+ async xRead(fileId, pData, iAmt, iOffsetLo, iOffsetHi) {
530
+ if (iAmt === 0) {
531
+ return VFS.SQLITE_OK;
532
+ }
533
+ const file = this.#openFiles.get(fileId);
534
+ if (!file) {
535
+ return VFS.SQLITE_IOERR_READ;
536
+ }
537
+ const data = this.#module.HEAPU8.subarray(pData, pData + iAmt);
538
+ const options = file.options;
539
+ const requestedLength = iAmt;
540
+ const iOffset = delegalize(iOffsetLo, iOffsetHi);
541
+ if (iOffset < 0) {
542
+ return VFS.SQLITE_IOERR_READ;
543
+ }
544
+ const fileSize = file.size;
545
+ if (iOffset >= fileSize) {
546
+ data.fill(0);
547
+ return VFS.SQLITE_IOERR_SHORT_READ;
548
+ }
549
+ const startChunk = Math.floor(iOffset / CHUNK_SIZE);
550
+ const endChunk = Math.floor((iOffset + requestedLength - 1) / CHUNK_SIZE);
551
+ const chunkKeys = [];
552
+ for (let i = startChunk; i <= endChunk; i++) {
553
+ chunkKeys.push(this.#chunkKey(file, i));
554
+ }
555
+ const chunks = await options.getBatch(chunkKeys);
556
+ for (let i = startChunk; i <= endChunk; i++) {
557
+ const chunkData = chunks[i - startChunk];
558
+ const chunkOffset = i * CHUNK_SIZE;
559
+ const readStart = Math.max(0, iOffset - chunkOffset);
560
+ const readEnd = Math.min(
561
+ CHUNK_SIZE,
562
+ iOffset + requestedLength - chunkOffset
563
+ );
564
+ if (chunkData) {
565
+ const sourceStart = readStart;
566
+ const sourceEnd = Math.min(readEnd, chunkData.length);
567
+ const destStart = chunkOffset + readStart - iOffset;
568
+ if (sourceEnd > sourceStart) {
569
+ data.set(chunkData.subarray(sourceStart, sourceEnd), destStart);
570
+ }
571
+ if (sourceEnd < readEnd) {
572
+ const zeroStart = destStart + (sourceEnd - sourceStart);
573
+ const zeroEnd = destStart + (readEnd - readStart);
574
+ data.fill(0, zeroStart, zeroEnd);
575
+ }
576
+ } else {
577
+ const destStart = chunkOffset + readStart - iOffset;
578
+ const destEnd = destStart + (readEnd - readStart);
579
+ data.fill(0, destStart, destEnd);
580
+ }
581
+ }
582
+ const actualBytes = Math.min(requestedLength, fileSize - iOffset);
583
+ if (actualBytes < requestedLength) {
584
+ data.fill(0, actualBytes);
585
+ return VFS.SQLITE_IOERR_SHORT_READ;
586
+ }
587
+ return VFS.SQLITE_OK;
588
+ }
589
+ async xWrite(fileId, pData, iAmt, iOffsetLo, iOffsetHi) {
590
+ if (iAmt === 0) {
591
+ return VFS.SQLITE_OK;
592
+ }
593
+ const file = this.#openFiles.get(fileId);
594
+ if (!file) {
595
+ return VFS.SQLITE_IOERR_WRITE;
596
+ }
597
+ const data = this.#module.HEAPU8.subarray(pData, pData + iAmt);
598
+ const iOffset = delegalize(iOffsetLo, iOffsetHi);
599
+ if (iOffset < 0) {
600
+ return VFS.SQLITE_IOERR_WRITE;
601
+ }
602
+ const options = file.options;
603
+ const writeLength = iAmt;
604
+ const writeEndOffset = iOffset + writeLength;
605
+ if (writeEndOffset > MAX_FILE_SIZE_BYTES) {
606
+ return VFS.SQLITE_IOERR_WRITE;
607
+ }
608
+ const startChunk = Math.floor(iOffset / CHUNK_SIZE);
609
+ const endChunk = Math.floor((iOffset + writeLength - 1) / CHUNK_SIZE);
610
+ const plans = [];
611
+ const chunkKeysToFetch = [];
612
+ for (let i = startChunk; i <= endChunk; i++) {
613
+ const chunkOffset = i * CHUNK_SIZE;
614
+ const writeStart = Math.max(0, iOffset - chunkOffset);
615
+ const writeEnd = Math.min(
616
+ CHUNK_SIZE,
617
+ iOffset + writeLength - chunkOffset
618
+ );
619
+ const existingBytesInChunk = Math.max(
620
+ 0,
621
+ Math.min(CHUNK_SIZE, file.size - chunkOffset)
622
+ );
623
+ const needsExisting = writeStart > 0 || existingBytesInChunk > writeEnd;
624
+ const chunkKey = this.#chunkKey(file, i);
625
+ let existingChunkIndex = -1;
626
+ if (needsExisting) {
627
+ existingChunkIndex = chunkKeysToFetch.length;
628
+ chunkKeysToFetch.push(chunkKey);
629
+ }
630
+ plans.push({
631
+ chunkKey,
632
+ chunkOffset,
633
+ writeStart,
634
+ writeEnd,
635
+ existingChunkIndex
636
+ });
637
+ }
638
+ const existingChunks = chunkKeysToFetch.length > 0 ? await options.getBatch(chunkKeysToFetch) : [];
639
+ const entriesToWrite = [];
640
+ for (const plan of plans) {
641
+ const existingChunk = plan.existingChunkIndex >= 0 ? existingChunks[plan.existingChunkIndex] : null;
642
+ let newChunk;
643
+ if (existingChunk) {
644
+ newChunk = new Uint8Array(Math.max(existingChunk.length, plan.writeEnd));
645
+ newChunk.set(existingChunk);
646
+ } else {
647
+ newChunk = new Uint8Array(plan.writeEnd);
648
+ }
649
+ const sourceStart = plan.chunkOffset + plan.writeStart - iOffset;
650
+ const sourceEnd = sourceStart + (plan.writeEnd - plan.writeStart);
651
+ newChunk.set(data.subarray(sourceStart, sourceEnd), plan.writeStart);
652
+ entriesToWrite.push([plan.chunkKey, newChunk]);
653
+ }
654
+ const previousSize = file.size;
655
+ const newSize = Math.max(file.size, writeEndOffset);
656
+ if (newSize !== previousSize) {
657
+ file.size = newSize;
658
+ file.metaDirty = true;
659
+ }
660
+ if (file.metaDirty) {
661
+ entriesToWrite.push([file.metaKey, encodeFileMeta2(file.size)]);
662
+ }
663
+ await options.putBatch(entriesToWrite);
664
+ if (file.metaDirty) {
665
+ file.metaDirty = false;
666
+ }
667
+ return VFS.SQLITE_OK;
668
+ }
669
+ async xTruncate(fileId, sizeLo, sizeHi) {
670
+ const file = this.#openFiles.get(fileId);
671
+ if (!file) {
672
+ return VFS.SQLITE_IOERR_TRUNCATE;
673
+ }
674
+ const size = delegalize(sizeLo, sizeHi);
675
+ if (size < 0 || size > MAX_FILE_SIZE_BYTES) {
676
+ return VFS.SQLITE_IOERR_TRUNCATE;
677
+ }
678
+ const options = file.options;
679
+ if (size >= file.size) {
680
+ if (size > file.size) {
681
+ file.size = size;
682
+ file.metaDirty = true;
683
+ await options.put(file.metaKey, encodeFileMeta2(file.size));
684
+ file.metaDirty = false;
685
+ }
686
+ return VFS.SQLITE_OK;
687
+ }
688
+ const lastChunkToKeep = Math.floor((size - 1) / CHUNK_SIZE);
689
+ const lastExistingChunk = Math.floor((file.size - 1) / CHUNK_SIZE);
690
+ const keysToDelete = [];
691
+ for (let i = lastChunkToKeep + 1; i <= lastExistingChunk; i++) {
692
+ keysToDelete.push(this.#chunkKey(file, i));
693
+ }
694
+ if (keysToDelete.length > 0) {
695
+ await options.deleteBatch(keysToDelete);
696
+ }
697
+ if (size > 0 && size % CHUNK_SIZE !== 0) {
698
+ const lastChunkKey = this.#chunkKey(file, lastChunkToKeep);
699
+ const lastChunkData = await options.get(lastChunkKey);
700
+ if (lastChunkData && lastChunkData.length > size % CHUNK_SIZE) {
701
+ const truncatedChunk = lastChunkData.subarray(0, size % CHUNK_SIZE);
702
+ await options.put(lastChunkKey, truncatedChunk);
703
+ }
704
+ }
705
+ file.size = size;
706
+ file.metaDirty = true;
707
+ await options.put(file.metaKey, encodeFileMeta2(file.size));
708
+ file.metaDirty = false;
709
+ return VFS.SQLITE_OK;
710
+ }
711
+ async xSync(fileId, _flags) {
712
+ const file = this.#openFiles.get(fileId);
713
+ if (!file || !file.metaDirty) {
714
+ return VFS.SQLITE_OK;
715
+ }
716
+ await file.options.put(file.metaKey, encodeFileMeta2(file.size));
717
+ file.metaDirty = false;
718
+ return VFS.SQLITE_OK;
719
+ }
720
+ async xFileSize(fileId, pSize) {
721
+ const file = this.#openFiles.get(fileId);
722
+ if (!file) {
723
+ return VFS.SQLITE_IOERR_FSTAT;
724
+ }
725
+ this.#writeBigInt64(pSize, BigInt(file.size));
726
+ return VFS.SQLITE_OK;
727
+ }
728
+ async xDelete(_pVfs, zName, _syncDir) {
729
+ await this.#delete(this.#module.UTF8ToString(zName));
730
+ return VFS.SQLITE_OK;
731
+ }
732
+ /**
733
+ * Internal delete implementation
734
+ */
735
+ async #delete(path) {
736
+ const { options, fileTag } = this.#resolveFileOrThrow(path);
737
+ const metaKey = getMetaKey(fileTag);
738
+ const sizeData = await options.get(metaKey);
739
+ if (!sizeData) {
740
+ return;
741
+ }
742
+ const size = decodeFileMeta2(sizeData);
743
+ const keysToDelete = [metaKey];
744
+ const numChunks = Math.ceil(size / CHUNK_SIZE);
745
+ for (let i = 0; i < numChunks; i++) {
746
+ keysToDelete.push(getChunkKey(fileTag, i));
747
+ }
748
+ await options.deleteBatch(keysToDelete);
749
+ }
750
+ async xAccess(_pVfs, zName, _flags, pResOut) {
751
+ const path = this.#module.UTF8ToString(zName);
752
+ const resolved = this.#resolveFile(path);
753
+ if (!resolved) {
754
+ this.#writeInt32(pResOut, 0);
755
+ return VFS.SQLITE_OK;
756
+ }
757
+ const compactMetaKey = getMetaKey(resolved.fileTag);
758
+ const metaData = await resolved.options.get(compactMetaKey);
759
+ this.#writeInt32(pResOut, metaData ? 1 : 0);
760
+ return VFS.SQLITE_OK;
761
+ }
762
+ xCheckReservedLock(_fileId, pResOut) {
763
+ this.#writeInt32(pResOut, 0);
764
+ return VFS.SQLITE_OK;
765
+ }
766
+ xLock(_fileId, _flags) {
767
+ return VFS.SQLITE_OK;
768
+ }
769
+ xUnlock(_fileId, _flags) {
770
+ return VFS.SQLITE_OK;
771
+ }
772
+ xFileControl(_fileId, _flags, _pArg) {
773
+ return VFS.SQLITE_NOTFOUND;
774
+ }
775
+ xDeviceCharacteristics(_fileId) {
776
+ return 0;
777
+ }
778
+ xFullPathname(_pVfs, zName, nOut, zOut) {
779
+ const path = this.#module.UTF8ToString(zName);
780
+ const bytes = TEXT_ENCODER.encode(path);
781
+ const out = this.#module.HEAPU8.subarray(zOut, zOut + nOut);
782
+ if (bytes.length >= out.length) {
783
+ return VFS.SQLITE_IOERR;
784
+ }
785
+ out.set(bytes, 0);
786
+ out[bytes.length] = 0;
787
+ return VFS.SQLITE_OK;
788
+ }
789
+ #decodeFilename(zName, flags) {
790
+ if (!zName) {
791
+ return null;
792
+ }
793
+ if (flags & VFS.SQLITE_OPEN_URI) {
794
+ let pName = zName;
795
+ let state = 1;
796
+ const charCodes = [];
797
+ while (state) {
798
+ const charCode = this.#module.HEAPU8[pName++];
799
+ if (charCode) {
800
+ charCodes.push(charCode);
801
+ continue;
802
+ }
803
+ if (!this.#module.HEAPU8[pName]) {
804
+ state = null;
805
+ }
806
+ switch (state) {
807
+ case 1:
808
+ charCodes.push("?".charCodeAt(0));
809
+ state = 2;
810
+ break;
811
+ case 2:
812
+ charCodes.push("=".charCodeAt(0));
813
+ state = 3;
814
+ break;
815
+ case 3:
816
+ charCodes.push("&".charCodeAt(0));
817
+ state = 2;
818
+ break;
819
+ }
820
+ }
821
+ return TEXT_DECODER.decode(new Uint8Array(charCodes));
822
+ }
823
+ return this.#module.UTF8ToString(zName);
824
+ }
825
+ #heapView() {
826
+ const heapBuffer = this.#module.HEAPU8.buffer;
827
+ if (heapBuffer !== this.#heapDataViewBuffer) {
828
+ this.#heapDataViewBuffer = heapBuffer;
829
+ this.#heapDataView = new DataView(heapBuffer);
830
+ }
831
+ return this.#heapDataView;
832
+ }
833
+ #writeInt32(pointer, value) {
834
+ const heapByteOffset = this.#module.HEAPU8.byteOffset + pointer;
835
+ this.#heapView().setInt32(heapByteOffset, value, true);
836
+ }
837
+ #writeBigInt64(pointer, value) {
838
+ const heapByteOffset = this.#module.HEAPU8.byteOffset + pointer;
839
+ this.#heapView().setBigInt64(heapByteOffset, value, true);
840
+ }
841
+ };
842
+ function delegalize(lo32, hi32) {
843
+ const hi = hi32 >>> 0;
844
+ const lo = lo32 >>> 0;
845
+ if (hi > MAX_FILE_SIZE_HI32) {
846
+ return -1;
847
+ }
848
+ if (hi === MAX_FILE_SIZE_HI32 && lo > MAX_FILE_SIZE_LO32) {
849
+ return -1;
850
+ }
851
+ return hi * UINT32_SIZE + lo;
852
+ }
853
+ export {
854
+ Database,
855
+ SqliteVfs
856
+ };
857
+ //# sourceMappingURL=index.js.map