@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.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vfs.ts","../../src/kv.ts","../../schemas/file-meta/versioned.ts","../schemas/file-meta/v1.ts"],"sourcesContent":["/**\n * SQLite raw database with KV storage backend\n *\n * This module provides a SQLite API that uses a KV-backed VFS\n * for storage. Each SqliteVfs instance is independent and can be\n * used concurrently with other instances.\n *\n * Keep this VFS on direct VFS.Base callbacks for minimal wrapper overhead.\n * Use @rivetkit/sqlite/src/FacadeVFS.js as the reference implementation for\n * callback ABI and pointer/data conversion behavior.\n * This implementation is optimized for single-writer semantics because each\n * actor owns one SQLite database.\n * SQLite invokes this VFS with byte-range file operations. This VFS maps those\n * ranges onto fixed-size KV chunks keyed by file tag and chunk index.\n * We intentionally rely on SQLite's pager cache for hot page reuse and do not\n * add a second cache in this VFS. This avoids duplicate cache invalidation\n * logic and keeps memory usage predictable for each actor.\n */\n\nimport * as VFS from \"@rivetkit/sqlite/src/VFS.js\";\nimport {\n\tFactory,\n\tSQLITE_OPEN_CREATE,\n\tSQLITE_OPEN_READWRITE,\n\tSQLITE_ROW,\n} from \"@rivetkit/sqlite\";\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport {\n\tCHUNK_SIZE,\n\tFILE_TAG_JOURNAL,\n\tFILE_TAG_MAIN,\n\tFILE_TAG_SHM,\n\tFILE_TAG_WAL,\n\tgetChunkKey,\n\tgetMetaKey,\n\ttype SqliteFileTag,\n} from \"./kv\";\nimport {\n\tFILE_META_VERSIONED,\n\tCURRENT_VERSION,\n} from \"../schemas/file-meta/versioned\";\nimport type { FileMeta } from \"../schemas/file-meta/mod\";\nimport type { KvVfsOptions } from \"./types\";\n\ntype SqliteEsmFactory = (config?: { wasmBinary?: ArrayBuffer | Uint8Array }) => Promise<unknown>;\ntype SQLite3Api = ReturnType<typeof Factory>;\ntype SqliteBindings = Parameters<SQLite3Api[\"bind_collection\"]>[1];\ntype SqliteVfsRegistration = Parameters<SQLite3Api[\"vfs_register\"]>[0];\n\ninterface SQLiteModule {\n\tUTF8ToString: (ptr: number) => string;\n\tHEAPU8: Uint8Array;\n}\n\nconst TEXT_ENCODER = new TextEncoder();\nconst TEXT_DECODER = new TextDecoder();\nconst SQLITE_MAX_PATHNAME_BYTES = 64;\n\n// Chunk keys encode the chunk index in 32 bits, so a file can span at most\n// 2^32 chunks. At 4 KiB/chunk this yields a hard limit of 16 TiB.\nconst UINT32_SIZE = 0x100000000;\nconst MAX_CHUNK_INDEX = 0xffffffff;\nconst MAX_FILE_SIZE_BYTES = (MAX_CHUNK_INDEX + 1) * CHUNK_SIZE;\nconst MAX_FILE_SIZE_HI32 = Math.floor(MAX_FILE_SIZE_BYTES / UINT32_SIZE);\nconst MAX_FILE_SIZE_LO32 = MAX_FILE_SIZE_BYTES % UINT32_SIZE;\n\n// libvfs captures this async/sync mask at registration time. Any VFS callback\n// that returns a Promise must be listed here so SQLite uses async relays.\nconst SQLITE_ASYNC_METHODS = new Set([\n\t\"xOpen\",\n\t\"xClose\",\n\t\"xRead\",\n\t\"xWrite\",\n\t\"xTruncate\",\n\t\"xSync\",\n\t\"xFileSize\",\n\t\"xDelete\",\n\t\"xAccess\",\n]);\n\ninterface LoadedSqliteRuntime {\n\tsqlite3: SQLite3Api;\n\tmodule: SQLiteModule;\n}\n\nfunction isSqliteEsmFactory(value: unknown): value is SqliteEsmFactory {\n\treturn typeof value === \"function\";\n}\n\nfunction isSQLiteModule(value: unknown): value is SQLiteModule {\n\tif (!value || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\tconst candidate = value as {\n\t\tUTF8ToString?: unknown;\n\t\tHEAPU8?: unknown;\n\t};\n\treturn (\n\t\ttypeof candidate.UTF8ToString === \"function\" &&\n\t\tcandidate.HEAPU8 instanceof Uint8Array\n\t);\n}\n\n\n/**\n * Lazily load and instantiate the async SQLite module for this VFS instance.\n * We do this on first open so actors that do not use SQLite do not pay module\n * parse and wasm initialization cost at startup, and we pass wasmBinary\n * explicitly so this works consistently in both ESM and CJS bundles.\n */\nasync function loadSqliteRuntime(): Promise<LoadedSqliteRuntime> {\n\t// Keep the module specifier assembled at runtime so TypeScript declaration\n\t// generation does not try to typecheck this deep dist import path.\n\t// Uses Array.join() instead of string concatenation to prevent esbuild/tsup\n\t// from constant-folding the expression at build time, which would allow\n\t// Turbopack to trace into the WASM package.\n\tconst specifier = [\"@rivetkit/sqlite\", \"dist\", \"wa-sqlite-async.mjs\"].join(\"/\");\n\tconst sqliteModule = await import(specifier);\n\tif (!isSqliteEsmFactory(sqliteModule.default)) {\n\t\tthrow new Error(\"Invalid SQLite ESM factory export\");\n\t}\n\tconst sqliteEsmFactory = sqliteModule.default;\n\tconst require = createRequire(import.meta.url);\n\tconst sqliteDistPath = \"@rivetkit/sqlite/dist/\";\n\tconst wasmPath = require.resolve(sqliteDistPath + \"wa-sqlite-async.wasm\");\n\tconst wasmBinary = readFileSync(wasmPath);\n\tconst module = await sqliteEsmFactory({ wasmBinary });\n\tif (!isSQLiteModule(module)) {\n\t\tthrow new Error(\"Invalid SQLite runtime module\");\n\t}\n\treturn {\n\t\tsqlite3: Factory(module),\n\t\tmodule,\n\t};\n}\n\n/**\n * Represents an open file\n */\ninterface OpenFile {\n\t/** File path */\n\tpath: string;\n\t/** File kind tag used by compact key layout */\n\tfileTag: SqliteFileTag;\n\t/** Precomputed metadata key */\n\tmetaKey: Uint8Array;\n\t/** File size in bytes */\n\tsize: number;\n\t/** True when in-memory size has not been persisted yet */\n\tmetaDirty: boolean;\n\t/** Open flags */\n\tflags: number;\n\t/** KV options for this file */\n\toptions: KvVfsOptions;\n}\n\ninterface ResolvedFile {\n\toptions: KvVfsOptions;\n\tfileTag: SqliteFileTag;\n}\n\n/**\n * Encodes file metadata to a Uint8Array using BARE schema\n */\nfunction encodeFileMeta(size: number): Uint8Array {\n\tconst meta: FileMeta = { size: BigInt(size) };\n\treturn FILE_META_VERSIONED.serializeWithEmbeddedVersion(\n\t\tmeta,\n\t\tCURRENT_VERSION,\n\t);\n}\n\n/**\n * Decodes file metadata from a Uint8Array using BARE schema\n */\nfunction decodeFileMeta(data: Uint8Array): number {\n\tconst meta = FILE_META_VERSIONED.deserializeWithEmbeddedVersion(data);\n\treturn Number(meta.size);\n}\n\nfunction isValidFileSize(size: number): boolean {\n\treturn Number.isSafeInteger(size) && size >= 0 && size <= MAX_FILE_SIZE_BYTES;\n}\n\n/**\n * Simple async mutex for serializing database operations\n * @rivetkit/sqlite calls are not safe to run concurrently on one module instance\n */\nclass AsyncMutex {\n\t#locked = false;\n\t#waiting: (() => void)[] = [];\n\n\tasync acquire(): Promise<void> {\n\t\twhile (this.#locked) {\n\t\t\tawait new Promise<void>((resolve) => this.#waiting.push(resolve));\n\t\t}\n\t\tthis.#locked = true;\n\t}\n\n\trelease(): void {\n\t\tthis.#locked = false;\n\t\tconst next = this.#waiting.shift();\n\t\tif (next) {\n\t\t\tnext();\n\t\t}\n\t}\n\n\tasync run<T>(fn: () => Promise<T>): Promise<T> {\n\t\tawait this.acquire();\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tthis.release();\n\t\t}\n\t}\n}\n\n/**\n * Database wrapper that provides a simplified SQLite API\n */\nexport class Database {\n\treadonly #sqlite3: SQLite3Api;\n\treadonly #handle: number;\n\treadonly #fileName: string;\n\treadonly #onClose: () => void;\n\treadonly #sqliteMutex: AsyncMutex;\n\n\tconstructor(\n\t\tsqlite3: SQLite3Api,\n\t\thandle: number,\n\t\tfileName: string,\n\t\tonClose: () => void,\n\t\tsqliteMutex: AsyncMutex,\n\t) {\n\t\tthis.#sqlite3 = sqlite3;\n\t\tthis.#handle = handle;\n\t\tthis.#fileName = fileName;\n\t\tthis.#onClose = onClose;\n\t\tthis.#sqliteMutex = sqliteMutex;\n\t}\n\n\t/**\n\t * Execute SQL with optional row callback\n\t * @param sql - SQL statement to execute\n\t * @param callback - Called for each result row with (row, columns)\n\t */\n\tasync exec(sql: string, callback?: (row: unknown[], columns: string[]) => void): Promise<void> {\n\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\tawait this.#sqlite3.exec(this.#handle, sql, callback);\n\t\t});\n\t}\n\n\t/**\n\t * Execute a parameterized SQL statement (no result rows)\n\t * @param sql - SQL statement with ? placeholders\n\t * @param params - Parameter values to bind\n\t */\n\tasync run(sql: string, params?: SqliteBindings): Promise<void> {\n\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\tfor await (const stmt of this.#sqlite3.statements(this.#handle, sql)) {\n\t\t\t\tif (params) {\n\t\t\t\t\tthis.#sqlite3.bind_collection(stmt, params);\n\t\t\t\t}\n\t\t\t\twhile ((await this.#sqlite3.step(stmt)) === SQLITE_ROW) {\n\t\t\t\t\t// Consume rows for statements that return results.\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Execute a parameterized SQL query and return results\n\t * @param sql - SQL query with ? placeholders\n\t * @param params - Parameter values to bind\n\t * @returns Object with rows (array of arrays) and columns (column names)\n\t */\n\tasync query(sql: string, params?: SqliteBindings): Promise<{ rows: unknown[][]; columns: string[] }> {\n\t\treturn this.#sqliteMutex.run(async () => {\n\t\t\tconst rows: unknown[][] = [];\n\t\t\tlet columns: string[] = [];\n\t\t\tfor await (const stmt of this.#sqlite3.statements(this.#handle, sql)) {\n\t\t\t\tif (params) {\n\t\t\t\t\tthis.#sqlite3.bind_collection(stmt, params);\n\t\t\t\t}\n\n\t\t\t\twhile ((await this.#sqlite3.step(stmt)) === SQLITE_ROW) {\n\t\t\t\t\tif (columns.length === 0) {\n\t\t\t\t\t\tcolumns = this.#sqlite3.column_names(stmt);\n\t\t\t\t\t}\n\t\t\t\t\trows.push(this.#sqlite3.row(stmt));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { rows, columns };\n\t\t});\n\t}\n\n\t/**\n\t * Close the database\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\tawait this.#sqlite3.close(this.#handle);\n\t\t});\n\t\tthis.#onClose();\n\t}\n\n\t/**\n\t * Get the raw @rivetkit/sqlite API (for advanced usage)\n\t */\n\tget sqlite3(): SQLite3Api {\n\t\treturn this.#sqlite3;\n\t}\n\n\t/**\n\t * Get the raw database handle (for advanced usage)\n\t */\n\tget handle(): number {\n\t\treturn this.#handle;\n\t}\n}\n\n/**\n * SQLite VFS backed by KV storage.\n *\n * Each instance is independent and has its own @rivetkit/sqlite WASM module.\n * This allows multiple instances to operate concurrently without interference.\n */\nexport class SqliteVfs {\n\t#sqlite3: SQLite3Api | null = null;\n\t#sqliteSystem: SqliteSystem | null = null;\n\t#initPromise: Promise<void> | null = null;\n\t#openMutex = new AsyncMutex();\n\t#sqliteMutex = new AsyncMutex();\n\t#instanceId: string;\n\t#destroyed = false;\n\n\tconstructor() {\n\t\t// Generate unique instance ID for VFS name\n\t\tthis.#instanceId = crypto.randomUUID().replace(/-/g, '').slice(0, 8);\n\t}\n\n\t/**\n\t * Initialize @rivetkit/sqlite and VFS (called once per instance)\n\t */\n\tasync #ensureInitialized(): Promise<void> {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new Error(\"SqliteVfs is closed\");\n\t\t}\n\n\t\t// Fast path: already initialized\n\t\tif (this.#sqlite3 && this.#sqliteSystem) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Synchronously create the promise if not started\n\t\tif (!this.#initPromise) {\n\t\t\tthis.#initPromise = (async () => {\n\t\t\t\tconst { sqlite3, module } = await loadSqliteRuntime();\n\t\t\t\tif (this.#destroyed) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.#sqlite3 = sqlite3;\n\t\t\t\tthis.#sqliteSystem = new SqliteSystem(\n\t\t\t\t\tsqlite3,\n\t\t\t\t\tmodule,\n\t\t\t\t\t`kv-vfs-${this.#instanceId}`,\n\t\t\t\t);\n\t\t\t\tthis.#sqliteSystem.register();\n\t\t\t})();\n\t\t}\n\n\t\t// Wait for initialization\n\t\ttry {\n\t\t\tawait this.#initPromise;\n\t\t} catch (error) {\n\t\t\tthis.#initPromise = null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Open a SQLite database using KV storage backend\n\t *\n\t * @param fileName - The database file name (typically the actor ID)\n\t * @param options - KV storage operations for this database\n\t * @returns A Database instance\n\t */\n\tasync open(\n\t\tfileName: string,\n\t\toptions: KvVfsOptions,\n\t): Promise<Database> {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new Error(\"SqliteVfs is closed\");\n\t\t}\n\n\t\t// Serialize all open operations within this instance\n\t\tawait this.#openMutex.acquire();\n\t\ttry {\n\t\t\t// Initialize @rivetkit/sqlite and SqliteSystem on first call\n\t\t\tawait this.#ensureInitialized();\n\n\t\t\tif (!this.#sqlite3 || !this.#sqliteSystem) {\n\t\t\t\tthrow new Error(\"Failed to initialize SQLite\");\n\t\t\t}\n\t\t\tconst sqlite3 = this.#sqlite3;\n\t\t\tconst sqliteSystem = this.#sqliteSystem;\n\n\t\t\t// Register this filename with its KV options\n\t\t\tsqliteSystem.registerFile(fileName, options);\n\n\t\t\t// Open database\n\t\t\tconst db = await this.#sqliteMutex.run(async () =>\n\t\t\t\tsqlite3.open_v2(\n\t\t\t\t\tfileName,\n\t\t\t\t\tSQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,\n\t\t\t\t\tsqliteSystem.name,\n\t\t\t\t),\n\t\t\t);\n\t\t\t// TODO: Benchmark PRAGMA tuning for KV-backed SQLite after open.\n\t\t\t// Start with journal_mode=PERSIST and journal_size_limit to reduce\n\t\t\t// journal churn on high-latency KV without introducing WAL.\n\n\t\t\t// Create cleanup callback\n\t\t\tconst onClose = () => {\n\t\t\t\tsqliteSystem.unregisterFile(fileName);\n\t\t\t};\n\n\t\t\treturn new Database(\n\t\t\t\tsqlite3,\n\t\t\t\tdb,\n\t\t\t\tfileName,\n\t\t\t\tonClose,\n\t\t\t\tthis.#sqliteMutex,\n\t\t\t);\n\t\t} finally {\n\t\t\tthis.#openMutex.release();\n\t\t}\n\t}\n\n\t/**\n\t * Tears down this VFS instance and releases internal references.\n\t */\n\tasync destroy(): Promise<void> {\n\t\tif (this.#destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#destroyed = true;\n\n\t\tconst initPromise = this.#initPromise;\n\t\tif (initPromise) {\n\t\t\ttry {\n\t\t\t\tawait initPromise;\n\t\t\t} catch {\n\t\t\t\t// Initialization failure already surfaced to caller.\n\t\t\t}\n\t\t}\n\n\t\tif (this.#sqliteSystem) {\n\t\t\tawait this.#sqliteSystem.close();\n\t\t}\n\n\t\tthis.#sqliteSystem = null;\n\t\tthis.#sqlite3 = null;\n\t\tthis.#initPromise = null;\n\t}\n\n\t/**\n\t * Alias for destroy to align with DB-style lifecycle naming.\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.destroy();\n\t}\n}\n\n/**\n * Internal VFS implementation\n */\nclass SqliteSystem implements SqliteVfsRegistration {\n\treadonly name: string;\n\treadonly mxPathName = SQLITE_MAX_PATHNAME_BYTES;\n\treadonly mxPathname = SQLITE_MAX_PATHNAME_BYTES;\n\t#mainFileName: string | null = null;\n\t#mainFileOptions: KvVfsOptions | null = null;\n\treadonly #openFiles: Map<number, OpenFile> = new Map();\n\treadonly #sqlite3: SQLite3Api;\n\treadonly #module: SQLiteModule;\n\t#heapDataView: DataView;\n\t#heapDataViewBuffer: ArrayBufferLike;\n\n\tconstructor(sqlite3: SQLite3Api, module: SQLiteModule, name: string) {\n\t\tthis.name = name;\n\t\tthis.#sqlite3 = sqlite3;\n\t\tthis.#module = module;\n\t\tthis.#heapDataViewBuffer = module.HEAPU8.buffer;\n\t\tthis.#heapDataView = new DataView(this.#heapDataViewBuffer);\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.#openFiles.clear();\n\t\tthis.#mainFileName = null;\n\t\tthis.#mainFileOptions = null;\n\t}\n\n\tisReady(): boolean {\n\t\treturn true;\n\t}\n\n\thasAsyncMethod(methodName: string): boolean {\n\t\treturn SQLITE_ASYNC_METHODS.has(methodName);\n\t}\n\n\t/**\n\t * Registers the VFS with SQLite\n\t */\n\tregister(): void {\n\t\tthis.#sqlite3.vfs_register(this, false);\n\t}\n\n\t/**\n\t * Registers a file with its KV options (before opening)\n\t */\n\tregisterFile(fileName: string, options: KvVfsOptions): void {\n\t\tif (!this.#mainFileName) {\n\t\t\tthis.#mainFileName = fileName;\n\t\t\tthis.#mainFileOptions = options;\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#mainFileName !== fileName) {\n\t\t\tthrow new Error(\n\t\t\t\t`SqliteSystem is actor-scoped and expects one main file. Got ${fileName}, expected ${this.#mainFileName}.`,\n\t\t\t);\n\t\t}\n\n\t\tthis.#mainFileOptions = options;\n\t}\n\n\t/**\n\t * Unregisters a file's KV options (after closing)\n\t */\n\tunregisterFile(fileName: string): void {\n\t\tif (this.#mainFileName === fileName) {\n\t\t\tthis.#mainFileName = null;\n\t\t\tthis.#mainFileOptions = null;\n\t\t}\n\t}\n\n\t/**\n\t * Resolve file path to the actor's main DB file or known SQLite sidecars.\n\t */\n\t#resolveFile(path: string): ResolvedFile | null {\n\t\tif (!this.#mainFileName || !this.#mainFileOptions) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (path === this.#mainFileName) {\n\t\t\treturn { options: this.#mainFileOptions, fileTag: FILE_TAG_MAIN };\n\t\t}\n\t\tif (path === `${this.#mainFileName}-journal`) {\n\t\t\treturn { options: this.#mainFileOptions, fileTag: FILE_TAG_JOURNAL };\n\t\t}\n\t\tif (path === `${this.#mainFileName}-wal`) {\n\t\t\treturn { options: this.#mainFileOptions, fileTag: FILE_TAG_WAL };\n\t\t}\n\t\tif (path === `${this.#mainFileName}-shm`) {\n\t\t\treturn { options: this.#mainFileOptions, fileTag: FILE_TAG_SHM };\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t#resolveFileOrThrow(path: string): ResolvedFile {\n\t\tconst resolved = this.#resolveFile(path);\n\t\tif (resolved) {\n\t\t\treturn resolved;\n\t\t}\n\n\t\tif (!this.#mainFileName) {\n\t\t\tthrow new Error(`No KV options registered for file: ${path}`);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`Unsupported SQLite file path ${path}. Expected one of ${this.#mainFileName}, ${this.#mainFileName}-journal, ${this.#mainFileName}-wal, ${this.#mainFileName}-shm.`,\n\t\t);\n\t}\n\n\t#chunkKey(file: OpenFile, chunkIndex: number): Uint8Array {\n\t\treturn getChunkKey(file.fileTag, chunkIndex);\n\t}\n\n\tasync xOpen(\n\t\t_pVfs: number,\n\t\tzName: number,\n\t\tfileId: number,\n\t\tflags: number,\n\t\tpOutFlags: number,\n\t): Promise<number> {\n\t\tconst path = this.#decodeFilename(zName, flags);\n\t\tif (!path) {\n\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t}\n\n\t\t// Get the registered KV options for this file\n\t\t// For journal/wal files, use the main database's options\n\t\tconst { options, fileTag } = this.#resolveFileOrThrow(path);\n\t\tconst metaKey = getMetaKey(fileTag);\n\n\t\t// Get existing file size if the file exists\n\t\tconst sizeData = await options.get(metaKey);\n\n\t\tlet size: number;\n\n\t\tif (sizeData) {\n\t\t\t// File exists, use existing size\n\t\t\tsize = decodeFileMeta(sizeData);\n\t\t\tif (!isValidFileSize(size)) {\n\t\t\t\treturn VFS.SQLITE_IOERR;\n\t\t\t}\n\t\t} else if (flags & VFS.SQLITE_OPEN_CREATE) {\n\t\t\t// File doesn't exist, create it\n\t\t\tsize = 0;\n\t\t\tawait options.put(metaKey, encodeFileMeta(size));\n\t\t} else {\n\t\t\t// File doesn't exist and we're not creating it\n\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t}\n\n\t\t// Store open file info with options\n\t\tthis.#openFiles.set(fileId, {\n\t\t\tpath,\n\t\t\tfileTag,\n\t\t\tmetaKey,\n\t\t\tsize,\n\t\t\tmetaDirty: false,\n\t\t\tflags,\n\t\t\toptions,\n\t\t});\n\n\t\t// Set output flags to the actual flags used.\n\t\tthis.#writeInt32(pOutFlags, flags);\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xClose(fileId: number): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\t// Delete-on-close files should skip metadata flush because the file will\n\t\t// be removed immediately.\n\t\tif (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {\n\t\t\tawait this.#delete(file.path);\n\t\t\tthis.#openFiles.delete(fileId);\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tif (file.metaDirty) {\n\t\t\tawait file.options.put(file.metaKey, encodeFileMeta(file.size));\n\t\t\tfile.metaDirty = false;\n\t\t}\n\n\t\tthis.#openFiles.delete(fileId);\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xRead(\n\t\tfileId: number,\n\t\tpData: number,\n\t\tiAmt: number,\n\t\tiOffsetLo: number,\n\t\tiOffsetHi: number,\n\t): Promise<number> {\n\t\tif (iAmt === 0) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_READ;\n\t\t}\n\n\t\tconst data = this.#module.HEAPU8.subarray(pData, pData + iAmt);\n\t\tconst options = file.options;\n\t\tconst requestedLength = iAmt;\n\t\tconst iOffset = delegalize(iOffsetLo, iOffsetHi);\n\t\tif (iOffset < 0) {\n\t\t\treturn VFS.SQLITE_IOERR_READ;\n\t\t}\n\t\tconst fileSize = file.size;\n\n\t\t// If offset is beyond file size, return short read with zeroed buffer\n\t\tif (iOffset >= fileSize) {\n\t\t\tdata.fill(0);\n\t\t\treturn VFS.SQLITE_IOERR_SHORT_READ;\n\t\t}\n\n\t\t// Calculate which chunks we need to read\n\t\tconst startChunk = Math.floor(iOffset / CHUNK_SIZE);\n\t\tconst endChunk = Math.floor((iOffset + requestedLength - 1) / CHUNK_SIZE);\n\n\t\t// Fetch all needed chunks\n\t\tconst chunkKeys: Uint8Array[] = [];\n\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\tchunkKeys.push(this.#chunkKey(file, i));\n\t\t}\n\n\t\tconst chunks = await options.getBatch(chunkKeys);\n\n\t\t// Copy data from chunks to output buffer\n\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\tconst chunkData = chunks[i - startChunk];\n\t\t\tconst chunkOffset = i * CHUNK_SIZE;\n\n\t\t\t// Calculate the range within this chunk\n\t\t\tconst readStart = Math.max(0, iOffset - chunkOffset);\n\t\t\tconst readEnd = Math.min(\n\t\t\t\tCHUNK_SIZE,\n\t\t\t\tiOffset + requestedLength - chunkOffset,\n\t\t\t);\n\n\t\t\tif (chunkData) {\n\t\t\t\t// Copy available data\n\t\t\t\tconst sourceStart = readStart;\n\t\t\t\tconst sourceEnd = Math.min(readEnd, chunkData.length);\n\t\t\t\tconst destStart = chunkOffset + readStart - iOffset;\n\n\t\t\t\tif (sourceEnd > sourceStart) {\n\t\t\t\t\tdata.set(chunkData.subarray(sourceStart, sourceEnd), destStart);\n\t\t\t\t}\n\n\t\t\t\t// Zero-fill if chunk is smaller than expected\n\t\t\t\tif (sourceEnd < readEnd) {\n\t\t\t\t\tconst zeroStart = destStart + (sourceEnd - sourceStart);\n\t\t\t\t\tconst zeroEnd = destStart + (readEnd - readStart);\n\t\t\t\t\tdata.fill(0, zeroStart, zeroEnd);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Chunk doesn't exist, zero-fill\n\t\t\t\tconst destStart = chunkOffset + readStart - iOffset;\n\t\t\t\tconst destEnd = destStart + (readEnd - readStart);\n\t\t\t\tdata.fill(0, destStart, destEnd);\n\t\t\t}\n\t\t}\n\n\t\t// If we read less than requested (past EOF), return short read\n\t\tconst actualBytes = Math.min(requestedLength, fileSize - iOffset);\n\t\tif (actualBytes < requestedLength) {\n\t\t\tdata.fill(0, actualBytes);\n\t\t\treturn VFS.SQLITE_IOERR_SHORT_READ;\n\t\t}\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xWrite(\n\t\tfileId: number,\n\t\tpData: number,\n\t\tiAmt: number,\n\t\tiOffsetLo: number,\n\t\tiOffsetHi: number,\n\t): Promise<number> {\n\t\tif (iAmt === 0) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\n\t\tconst data = this.#module.HEAPU8.subarray(pData, pData + iAmt);\n\t\tconst iOffset = delegalize(iOffsetLo, iOffsetHi);\n\t\tif (iOffset < 0) {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\t\tconst options = file.options;\n\t\tconst writeLength = iAmt;\n\t\tconst writeEndOffset = iOffset + writeLength;\n\t\tif (writeEndOffset > MAX_FILE_SIZE_BYTES) {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\n\t\t// Calculate which chunks we need to modify\n\t\tconst startChunk = Math.floor(iOffset / CHUNK_SIZE);\n\t\tconst endChunk = Math.floor((iOffset + writeLength - 1) / CHUNK_SIZE);\n\n\t\tinterface WritePlan {\n\t\t\tchunkKey: Uint8Array;\n\t\t\tchunkOffset: number;\n\t\t\twriteStart: number;\n\t\t\twriteEnd: number;\n\t\t\texistingChunkIndex: number;\n\t\t}\n\n\t\t// Only fetch chunks where we must preserve existing prefix/suffix bytes.\n\t\tconst plans: WritePlan[] = [];\n\t\tconst chunkKeysToFetch: Uint8Array[] = [];\n\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\tconst chunkOffset = i * CHUNK_SIZE;\n\t\t\tconst writeStart = Math.max(0, iOffset - chunkOffset);\n\t\t\tconst writeEnd = Math.min(\n\t\t\t\tCHUNK_SIZE,\n\t\t\t\tiOffset + writeLength - chunkOffset,\n\t\t\t);\n\t\t\tconst existingBytesInChunk = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(CHUNK_SIZE, file.size - chunkOffset),\n\t\t\t);\n\t\t\tconst needsExisting = writeStart > 0 || existingBytesInChunk > writeEnd;\n\t\t\tconst chunkKey = this.#chunkKey(file, i);\n\t\t\tlet existingChunkIndex = -1;\n\t\t\tif (needsExisting) {\n\t\t\t\texistingChunkIndex = chunkKeysToFetch.length;\n\t\t\t\tchunkKeysToFetch.push(chunkKey);\n\t\t\t}\n\t\t\tplans.push({\n\t\t\t\tchunkKey,\n\t\t\t\tchunkOffset,\n\t\t\t\twriteStart,\n\t\t\t\twriteEnd,\n\t\t\t\texistingChunkIndex,\n\t\t\t});\n\t\t}\n\n\t\tconst existingChunks = chunkKeysToFetch.length > 0\n\t\t\t? await options.getBatch(chunkKeysToFetch)\n\t\t\t: [];\n\n\t\t// Prepare new chunk data\n\t\tconst entriesToWrite: [Uint8Array, Uint8Array][] = [];\n\n\t\tfor (const plan of plans) {\n\t\t\tconst existingChunk =\n\t\t\t\tplan.existingChunkIndex >= 0\n\t\t\t\t\t? existingChunks[plan.existingChunkIndex]\n\t\t\t\t\t: null;\n\t\t\t// Create new chunk data\n\t\t\tlet newChunk: Uint8Array;\n\t\t\tif (existingChunk) {\n\t\t\t\tnewChunk = new Uint8Array(Math.max(existingChunk.length, plan.writeEnd));\n\t\t\t\tnewChunk.set(existingChunk);\n\t\t\t} else {\n\t\t\t\tnewChunk = new Uint8Array(plan.writeEnd);\n\t\t\t}\n\n\t\t\t// Copy data from input buffer to chunk\n\t\t\tconst sourceStart = plan.chunkOffset + plan.writeStart - iOffset;\n\t\t\tconst sourceEnd = sourceStart + (plan.writeEnd - plan.writeStart);\n\t\t\tnewChunk.set(data.subarray(sourceStart, sourceEnd), plan.writeStart);\n\n\t\t\tentriesToWrite.push([plan.chunkKey, newChunk]);\n\t\t}\n\n\t\t// Update file size if we wrote past the end\n\t\tconst previousSize = file.size;\n\t\tconst newSize = Math.max(file.size, writeEndOffset);\n\t\tif (newSize !== previousSize) {\n\t\t\tfile.size = newSize;\n\t\t\tfile.metaDirty = true;\n\t\t}\n\t\tif (file.metaDirty) {\n\t\t\tentriesToWrite.push([file.metaKey, encodeFileMeta(file.size)]);\n\t\t}\n\n\t\t// Write all chunks and metadata\n\t\tawait options.putBatch(entriesToWrite);\n\t\tif (file.metaDirty) {\n\t\t\tfile.metaDirty = false;\n\t\t}\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xTruncate(\n\t\tfileId: number,\n\t\tsizeLo: number,\n\t\tsizeHi: number,\n\t): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t}\n\n\t\tconst size = delegalize(sizeLo, sizeHi);\n\t\tif (size < 0 || size > MAX_FILE_SIZE_BYTES) {\n\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t}\n\t\tconst options = file.options;\n\n\t\t// If truncating to larger size, just update metadata\n\t\tif (size >= file.size) {\n\t\t\tif (size > file.size) {\n\t\t\t\tfile.size = size;\n\t\t\t\tfile.metaDirty = true;\n\t\t\t\tawait options.put(file.metaKey, encodeFileMeta(file.size));\n\t\t\t\tfile.metaDirty = false;\n\t\t\t}\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\t// Calculate which chunks to delete\n\t\t// Note: When size=0, lastChunkToKeep = floor(-1/4096) = -1, which means\n\t\t// all chunks (starting from index 0) will be deleted in the loop below.\n\t\tconst lastChunkToKeep = Math.floor((size - 1) / CHUNK_SIZE);\n\t\tconst lastExistingChunk = Math.floor((file.size - 1) / CHUNK_SIZE);\n\n\t\t// Delete chunks beyond the new size\n\t\tconst keysToDelete: Uint8Array[] = [];\n\t\tfor (let i = lastChunkToKeep + 1; i <= lastExistingChunk; i++) {\n\t\t\tkeysToDelete.push(this.#chunkKey(file, i));\n\t\t}\n\n\t\tif (keysToDelete.length > 0) {\n\t\t\tawait options.deleteBatch(keysToDelete);\n\t\t}\n\n\t\t// Truncate the last kept chunk if needed\n\t\tif (size > 0 && size % CHUNK_SIZE !== 0) {\n\t\t\tconst lastChunkKey = this.#chunkKey(file, lastChunkToKeep);\n\t\t\tconst lastChunkData = await options.get(lastChunkKey);\n\n\t\t\tif (lastChunkData && lastChunkData.length > size % CHUNK_SIZE) {\n\t\t\t\tconst truncatedChunk = lastChunkData.subarray(0, size % CHUNK_SIZE);\n\t\t\t\tawait options.put(lastChunkKey, truncatedChunk);\n\t\t\t}\n\t\t}\n\n\t\t// Update file size\n\t\tfile.size = size;\n\t\tfile.metaDirty = true;\n\t\tawait options.put(file.metaKey, encodeFileMeta(file.size));\n\t\tfile.metaDirty = false;\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xSync(fileId: number, _flags: number): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file || !file.metaDirty) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tawait file.options.put(file.metaKey, encodeFileMeta(file.size));\n\t\tfile.metaDirty = false;\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xFileSize(fileId: number, pSize: number): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_FSTAT;\n\t\t}\n\n\t\t// Set size as 64-bit integer.\n\t\tthis.#writeBigInt64(pSize, BigInt(file.size));\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xDelete(_pVfs: number, zName: number, _syncDir: number): Promise<number> {\n\t\tawait this.#delete(this.#module.UTF8ToString(zName));\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\t/**\n\t * Internal delete implementation\n\t */\n\tasync #delete(path: string): Promise<void> {\n\t\tconst { options, fileTag } = this.#resolveFileOrThrow(path);\n\t\tconst metaKey = getMetaKey(fileTag);\n\n\t\t// Get file size to find out how many chunks to delete\n\t\tconst sizeData = await options.get(metaKey);\n\n\t\tif (!sizeData) {\n\t\t\t// File doesn't exist, that's OK\n\t\t\treturn;\n\t\t}\n\n\t\tconst size = decodeFileMeta(sizeData);\n\n\t\t// Delete all chunks\n\t\tconst keysToDelete: Uint8Array[] = [metaKey];\n\t\tconst numChunks = Math.ceil(size / CHUNK_SIZE);\n\t\tfor (let i = 0; i < numChunks; i++) {\n\t\t\tkeysToDelete.push(getChunkKey(fileTag, i));\n\t\t}\n\n\t\tawait options.deleteBatch(keysToDelete);\n\t}\n\n\tasync xAccess(\n\t\t_pVfs: number,\n\t\tzName: number,\n\t\t_flags: number,\n\t\tpResOut: number,\n\t): Promise<number> {\n\t\t// TODO: Measure how often xAccess runs during open and whether these\n\t\t// existence checks add meaningful KV round-trip overhead. If they do,\n\t\t// consider serving file existence from in-memory state.\n\t\tconst path = this.#module.UTF8ToString(zName);\n\t\tconst resolved = this.#resolveFile(path);\n\t\tif (!resolved) {\n\t\t\t// File not registered, doesn't exist\n\t\t\tthis.#writeInt32(pResOut, 0);\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tconst compactMetaKey = getMetaKey(resolved.fileTag);\n\t\tconst metaData = await resolved.options.get(compactMetaKey);\n\n\t\t// Set result: 1 if file exists, 0 otherwise\n\t\tthis.#writeInt32(pResOut, metaData ? 1 : 0);\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txCheckReservedLock(_fileId: number, pResOut: number): number {\n\t\t// This VFS is actor-scoped with one writer, so there is no external\n\t\t// reserved lock state to report.\n\t\tthis.#writeInt32(pResOut, 0);\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txLock(_fileId: number, _flags: number): number {\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txUnlock(_fileId: number, _flags: number): number {\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txFileControl(_fileId: number, _flags: number, _pArg: number): number {\n\t\treturn VFS.SQLITE_NOTFOUND;\n\t}\n\n\txDeviceCharacteristics(_fileId: number): number {\n\t\treturn 0;\n\t}\n\n\txFullPathname(_pVfs: number, zName: number, nOut: number, zOut: number): number {\n\t\tconst path = this.#module.UTF8ToString(zName);\n\t\tconst bytes = TEXT_ENCODER.encode(path);\n\t\tconst out = this.#module.HEAPU8.subarray(zOut, zOut + nOut);\n\t\tif (bytes.length >= out.length) {\n\t\t\treturn VFS.SQLITE_IOERR;\n\t\t}\n\t\tout.set(bytes, 0);\n\t\tout[bytes.length] = 0;\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\t#decodeFilename(zName: number, flags: number): string | null {\n\t\tif (!zName) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (flags & VFS.SQLITE_OPEN_URI) {\n\t\t\t// Decode SQLite URI filename layout: path\\0key\\0value\\0...\\0\n\t\t\tlet pName = zName;\n\t\t\tlet state: 1 | 2 | 3 | null = 1;\n\t\t\tconst charCodes: number[] = [];\n\t\t\twhile (state) {\n\t\t\t\tconst charCode = this.#module.HEAPU8[pName++];\n\t\t\t\tif (charCode) {\n\t\t\t\t\tcharCodes.push(charCode);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!this.#module.HEAPU8[pName]) {\n\t\t\t\t\tstate = null;\n\t\t\t\t}\n\t\t\t\tswitch (state) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tcharCodes.push(\"?\".charCodeAt(0));\n\t\t\t\t\t\tstate = 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tcharCodes.push(\"=\".charCodeAt(0));\n\t\t\t\t\t\tstate = 3;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tcharCodes.push(\"&\".charCodeAt(0));\n\t\t\t\t\t\tstate = 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn TEXT_DECODER.decode(new Uint8Array(charCodes));\n\t\t}\n\n\t\treturn this.#module.UTF8ToString(zName);\n\t}\n\n\t#heapView(): DataView {\n\t\tconst heapBuffer = this.#module.HEAPU8.buffer;\n\t\tif (heapBuffer !== this.#heapDataViewBuffer) {\n\t\t\tthis.#heapDataViewBuffer = heapBuffer;\n\t\t\tthis.#heapDataView = new DataView(heapBuffer);\n\t\t}\n\t\treturn this.#heapDataView;\n\t}\n\n\t#writeInt32(pointer: number, value: number): void {\n\t\tconst heapByteOffset = this.#module.HEAPU8.byteOffset + pointer;\n\t\tthis.#heapView().setInt32(heapByteOffset, value, true);\n\t}\n\n\t#writeBigInt64(pointer: number, value: bigint): void {\n\t\tconst heapByteOffset = this.#module.HEAPU8.byteOffset + pointer;\n\t\tthis.#heapView().setBigInt64(heapByteOffset, value, true);\n\t}\n}\n\n/**\n * Rebuild an i64 from Emscripten's legalized (lo32, hi32) pair.\n * SQLite passes file offsets and sizes this way. We decode into unsigned words\n * and reject values above the VFS max file size.\n */\nfunction delegalize(lo32: number, hi32: number): number {\n\tconst hi = hi32 >>> 0;\n\tconst lo = lo32 >>> 0;\n\tif (hi > MAX_FILE_SIZE_HI32) {\n\t\treturn -1;\n\t}\n\tif (hi === MAX_FILE_SIZE_HI32 && lo > MAX_FILE_SIZE_LO32) {\n\t\treturn -1;\n\t}\n\treturn (hi * UINT32_SIZE) + lo;\n}\n","/**\n * Key management for SQLite VFS storage\n *\n * This module contains constants and utilities for building keys used in the\n * key-value store for SQLite file storage.\n */\n\n/**\n * Size of each file chunk stored in KV.\n *\n * SQLite calls the VFS with byte ranges, but KV stores whole values by key.\n * The VFS maps each byte range to one or more fixed-size chunks, then uses\n * chunk keys to read or write those values in KV.\n */\nexport const CHUNK_SIZE = 4096;\n\n/** Top-level SQLite prefix (must match SQLITE_PREFIX in actor KV system) */\nexport const SQLITE_PREFIX = 8;\n\n/** Schema version namespace byte after SQLITE_PREFIX */\nexport const SQLITE_SCHEMA_VERSION = 1;\n\n/** Key prefix byte for file metadata (after SQLITE_PREFIX + version) */\nexport const META_PREFIX = 0;\n\n/** Key prefix byte for file chunks (after SQLITE_PREFIX + version) */\nexport const CHUNK_PREFIX = 1;\n\n/** File kind tag for the actor's main database file */\nexport const FILE_TAG_MAIN = 0;\n\n/** File kind tag for the actor's rollback journal sidecar */\nexport const FILE_TAG_JOURNAL = 1;\n\n/** File kind tag for the actor's WAL sidecar */\nexport const FILE_TAG_WAL = 2;\n\n/** File kind tag for the actor's SHM sidecar */\nexport const FILE_TAG_SHM = 3;\n\nexport type SqliteFileTag =\n\t| typeof FILE_TAG_MAIN\n\t| typeof FILE_TAG_JOURNAL\n\t| typeof FILE_TAG_WAL\n\t| typeof FILE_TAG_SHM;\n\n/**\n * Gets the key for file metadata\n * Format: [SQLITE_PREFIX (1 byte), version (1 byte), META_PREFIX (1 byte), file tag (1 byte)]\n */\nexport function getMetaKey(fileTag: SqliteFileTag): Uint8Array {\n\tconst key = new Uint8Array(4);\n\tkey[0] = SQLITE_PREFIX;\n\tkey[1] = SQLITE_SCHEMA_VERSION;\n\tkey[2] = META_PREFIX;\n\tkey[3] = fileTag;\n\treturn key;\n}\n\n/**\n * Gets the key for one chunk of file data.\n * Format: [SQLITE_PREFIX, CHUNK_PREFIX, file tag, chunk index (u32 big-endian)]\n *\n * The chunk index is derived from byte offset as floor(offset / CHUNK_SIZE),\n * which is how SQLite byte ranges map onto KV keys.\n */\nexport function getChunkKey(\n\tfileTag: SqliteFileTag,\n\tchunkIndex: number,\n): Uint8Array {\n\tconst key = new Uint8Array(8);\n\tkey[0] = SQLITE_PREFIX;\n\tkey[1] = SQLITE_SCHEMA_VERSION;\n\tkey[2] = CHUNK_PREFIX;\n\tkey[3] = fileTag;\n\tkey[4] = (chunkIndex >>> 24) & 0xff;\n\tkey[5] = (chunkIndex >>> 16) & 0xff;\n\tkey[6] = (chunkIndex >>> 8) & 0xff;\n\tkey[7] = chunkIndex & 0xff;\n\treturn key;\n}\n","import { createVersionedDataHandler } from \"vbare\";\nimport * as v1 from \"../../dist/schemas/file-meta/v1\";\n\nexport const CURRENT_VERSION = 1;\n\nexport const FILE_META_VERSIONED = createVersionedDataHandler<v1.FileMeta>({\n\tdeserializeVersion: (bytes, version) => {\n\t\tswitch (version) {\n\t\t\tcase 1:\n\t\t\t\treturn v1.decodeFileMeta(bytes);\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown version ${version}`);\n\t\t}\n\t},\n\tserializeVersion: (data, version) => {\n\t\tswitch (version) {\n\t\t\tcase 1:\n\t\t\t\treturn v1.encodeFileMeta(data as v1.FileMeta);\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown version ${version}`);\n\t\t}\n\t},\n\tdeserializeConverters: () => [],\n\tserializeConverters: () => [],\n});\n","// @generated - post-processed by compile-bare.ts\nimport * as bare from \"@rivetkit/bare-ts\"\n\nconst config = /* @__PURE__ */ bare.Config({})\n\nexport type u64 = bigint\n\nexport type FileMeta = {\n readonly size: u64,\n}\n\nexport function readFileMeta(bc: bare.ByteCursor): FileMeta {\n return {\n size: bare.readU64(bc),\n }\n}\n\nexport function writeFileMeta(bc: bare.ByteCursor, x: FileMeta): void {\n bare.writeU64(bc, x.size)\n}\n\nexport function encodeFileMeta(x: FileMeta): Uint8Array {\n const bc = new bare.ByteCursor(\n new Uint8Array(config.initialBufferLength),\n config\n )\n writeFileMeta(bc, x)\n return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset)\n}\n\nexport function decodeFileMeta(bytes: Uint8Array): FileMeta {\n const bc = new bare.ByteCursor(bytes, config)\n const result = readFileMeta(bc)\n if (bc.offset < bc.view.byteLength) {\n throw new bare.BareError(bc.offset, \"remaining bytes\")\n }\n return result\n}\n\n\nfunction assert(condition: boolean, message?: string): asserts condition {\n if (!condition) throw new Error(message ?? \"Assertion failed\")\n}\n"],"mappings":"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","names":["SQLITE_OPEN_CREATE","require","encodeFileMeta","decodeFileMeta","SQLITE_OPEN_CREATE"]}
|
|
1
|
+
{"version":3,"sources":["../../src/vfs.ts","../../src/kv.ts","../../src/generated/empty-db-page.ts","../../schemas/file-meta/versioned.ts","../schemas/file-meta/v1.ts","../../src/pool.ts"],"sourcesContent":["/**\n * SQLite raw database with KV storage backend\n *\n * This module provides a SQLite API that uses a KV-backed VFS\n * for storage. Each SqliteVfs instance is independent and can be\n * used concurrently with other instances.\n *\n * Keep this VFS on direct VFS.Base callbacks for minimal wrapper overhead.\n * Use @rivetkit/sqlite/src/FacadeVFS.js as the reference implementation for\n * callback ABI and pointer/data conversion behavior.\n * This implementation is optimized for single-writer semantics because each\n * actor owns one SQLite database.\n * SQLite invokes this VFS with byte-range file operations. This VFS maps those\n * ranges onto fixed-size KV chunks keyed by file tag and chunk index.\n * We intentionally rely on SQLite's pager cache for hot page reuse and do not\n * add a second cache in this VFS. This avoids duplicate cache invalidation\n * logic and keeps memory usage predictable for each actor.\n */\n\nimport * as VFS from \"@rivetkit/sqlite/src/VFS.js\";\nimport {\n\tFactory,\n\tSQLITE_OPEN_CREATE,\n\tSQLITE_OPEN_READWRITE,\n\tSQLITE_ROW,\n} from \"@rivetkit/sqlite\";\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport {\n\tCHUNK_SIZE,\n\tFILE_TAG_JOURNAL,\n\tFILE_TAG_MAIN,\n\tFILE_TAG_SHM,\n\tFILE_TAG_WAL,\n\tgetChunkKey,\n\tgetMetaKey,\n\ttype SqliteFileTag,\n} from \"./kv\";\nimport { EMPTY_DB_PAGE } from \"./generated/empty-db-page\";\nimport {\n\tFILE_META_VERSIONED,\n\tCURRENT_VERSION,\n} from \"../schemas/file-meta/versioned\";\nimport type { FileMeta } from \"../schemas/file-meta/mod\";\nimport type { KvVfsOptions } from \"./types\";\n\n/**\n * Common interface for database handles returned by ISqliteVfs.open().\n * Both the concrete Database class and the pool's TrackedDatabase wrapper\n * implement this, so consumers can use either interchangeably.\n */\nexport interface IDatabase {\n\texec(\n\t\tsql: string,\n\t\tcallback?: (row: unknown[], columns: string[]) => void,\n\t): Promise<void>;\n\trun(sql: string, params?: SqliteBindings): Promise<void>;\n\tquery(\n\t\tsql: string,\n\t\tparams?: SqliteBindings,\n\t): Promise<{ rows: unknown[][]; columns: string[] }>;\n\tclose(): Promise<void>;\n\treadonly fileName: string;\n}\n\n/**\n * Common interface for SQLite VFS backends. Both standalone SqliteVfs and\n * PooledSqliteHandle implement this so callers can use either interchangeably.\n */\nexport interface ISqliteVfs {\n\topen(fileName: string, options: KvVfsOptions): Promise<IDatabase>;\n\tdestroy(): Promise<void>;\n}\n\ntype SqliteEsmFactory = (config?: {\n\twasmBinary?: ArrayBuffer | Uint8Array;\n\tinstantiateWasm?: (\n\t\timports: WebAssembly.Imports,\n\t\treceiveInstance: (instance: WebAssembly.Instance) => void,\n\t) => WebAssembly.Exports;\n}) => Promise<unknown>;\ntype SQLite3Api = ReturnType<typeof Factory>;\ntype SqliteBindings = Parameters<SQLite3Api[\"bind_collection\"]>[1];\ntype SqliteVfsRegistration = Parameters<SQLite3Api[\"vfs_register\"]>[0];\n\ninterface SQLiteModule {\n\tUTF8ToString: (ptr: number) => string;\n\tHEAPU8: Uint8Array;\n}\n\nconst TEXT_ENCODER = new TextEncoder();\nconst TEXT_DECODER = new TextDecoder();\nconst SQLITE_MAX_PATHNAME_BYTES = 64;\n\n// Chunk keys encode the chunk index in 32 bits, so a file can span at most\n// 2^32 chunks. At 4 KiB/chunk this yields a hard limit of 16 TiB.\nconst UINT32_SIZE = 0x100000000;\nconst MAX_CHUNK_INDEX = 0xffffffff;\nconst MAX_FILE_SIZE_BYTES = (MAX_CHUNK_INDEX + 1) * CHUNK_SIZE;\nconst MAX_FILE_SIZE_HI32 = Math.floor(MAX_FILE_SIZE_BYTES / UINT32_SIZE);\nconst MAX_FILE_SIZE_LO32 = MAX_FILE_SIZE_BYTES % UINT32_SIZE;\n\n// Maximum number of keys the KV backend accepts in a single deleteBatch or putBatch call.\nconst KV_MAX_BATCH_KEYS = 128;\n\n// -- BATCH_ATOMIC and KV round trip documentation --\n//\n// KV round trips per actor database lifecycle:\n//\n// Open (new database):\n// 1 putBatch -- xOpen pre-writes EMPTY_DB_PAGE + metadata (2 keys)\n// PRAGMAs are in-memory, 0 KV ops\n//\n// Open (existing database / wake from sleep):\n// 1 get -- xOpen reads metadata to determine file size\n// PRAGMAs are in-memory, 0 KV ops\n//\n// First SQL operation (e.g., migration CREATE TABLE):\n// 1 getBatch -- pager reads page 1 (database header)\n// N getBatch -- pager reads additional pages as needed by the schema\n// 1 putBatch -- BATCH_ATOMIC commit (all dirty pages + metadata)\n//\n// Subsequent writes (warm pager cache):\n// 0 reads -- pages served from pager cache\n// 1 putBatch -- BATCH_ATOMIC commit\n//\n// Subsequent reads (warm pager cache):\n// 0 reads -- pages served from pager cache\n// 0 writes -- SELECT-only, no dirty pages\n//\n// Large writes (> 127 dirty pages):\n// BATCH_ATOMIC COMMIT returns SQLITE_IOERR, SQLite falls back to\n// journal mode with multiple putBatch calls (each <= 128 keys).\n//\n// BATCH_ATOMIC requires SQLite's pager to use an in-memory journal.\n// The pager only does this when dbSize > 0. For new databases, xOpen\n// pre-writes a valid empty page (EMPTY_DB_PAGE) so dbSize is 1 from\n// the start. Without this, the first transaction opens a real journal\n// file, and locking_mode=EXCLUSIVE prevents it from ever being closed,\n// permanently disabling BATCH_ATOMIC.\n//\n// See scripts/generate-empty-db-page.ts for how EMPTY_DB_PAGE is built.\n\n// BATCH_ATOMIC capability flag returned by xDeviceCharacteristics.\nconst SQLITE_IOCAP_BATCH_ATOMIC = 0x4000;\n\n// xFileControl opcodes for atomic write bracketing.\nconst SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31;\nconst SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32;\nconst SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33;\n\n// libvfs captures this async/sync mask at registration time. Any VFS callback\n// that returns a Promise must be listed here so SQLite uses async relays.\nconst SQLITE_ASYNC_METHODS = new Set([\n\t\"xOpen\",\n\t\"xClose\",\n\t\"xRead\",\n\t\"xWrite\",\n\t\"xTruncate\",\n\t\"xSync\",\n\t\"xFileSize\",\n\t\"xDelete\",\n\t\"xAccess\",\n\t\"xFileControl\",\n]);\n\ninterface LoadedSqliteRuntime {\n\tsqlite3: SQLite3Api;\n\tmodule: SQLiteModule;\n}\n\nfunction isSqliteEsmFactory(value: unknown): value is SqliteEsmFactory {\n\treturn typeof value === \"function\";\n}\n\nfunction isSQLiteModule(value: unknown): value is SQLiteModule {\n\tif (!value || typeof value !== \"object\") {\n\t\treturn false;\n\t}\n\tconst candidate = value as {\n\t\tUTF8ToString?: unknown;\n\t\tHEAPU8?: unknown;\n\t};\n\treturn (\n\t\ttypeof candidate.UTF8ToString === \"function\" &&\n\t\tcandidate.HEAPU8 instanceof Uint8Array\n\t);\n}\n\n/**\n * Lazily load and instantiate the async SQLite module for this VFS instance.\n * We do this on first open so actors that do not use SQLite do not pay module\n * parse and wasm initialization cost at startup, and we pass wasmBinary\n * explicitly so this works consistently in both ESM and CJS bundles.\n */\nasync function loadSqliteRuntime(\n\twasmModule?: WebAssembly.Module,\n): Promise<LoadedSqliteRuntime> {\n\t// Keep the module specifier assembled at runtime so TypeScript declaration\n\t// generation does not try to typecheck this deep dist import path.\n\t// Uses Array.join() instead of string concatenation to prevent esbuild/tsup\n\t// from constant-folding the expression at build time, which would allow\n\t// Turbopack to trace into the WASM package.\n\tconst specifier = [\"@rivetkit/sqlite\", \"dist\", \"wa-sqlite-async.mjs\"].join(\n\t\t\"/\",\n\t);\n\tconst sqliteModule = await import(specifier);\n\tif (!isSqliteEsmFactory(sqliteModule.default)) {\n\t\tthrow new Error(\"Invalid SQLite ESM factory export\");\n\t}\n\tconst sqliteEsmFactory = sqliteModule.default;\n\n\tlet module: unknown;\n\tif (wasmModule) {\n\t\t// Use the pre-compiled WebAssembly.Module directly, skipping\n\t\t// WebAssembly.compile. The Emscripten instantiateWasm callback lets us\n\t\t// provide a module that has already been compiled and cached by the pool.\n\t\tmodule = await sqliteEsmFactory({\n\t\t\tinstantiateWasm(\n\t\t\t\timports: WebAssembly.Imports,\n\t\t\t\treceiveInstance: (instance: WebAssembly.Instance) => void,\n\t\t\t) {\n\t\t\t\tWebAssembly.instantiate(wasmModule, imports).then((instance) => {\n\t\t\t\t\treceiveInstance(instance);\n\t\t\t\t});\n\t\t\t\treturn {} as WebAssembly.Exports;\n\t\t\t},\n\t\t});\n\t} else {\n\t\tconst require = createRequire(import.meta.url);\n\t\tconst sqliteDistPath = \"@rivetkit/sqlite/dist/\";\n\t\tconst wasmPath = require.resolve(\n\t\t\tsqliteDistPath + \"wa-sqlite-async.wasm\",\n\t\t);\n\t\tconst wasmBinary = readFileSync(wasmPath);\n\t\tmodule = await sqliteEsmFactory({ wasmBinary });\n\t}\n\n\tif (!isSQLiteModule(module)) {\n\t\tthrow new Error(\"Invalid SQLite runtime module\");\n\t}\n\treturn {\n\t\tsqlite3: Factory(module),\n\t\tmodule,\n\t};\n}\n\n/**\n * Represents an open file\n */\ninterface OpenFile {\n\t/** File path */\n\tpath: string;\n\t/** File kind tag used by compact key layout */\n\tfileTag: SqliteFileTag;\n\t/** Precomputed metadata key */\n\tmetaKey: Uint8Array;\n\t/** File size in bytes */\n\tsize: number;\n\t/** True when in-memory size has not been persisted yet */\n\tmetaDirty: boolean;\n\t/** Open flags */\n\tflags: number;\n\t/** KV options for this file */\n\toptions: KvVfsOptions;\n\t/** True while inside a BATCH_ATOMIC write bracket */\n\tbatchMode: boolean;\n\t/** Buffered dirty pages during batch mode. Key is the chunk index. */\n\tdirtyBuffer: Map<number, Uint8Array> | null;\n\t/** File size saved at BEGIN_ATOMIC_WRITE for rollback */\n\tsavedFileSize: number;\n}\n\ninterface ResolvedFile {\n\toptions: KvVfsOptions;\n\tfileTag: SqliteFileTag;\n}\n\n/**\n * Encodes file metadata to a Uint8Array using BARE schema\n */\nfunction encodeFileMeta(size: number): Uint8Array {\n\tconst meta: FileMeta = { size: BigInt(size) };\n\treturn FILE_META_VERSIONED.serializeWithEmbeddedVersion(\n\t\tmeta,\n\t\tCURRENT_VERSION,\n\t);\n}\n\n/**\n * Decodes file metadata from a Uint8Array using BARE schema\n */\nfunction decodeFileMeta(data: Uint8Array): number {\n\tconst meta = FILE_META_VERSIONED.deserializeWithEmbeddedVersion(data);\n\treturn Number(meta.size);\n}\n\nfunction isValidFileSize(size: number): boolean {\n\treturn (\n\t\tNumber.isSafeInteger(size) && size >= 0 && size <= MAX_FILE_SIZE_BYTES\n\t);\n}\n\n/**\n * Simple async mutex for serializing database operations\n * @rivetkit/sqlite calls are not safe to run concurrently on one module instance\n */\nclass AsyncMutex {\n\t#locked = false;\n\t#waiting: (() => void)[] = [];\n\n\tasync acquire(): Promise<void> {\n\t\twhile (this.#locked) {\n\t\t\tawait new Promise<void>((resolve) => this.#waiting.push(resolve));\n\t\t}\n\t\tthis.#locked = true;\n\t}\n\n\trelease(): void {\n\t\tthis.#locked = false;\n\t\tconst next = this.#waiting.shift();\n\t\tif (next) {\n\t\t\tnext();\n\t\t}\n\t}\n\n\tasync run<T>(fn: () => Promise<T>): Promise<T> {\n\t\tawait this.acquire();\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tthis.release();\n\t\t}\n\t}\n}\n\n/**\n * Database wrapper that provides a simplified SQLite API\n */\nexport class Database implements IDatabase {\n\treadonly #sqlite3: SQLite3Api;\n\treadonly #handle: number;\n\treadonly #fileName: string;\n\treadonly #onClose: () => Promise<void>;\n\treadonly #sqliteMutex: AsyncMutex;\n\t#closed = false;\n\n\tconstructor(\n\t\tsqlite3: SQLite3Api,\n\t\thandle: number,\n\t\tfileName: string,\n\t\tonClose: () => Promise<void>,\n\t\tsqliteMutex: AsyncMutex,\n\t) {\n\t\tthis.#sqlite3 = sqlite3;\n\t\tthis.#handle = handle;\n\t\tthis.#fileName = fileName;\n\t\tthis.#onClose = onClose;\n\t\tthis.#sqliteMutex = sqliteMutex;\n\t}\n\n\t/**\n\t * Execute SQL with optional row callback\n\t * @param sql - SQL statement to execute\n\t * @param callback - Called for each result row with (row, columns)\n\t */\n\tasync exec(\n\t\tsql: string,\n\t\tcallback?: (row: unknown[], columns: string[]) => void,\n\t): Promise<void> {\n\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\tawait this.#sqlite3.exec(this.#handle, sql, callback);\n\t\t});\n\t}\n\n\t/**\n\t * Execute a parameterized SQL statement (no result rows)\n\t * @param sql - SQL statement with ? placeholders\n\t * @param params - Parameter values to bind\n\t */\n\tasync run(sql: string, params?: SqliteBindings): Promise<void> {\n\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\tfor await (const stmt of this.#sqlite3.statements(\n\t\t\t\tthis.#handle,\n\t\t\t\tsql,\n\t\t\t)) {\n\t\t\t\tif (params) {\n\t\t\t\t\tthis.#sqlite3.bind_collection(stmt, params);\n\t\t\t\t}\n\t\t\t\twhile ((await this.#sqlite3.step(stmt)) === SQLITE_ROW) {\n\t\t\t\t\t// Consume rows for statements that return results.\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Execute a parameterized SQL query and return results\n\t * @param sql - SQL query with ? placeholders\n\t * @param params - Parameter values to bind\n\t * @returns Object with rows (array of arrays) and columns (column names)\n\t */\n\tasync query(\n\t\tsql: string,\n\t\tparams?: SqliteBindings,\n\t): Promise<{ rows: unknown[][]; columns: string[] }> {\n\t\treturn this.#sqliteMutex.run(async () => {\n\t\t\tconst rows: unknown[][] = [];\n\t\t\tlet columns: string[] = [];\n\t\t\tfor await (const stmt of this.#sqlite3.statements(\n\t\t\t\tthis.#handle,\n\t\t\t\tsql,\n\t\t\t)) {\n\t\t\t\tif (params) {\n\t\t\t\t\tthis.#sqlite3.bind_collection(stmt, params);\n\t\t\t\t}\n\n\t\t\t\twhile ((await this.#sqlite3.step(stmt)) === SQLITE_ROW) {\n\t\t\t\t\tif (columns.length === 0) {\n\t\t\t\t\t\tcolumns = this.#sqlite3.column_names(stmt);\n\t\t\t\t\t}\n\t\t\t\t\trows.push(this.#sqlite3.row(stmt));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { rows, columns };\n\t\t});\n\t}\n\n\t/**\n\t * Close the database\n\t */\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#closed = true;\n\n\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\tawait this.#sqlite3.close(this.#handle);\n\t\t});\n\t\tawait this.#onClose();\n\t}\n\n\t/**\n\t * Get the database file name\n\t */\n\tget fileName(): string {\n\t\treturn this.#fileName;\n\t}\n\n\t/**\n\t * Get the raw @rivetkit/sqlite API (for advanced usage)\n\t */\n\tget sqlite3(): SQLite3Api {\n\t\treturn this.#sqlite3;\n\t}\n\n\t/**\n\t * Get the raw database handle (for advanced usage)\n\t */\n\tget handle(): number {\n\t\treturn this.#handle;\n\t}\n}\n\n/**\n * SQLite VFS backed by KV storage.\n *\n * Each instance is independent and has its own @rivetkit/sqlite WASM module.\n * This allows multiple instances to operate concurrently without interference.\n */\nexport class SqliteVfs implements ISqliteVfs {\n\t#sqlite3: SQLite3Api | null = null;\n\t#sqliteSystem: SqliteSystem | null = null;\n\t#initPromise: Promise<void> | null = null;\n\t#openMutex = new AsyncMutex();\n\t#sqliteMutex = new AsyncMutex();\n\t#instanceId: string;\n\t#destroyed = false;\n\t#openDatabases: Set<Database> = new Set();\n\t#wasmModule?: WebAssembly.Module;\n\n\tconstructor(wasmModule?: WebAssembly.Module) {\n\t\t// Generate unique instance ID for VFS name\n\t\tthis.#instanceId = crypto.randomUUID().replace(/-/g, \"\").slice(0, 8);\n\t\tthis.#wasmModule = wasmModule;\n\t}\n\n\t/**\n\t * Initialize @rivetkit/sqlite and VFS (called once per instance)\n\t */\n\tasync #ensureInitialized(): Promise<void> {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new Error(\"SqliteVfs is closed\");\n\t\t}\n\n\t\t// Fast path: already initialized\n\t\tif (this.#sqlite3 && this.#sqliteSystem) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Synchronously create the promise if not started\n\t\tif (!this.#initPromise) {\n\t\t\tthis.#initPromise = (async () => {\n\t\t\t\tconst { sqlite3, module } = await loadSqliteRuntime(\n\t\t\t\t\tthis.#wasmModule,\n\t\t\t\t);\n\t\t\t\tif (this.#destroyed) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.#sqlite3 = sqlite3;\n\t\t\t\tthis.#sqliteSystem = new SqliteSystem(\n\t\t\t\t\tsqlite3,\n\t\t\t\t\tmodule,\n\t\t\t\t\t`kv-vfs-${this.#instanceId}`,\n\t\t\t\t);\n\t\t\t\tthis.#sqliteSystem.register();\n\t\t\t})();\n\t\t}\n\n\t\t// Wait for initialization\n\t\ttry {\n\t\t\tawait this.#initPromise;\n\t\t} catch (error) {\n\t\t\tthis.#initPromise = null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Open a SQLite database using KV storage backend\n\t *\n\t * @param fileName - The database file name (typically the actor ID)\n\t * @param options - KV storage operations for this database\n\t * @returns A Database instance\n\t */\n\tasync open(fileName: string, options: KvVfsOptions): Promise<IDatabase> {\n\t\tif (this.#destroyed) {\n\t\t\tthrow new Error(\"SqliteVfs is closed\");\n\t\t}\n\n\t\t// Serialize all open operations within this instance\n\t\tawait this.#openMutex.acquire();\n\t\ttry {\n\t\t\t// Reject double-open of the same fileName. Two handles to the same\n\t\t\t// file would have separate pager caches and no real locking\n\t\t\t// (xLock/xUnlock are no-ops), causing silent data corruption.\n\t\t\tfor (const db of this.#openDatabases) {\n\t\t\t\tif (db.fileName === fileName) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`SqliteVfs: fileName \"${fileName}\" is already open on this instance`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Initialize @rivetkit/sqlite and SqliteSystem on first call\n\t\t\tawait this.#ensureInitialized();\n\n\t\t\tif (!this.#sqlite3 || !this.#sqliteSystem) {\n\t\t\t\tthrow new Error(\"Failed to initialize SQLite\");\n\t\t\t}\n\t\t\tconst sqlite3 = this.#sqlite3;\n\t\t\tconst sqliteSystem = this.#sqliteSystem;\n\n\t\t\t// Register this filename with its KV options\n\t\t\tsqliteSystem.registerFile(fileName, options);\n\n\t\t\t// Open database\n\t\t\tconst db = await this.#sqliteMutex.run(async () =>\n\t\t\t\tsqlite3.open_v2(\n\t\t\t\t\tfileName,\n\t\t\t\t\tSQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,\n\t\t\t\t\tsqliteSystem.name,\n\t\t\t\t),\n\t\t\t);\n\t\t\t// Single-writer optimizations for KV-backed SQLite. Each actor owns\n\t\t\t// its database exclusively. BATCH_ATOMIC batches dirty pages into a\n\t\t\t// single putBatch call instead of 5-7 individual KV round trips per\n\t\t\t// write transaction.\n\t\t\t//\n\t\t\t// BATCH_ATOMIC requires an in-memory journal, which SQLite only uses\n\t\t\t// when dbSize > 0. The xOpen handler pre-writes a valid empty page 1\n\t\t\t// for new databases so this condition is satisfied from the start.\n\t\t\t// See xOpen and scripts/generate-empty-db-page.ts for details.\n\t\t\tawait this.#sqliteMutex.run(async () => {\n\t\t\t\tawait sqlite3.exec(db, \"PRAGMA page_size = 4096\");\n\t\t\t\tawait sqlite3.exec(db, \"PRAGMA journal_mode = DELETE\");\n\t\t\t\tawait sqlite3.exec(db, \"PRAGMA synchronous = NORMAL\");\n\t\t\t\tawait sqlite3.exec(db, \"PRAGMA temp_store = MEMORY\");\n\t\t\t\tawait sqlite3.exec(db, \"PRAGMA auto_vacuum = NONE\");\n\t\t\t\tawait sqlite3.exec(db, \"PRAGMA locking_mode = EXCLUSIVE\");\n\t\t\t});\n\n\t\t\t// Wrap unregistration under #openMutex so it serializes with\n\t\t\t// registerFile and prevents interleaving when short names recycle.\n\t\t\tconst onClose = async () => {\n\t\t\t\tthis.#openDatabases.delete(database);\n\t\t\t\tawait this.#openMutex.run(async () => {\n\t\t\t\t\tsqliteSystem.unregisterFile(fileName);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst database = new Database(\n\t\t\t\tsqlite3,\n\t\t\t\tdb,\n\t\t\t\tfileName,\n\t\t\t\tonClose,\n\t\t\t\tthis.#sqliteMutex,\n\t\t\t);\n\t\t\tthis.#openDatabases.add(database);\n\n\t\t\treturn database;\n\t\t} finally {\n\t\t\tthis.#openMutex.release();\n\t\t}\n\t}\n\n\t/**\n\t * Force-close all Database handles whose fileName exactly matches the\n\t * given name. Snapshots the set to an array before iterating to avoid\n\t * mutation during async iteration.\n\t *\n\t * Uses exact file name match because short names are numeric strings\n\t * ('0', '1', ..., '10', '11', ...) and a prefix match like\n\t * startsWith('1') would incorrectly match '10', '11', etc., causing\n\t * cross-actor corruption. Sidecar files (-journal, -wal, -shm) are not\n\t * tracked as separate Database handles, so prefix matching for sidecars\n\t * is not needed.\n\t */\n\tasync forceCloseByFileName(\n\t\tfileName: string,\n\t): Promise<{ allSucceeded: boolean }> {\n\t\tconst snapshot = [...this.#openDatabases];\n\t\tlet allSucceeded = true;\n\t\tfor (const db of snapshot) {\n\t\t\tif (db.fileName === fileName) {\n\t\t\t\ttry {\n\t\t\t\t\tawait db.close();\n\t\t\t\t} catch {\n\t\t\t\t\tallSucceeded = false;\n\t\t\t\t\t// When close fails, onClose never fires, leaving orphaned\n\t\t\t\t\t// entries in #openDatabases and #registeredFiles. Clean up\n\t\t\t\t\t// manually so stale registrations don't accumulate.\n\t\t\t\t\tthis.#openDatabases.delete(db);\n\t\t\t\t\tconst sqliteSystem = this.#sqliteSystem;\n\t\t\t\t\tif (sqliteSystem) {\n\t\t\t\t\t\tawait this.#openMutex.run(async () => {\n\t\t\t\t\t\t\tsqliteSystem.unregisterFile(db.fileName);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { allSucceeded };\n\t}\n\n\t/**\n\t * Force-close all open Database handles. Best-effort: errors are\n\t * swallowed so this is safe to call during instance teardown.\n\t */\n\tasync forceCloseAll(): Promise<void> {\n\t\tconst snapshot = [...this.#openDatabases];\n\t\tfor (const db of snapshot) {\n\t\t\ttry {\n\t\t\t\tawait db.close();\n\t\t\t} catch {\n\t\t\t\t// Best-effort teardown. Swallow errors.\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Tears down this VFS instance and releases internal references.\n\t */\n\tasync destroy(): Promise<void> {\n\t\tif (this.#destroyed) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#destroyed = true;\n\n\t\tconst initPromise = this.#initPromise;\n\t\tif (initPromise) {\n\t\t\ttry {\n\t\t\t\tawait initPromise;\n\t\t\t} catch {\n\t\t\t\t// Initialization failure already surfaced to caller.\n\t\t\t}\n\t\t}\n\n\t\tif (this.#sqliteSystem) {\n\t\t\tawait this.#sqliteSystem.close();\n\t\t}\n\n\t\tthis.#sqliteSystem = null;\n\t\tthis.#sqlite3 = null;\n\t\tthis.#initPromise = null;\n\t}\n\n\t/**\n\t * Alias for destroy to align with DB-style lifecycle naming.\n\t */\n\tasync close(): Promise<void> {\n\t\tawait this.destroy();\n\t}\n}\n\n/**\n * Internal VFS implementation\n */\nclass SqliteSystem implements SqliteVfsRegistration {\n\treadonly name: string;\n\treadonly mxPathName = SQLITE_MAX_PATHNAME_BYTES;\n\treadonly mxPathname = SQLITE_MAX_PATHNAME_BYTES;\n\treadonly #registeredFiles: Map<string, KvVfsOptions> = new Map();\n\treadonly #openFiles: Map<number, OpenFile> = new Map();\n\treadonly #sqlite3: SQLite3Api;\n\treadonly #module: SQLiteModule;\n\t#heapDataView: DataView;\n\t#heapDataViewBuffer: ArrayBufferLike;\n\n\tconstructor(sqlite3: SQLite3Api, module: SQLiteModule, name: string) {\n\t\tthis.name = name;\n\t\tthis.#sqlite3 = sqlite3;\n\t\tthis.#module = module;\n\t\tthis.#heapDataViewBuffer = module.HEAPU8.buffer;\n\t\tthis.#heapDataView = new DataView(this.#heapDataViewBuffer);\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.#openFiles.clear();\n\t\tthis.#registeredFiles.clear();\n\t}\n\n\tisReady(): boolean {\n\t\treturn true;\n\t}\n\n\thasAsyncMethod(methodName: string): boolean {\n\t\treturn SQLITE_ASYNC_METHODS.has(methodName);\n\t}\n\n\t/**\n\t * Registers the VFS with SQLite\n\t */\n\tregister(): void {\n\t\tthis.#sqlite3.vfs_register(this, false);\n\t}\n\n\t/**\n\t * Registers a file with its KV options (before opening).\n\t */\n\tregisterFile(fileName: string, options: KvVfsOptions): void {\n\t\tthis.#registeredFiles.set(fileName, options);\n\t}\n\n\t/**\n\t * Unregisters a file's KV options (after closing).\n\t */\n\tunregisterFile(fileName: string): void {\n\t\tthis.#registeredFiles.delete(fileName);\n\t}\n\n\t/**\n\t * Resolve file path to a registered database file or one of its SQLite\n\t * sidecars (-journal, -wal, -shm). File tags are reused across files\n\t * because each file's KvVfsOptions routes to a separate KV namespace.\n\t */\n\t#resolveFile(path: string): ResolvedFile | null {\n\t\t// Direct match: O(1) lookup for main database file.\n\t\tconst directOptions = this.#registeredFiles.get(path);\n\t\tif (directOptions) {\n\t\t\treturn { options: directOptions, fileTag: FILE_TAG_MAIN };\n\t\t}\n\n\t\t// Sidecar match: strip each known suffix and check the base name.\n\t\tif (path.endsWith(\"-journal\")) {\n\t\t\tconst baseName = path.slice(0, -8);\n\t\t\tconst options = this.#registeredFiles.get(baseName);\n\t\t\tif (options) {\n\t\t\t\treturn { options, fileTag: FILE_TAG_JOURNAL };\n\t\t\t}\n\t\t} else if (path.endsWith(\"-wal\")) {\n\t\t\tconst baseName = path.slice(0, -4);\n\t\t\tconst options = this.#registeredFiles.get(baseName);\n\t\t\tif (options) {\n\t\t\t\treturn { options, fileTag: FILE_TAG_WAL };\n\t\t\t}\n\t\t} else if (path.endsWith(\"-shm\")) {\n\t\t\tconst baseName = path.slice(0, -4);\n\t\t\tconst options = this.#registeredFiles.get(baseName);\n\t\t\tif (options) {\n\t\t\t\treturn { options, fileTag: FILE_TAG_SHM };\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t#resolveFileOrThrow(path: string): ResolvedFile {\n\t\tconst resolved = this.#resolveFile(path);\n\t\tif (resolved) {\n\t\t\treturn resolved;\n\t\t}\n\n\t\tif (this.#registeredFiles.size === 0) {\n\t\t\tthrow new Error(`No KV options registered for file: ${path}`);\n\t\t}\n\n\t\tconst registered = Array.from(this.#registeredFiles.keys()).join(\", \");\n\t\tthrow new Error(\n\t\t\t`Unsupported SQLite file path ${path}. Registered base names: ${registered}.`,\n\t\t);\n\t}\n\n\t#chunkKey(file: OpenFile, chunkIndex: number): Uint8Array {\n\t\treturn getChunkKey(file.fileTag, chunkIndex);\n\t}\n\n\tasync xOpen(\n\t\t_pVfs: number,\n\t\tzName: number,\n\t\tfileId: number,\n\t\tflags: number,\n\t\tpOutFlags: number,\n\t): Promise<number> {\n\t\tconst path = this.#decodeFilename(zName, flags);\n\t\tif (!path) {\n\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t}\n\n\t\t// Get the registered KV options for this file\n\t\t// For journal/wal files, use the main database's options\n\t\tconst { options, fileTag } = this.#resolveFileOrThrow(path);\n\t\tconst metaKey = getMetaKey(fileTag);\n\n\t\t// Get existing file size if the file exists\n\t\tlet sizeData: Uint8Array | null;\n\t\ttry {\n\t\t\tsizeData = await options.get(metaKey);\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t}\n\n\t\tlet size: number;\n\n\t\tif (sizeData) {\n\t\t\t// File exists, use existing size\n\t\t\tsize = decodeFileMeta(sizeData);\n\t\t\tif (!isValidFileSize(size)) {\n\t\t\t\treturn VFS.SQLITE_IOERR;\n\t\t\t}\n\t\t} else if (flags & VFS.SQLITE_OPEN_CREATE) {\n\t\t\tif (fileTag === FILE_TAG_MAIN) {\n\t\t\t\t// Pre-write a valid empty database page so SQLite sees\n\t\t\t\t// dbSize > 0 on first read. This enables BATCH_ATOMIC\n\t\t\t\t// from the very first write transaction. Without this,\n\t\t\t\t// SQLite's pager opens a real journal file for the first\n\t\t\t\t// write (because jrnlBufferSize returns a positive value\n\t\t\t\t// when dbSize == 0), and with locking_mode=EXCLUSIVE that\n\t\t\t\t// real journal is never closed, permanently disabling\n\t\t\t\t// batch atomic writes.\n\t\t\t\t//\n\t\t\t\t// The page is generated by scripts/generate-empty-header.ts\n\t\t\t\t// using the same wa-sqlite WASM binary we ship.\n\t\t\t\tconst chunkKey = getChunkKey(fileTag, 0);\n\t\t\t\tsize = EMPTY_DB_PAGE.length;\n\t\t\t\ttry {\n\t\t\t\t\tawait options.putBatch([\n\t\t\t\t\t\t[chunkKey, EMPTY_DB_PAGE],\n\t\t\t\t\t\t[metaKey, encodeFileMeta(size)],\n\t\t\t\t\t]);\n\t\t\t\t} catch {\n\t\t\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Sidecar files (journal, WAL, SHM) start empty.\n\t\t\t\tsize = 0;\n\t\t\t\ttry {\n\t\t\t\t\tawait options.put(metaKey, encodeFileMeta(size));\n\t\t\t\t} catch {\n\t\t\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// File doesn't exist and we're not creating it\n\t\t\treturn VFS.SQLITE_CANTOPEN;\n\t\t}\n\n\t\t// Store open file info with options\n\t\tthis.#openFiles.set(fileId, {\n\t\t\tpath,\n\t\t\tfileTag,\n\t\t\tmetaKey,\n\t\t\tsize,\n\t\t\tmetaDirty: false,\n\t\t\tflags,\n\t\t\toptions,\n\t\t\tbatchMode: false,\n\t\t\tdirtyBuffer: null,\n\t\t\tsavedFileSize: 0,\n\t\t});\n\n\t\t// Set output flags to the actual flags used.\n\t\tthis.#writeInt32(pOutFlags, flags);\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xClose(fileId: number): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\ttry {\n\t\t\t// Delete-on-close files should skip metadata flush because the file\n\t\t\t// will be removed immediately.\n\t\t\tif (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {\n\t\t\t\tawait this.#delete(file.path);\n\t\t\t} else if (file.metaDirty) {\n\t\t\t\tawait file.options.put(\n\t\t\t\t\tfile.metaKey,\n\t\t\t\t\tencodeFileMeta(file.size),\n\t\t\t\t);\n\t\t\t\tfile.metaDirty = false;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Always clean up the file handle even if the KV operation fails.\n\t\t\tthis.#openFiles.delete(fileId);\n\t\t\treturn VFS.SQLITE_IOERR;\n\t\t}\n\n\t\tthis.#openFiles.delete(fileId);\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xRead(\n\t\tfileId: number,\n\t\tpData: number,\n\t\tiAmt: number,\n\t\tiOffsetLo: number,\n\t\tiOffsetHi: number,\n\t): Promise<number> {\n\t\tif (iAmt === 0) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_READ;\n\t\t}\n\n\t\tlet data = this.#module.HEAPU8.subarray(pData, pData + iAmt);\n\t\tconst options = file.options;\n\t\tconst requestedLength = iAmt;\n\t\tconst iOffset = delegalize(iOffsetLo, iOffsetHi);\n\t\tif (iOffset < 0) {\n\t\t\treturn VFS.SQLITE_IOERR_READ;\n\t\t}\n\t\tconst fileSize = file.size;\n\n\t\t// If offset is beyond file size, return short read with zeroed buffer\n\t\tif (iOffset >= fileSize) {\n\t\t\tdata.fill(0);\n\t\t\treturn VFS.SQLITE_IOERR_SHORT_READ;\n\t\t}\n\n\t\t// Calculate which chunks we need to read\n\t\tconst startChunk = Math.floor(iOffset / CHUNK_SIZE);\n\t\tconst endChunk = Math.floor(\n\t\t\t(iOffset + requestedLength - 1) / CHUNK_SIZE,\n\t\t);\n\n\t\t// Fetch needed chunks, checking dirty buffer first in batch mode.\n\t\tconst chunkKeys: Uint8Array[] = [];\n\t\tconst chunkIndexToBuffered: Map<number, Uint8Array> = new Map();\n\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\t// In batch mode, serve from dirty buffer if available.\n\t\t\tif (file.batchMode && file.dirtyBuffer) {\n\t\t\t\tconst buffered = file.dirtyBuffer.get(i);\n\t\t\t\tif (buffered) {\n\t\t\t\t\tchunkIndexToBuffered.set(i, buffered);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tchunkKeys.push(this.#chunkKey(file, i));\n\t\t}\n\n\t\tlet kvChunks: (Uint8Array | null)[];\n\t\ttry {\n\t\t\tkvChunks =\n\t\t\t\tchunkKeys.length > 0\n\t\t\t\t\t? await options.getBatch(chunkKeys)\n\t\t\t\t\t: [];\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_IOERR_READ;\n\t\t}\n\n\t\t// Re-read HEAPU8 after await to defend against buffer detachment\n\t\t// from memory.grow() that may have occurred during getBatch.\n\t\tdata = this.#module.HEAPU8.subarray(pData, pData + iAmt);\n\n\t\t// Copy data from chunks to output buffer\n\t\tlet kvIdx = 0;\n\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\tconst chunkData =\n\t\t\t\tchunkIndexToBuffered.get(i) ?? kvChunks[kvIdx++];\n\t\t\tconst chunkOffset = i * CHUNK_SIZE;\n\n\t\t\t// Calculate the range within this chunk\n\t\t\tconst readStart = Math.max(0, iOffset - chunkOffset);\n\t\t\tconst readEnd = Math.min(\n\t\t\t\tCHUNK_SIZE,\n\t\t\t\tiOffset + requestedLength - chunkOffset,\n\t\t\t);\n\n\t\t\tif (chunkData) {\n\t\t\t\t// Copy available data\n\t\t\t\tconst sourceStart = readStart;\n\t\t\t\tconst sourceEnd = Math.min(readEnd, chunkData.length);\n\t\t\t\tconst destStart = chunkOffset + readStart - iOffset;\n\n\t\t\t\tif (sourceEnd > sourceStart) {\n\t\t\t\t\tdata.set(\n\t\t\t\t\t\tchunkData.subarray(sourceStart, sourceEnd),\n\t\t\t\t\t\tdestStart,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Zero-fill if chunk is smaller than expected\n\t\t\t\tif (sourceEnd < readEnd) {\n\t\t\t\t\tconst zeroStart = destStart + (sourceEnd - sourceStart);\n\t\t\t\t\tconst zeroEnd = destStart + (readEnd - readStart);\n\t\t\t\t\tdata.fill(0, zeroStart, zeroEnd);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Chunk doesn't exist, zero-fill\n\t\t\t\tconst destStart = chunkOffset + readStart - iOffset;\n\t\t\t\tconst destEnd = destStart + (readEnd - readStart);\n\t\t\t\tdata.fill(0, destStart, destEnd);\n\t\t\t}\n\t\t}\n\n\t\t// If we read less than requested (past EOF), return short read\n\t\tconst actualBytes = Math.min(requestedLength, fileSize - iOffset);\n\t\tif (actualBytes < requestedLength) {\n\t\t\tdata.fill(0, actualBytes);\n\t\t\treturn VFS.SQLITE_IOERR_SHORT_READ;\n\t\t}\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xWrite(\n\t\tfileId: number,\n\t\tpData: number,\n\t\tiAmt: number,\n\t\tiOffsetLo: number,\n\t\tiOffsetHi: number,\n\t): Promise<number> {\n\t\tif (iAmt === 0) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\n\t\tlet data = this.#module.HEAPU8.subarray(pData, pData + iAmt);\n\t\tconst iOffset = delegalize(iOffsetLo, iOffsetHi);\n\t\tif (iOffset < 0) {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\t\tconst options = file.options;\n\t\tconst writeLength = iAmt;\n\t\tconst writeEndOffset = iOffset + writeLength;\n\t\tif (writeEndOffset > MAX_FILE_SIZE_BYTES) {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\n\t\t// Calculate which chunks we need to modify\n\t\tconst startChunk = Math.floor(iOffset / CHUNK_SIZE);\n\t\tconst endChunk = Math.floor((iOffset + writeLength - 1) / CHUNK_SIZE);\n\n\t\t// Batch mode: buffer pages in dirtyBuffer instead of writing to KV.\n\t\t// COMMIT_ATOMIC_WRITE flushes the buffer in a single putBatch.\n\t\tif (file.batchMode && file.dirtyBuffer) {\n\t\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\t\tconst chunkOffset = i * CHUNK_SIZE;\n\t\t\t\tconst sourceStart = Math.max(0, chunkOffset - iOffset);\n\t\t\t\tconst sourceEnd = Math.min(\n\t\t\t\t\twriteLength,\n\t\t\t\t\tchunkOffset + CHUNK_SIZE - iOffset,\n\t\t\t\t);\n\t\t\t\t// .slice() creates an independent copy that won't be\n\t\t\t\t// invalidated by memory.grow() after an await.\n\t\t\t\tfile.dirtyBuffer.set(\n\t\t\t\t\ti,\n\t\t\t\t\tdata.subarray(sourceStart, sourceEnd).slice(),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Update file size if write extends the file\n\t\t\tconst newSize = Math.max(file.size, writeEndOffset);\n\t\t\tif (newSize !== file.size) {\n\t\t\t\tfile.size = newSize;\n\t\t\t\tfile.metaDirty = true;\n\t\t\t}\n\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tinterface WritePlan {\n\t\t\tchunkKey: Uint8Array;\n\t\t\tchunkOffset: number;\n\t\t\twriteStart: number;\n\t\t\twriteEnd: number;\n\t\t\texistingChunkIndex: number;\n\t\t}\n\n\t\t// Only fetch chunks where we must preserve existing prefix/suffix bytes.\n\t\tconst plans: WritePlan[] = [];\n\t\tconst chunkKeysToFetch: Uint8Array[] = [];\n\t\tfor (let i = startChunk; i <= endChunk; i++) {\n\t\t\tconst chunkOffset = i * CHUNK_SIZE;\n\t\t\tconst writeStart = Math.max(0, iOffset - chunkOffset);\n\t\t\tconst writeEnd = Math.min(\n\t\t\t\tCHUNK_SIZE,\n\t\t\t\tiOffset + writeLength - chunkOffset,\n\t\t\t);\n\t\t\tconst existingBytesInChunk = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(CHUNK_SIZE, file.size - chunkOffset),\n\t\t\t);\n\t\t\tconst needsExisting =\n\t\t\t\twriteStart > 0 || existingBytesInChunk > writeEnd;\n\t\t\tconst chunkKey = this.#chunkKey(file, i);\n\t\t\tlet existingChunkIndex = -1;\n\t\t\tif (needsExisting) {\n\t\t\t\texistingChunkIndex = chunkKeysToFetch.length;\n\t\t\t\tchunkKeysToFetch.push(chunkKey);\n\t\t\t}\n\t\t\tplans.push({\n\t\t\t\tchunkKey,\n\t\t\t\tchunkOffset,\n\t\t\t\twriteStart,\n\t\t\t\twriteEnd,\n\t\t\t\texistingChunkIndex,\n\t\t\t});\n\t\t}\n\n\t\tlet existingChunks: (Uint8Array | null)[];\n\t\ttry {\n\t\t\texistingChunks =\n\t\t\t\tchunkKeysToFetch.length > 0\n\t\t\t\t\t? await options.getBatch(chunkKeysToFetch)\n\t\t\t\t\t: [];\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\n\t\t// Re-read HEAPU8 after await to defend against buffer detachment\n\t\t// from memory.grow() that may have occurred during getBatch.\n\t\tdata = this.#module.HEAPU8.subarray(pData, pData + iAmt);\n\n\t\t// Prepare new chunk data\n\t\tconst entriesToWrite: [Uint8Array, Uint8Array][] = [];\n\n\t\tfor (const plan of plans) {\n\t\t\tconst existingChunk =\n\t\t\t\tplan.existingChunkIndex >= 0\n\t\t\t\t\t? existingChunks[plan.existingChunkIndex]\n\t\t\t\t\t: null;\n\t\t\t// Create new chunk data\n\t\t\tlet newChunk: Uint8Array;\n\t\t\tif (existingChunk) {\n\t\t\t\tnewChunk = new Uint8Array(\n\t\t\t\t\tMath.max(existingChunk.length, plan.writeEnd),\n\t\t\t\t);\n\t\t\t\tnewChunk.set(existingChunk);\n\t\t\t} else {\n\t\t\t\tnewChunk = new Uint8Array(plan.writeEnd);\n\t\t\t}\n\n\t\t\t// Copy data from input buffer to chunk\n\t\t\tconst sourceStart = plan.chunkOffset + plan.writeStart - iOffset;\n\t\t\tconst sourceEnd = sourceStart + (plan.writeEnd - plan.writeStart);\n\t\t\tnewChunk.set(\n\t\t\t\tdata.subarray(sourceStart, sourceEnd),\n\t\t\t\tplan.writeStart,\n\t\t\t);\n\n\t\t\tentriesToWrite.push([plan.chunkKey, newChunk]);\n\t\t}\n\n\t\t// Update file size if we wrote past the end\n\t\tconst previousSize = file.size;\n\t\tconst previousMetaDirty = file.metaDirty;\n\t\tconst newSize = Math.max(file.size, writeEndOffset);\n\t\tif (newSize !== previousSize) {\n\t\t\tfile.size = newSize;\n\t\t\tfile.metaDirty = true;\n\t\t}\n\t\tif (file.metaDirty) {\n\t\t\tentriesToWrite.push([file.metaKey, encodeFileMeta(file.size)]);\n\t\t}\n\n\t\t// Write all chunks and metadata\n\t\ttry {\n\t\t\tawait options.putBatch(entriesToWrite);\n\t\t} catch {\n\t\t\tfile.size = previousSize;\n\t\t\tfile.metaDirty = previousMetaDirty;\n\t\t\treturn VFS.SQLITE_IOERR_WRITE;\n\t\t}\n\t\tif (file.metaDirty) {\n\t\t\tfile.metaDirty = false;\n\t\t}\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xTruncate(\n\t\tfileId: number,\n\t\tsizeLo: number,\n\t\tsizeHi: number,\n\t): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t}\n\n\t\tconst size = delegalize(sizeLo, sizeHi);\n\t\tif (size < 0 || size > MAX_FILE_SIZE_BYTES) {\n\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t}\n\t\tconst options = file.options;\n\n\t\t// If truncating to larger size, just update metadata\n\t\tif (size >= file.size) {\n\t\t\tif (size > file.size) {\n\t\t\t\tconst previousSize = file.size;\n\t\t\t\tconst previousMetaDirty = file.metaDirty;\n\t\t\t\tfile.size = size;\n\t\t\t\tfile.metaDirty = true;\n\t\t\t\ttry {\n\t\t\t\t\tawait options.put(file.metaKey, encodeFileMeta(file.size));\n\t\t\t\t} catch {\n\t\t\t\t\tfile.size = previousSize;\n\t\t\t\t\tfile.metaDirty = previousMetaDirty;\n\t\t\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t\t\t}\n\t\t\t\tfile.metaDirty = false;\n\t\t\t}\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\t// Calculate which chunks to delete\n\t\t// Note: When size=0, lastChunkToKeep = floor(-1/4096) = -1, which means\n\t\t// all chunks (starting from index 0) will be deleted in the loop below.\n\t\tconst lastChunkToKeep = Math.floor((size - 1) / CHUNK_SIZE);\n\t\tconst lastExistingChunk = Math.floor((file.size - 1) / CHUNK_SIZE);\n\n\t\t// Update metadata first so a crash leaves orphaned chunks (wasted\n\t\t// space) rather than metadata pointing at missing chunks (corruption).\n\t\tconst previousSize = file.size;\n\t\tconst previousMetaDirty = file.metaDirty;\n\t\tfile.size = size;\n\t\tfile.metaDirty = true;\n\t\ttry {\n\t\t\tawait options.put(file.metaKey, encodeFileMeta(file.size));\n\t\t} catch {\n\t\t\tfile.size = previousSize;\n\t\t\tfile.metaDirty = previousMetaDirty;\n\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t}\n\t\tfile.metaDirty = false;\n\n\t\t// Remaining operations clean up old chunk data. Metadata already\n\t\t// reflects the new size, so failures here leave orphaned/oversized\n\t\t// chunks that are invisible to SQLite (xRead clips to file.size).\n\t\ttry {\n\t\t\t// Truncate the last kept chunk if needed\n\t\t\tif (size > 0 && size % CHUNK_SIZE !== 0) {\n\t\t\t\tconst lastChunkKey = this.#chunkKey(file, lastChunkToKeep);\n\t\t\t\tconst lastChunkData = await options.get(lastChunkKey);\n\n\t\t\t\tif (lastChunkData && lastChunkData.length > size % CHUNK_SIZE) {\n\t\t\t\t\tconst truncatedChunk = lastChunkData.subarray(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tsize % CHUNK_SIZE,\n\t\t\t\t\t);\n\t\t\t\t\tawait options.put(lastChunkKey, truncatedChunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Delete chunks beyond the new size\n\t\t\tconst keysToDelete: Uint8Array[] = [];\n\t\t\tfor (let i = lastChunkToKeep + 1; i <= lastExistingChunk; i++) {\n\t\t\t\tkeysToDelete.push(this.#chunkKey(file, i));\n\t\t\t}\n\n\t\t\tfor (let b = 0; b < keysToDelete.length; b += KV_MAX_BATCH_KEYS) {\n\t\t\t\tawait options.deleteBatch(keysToDelete.slice(b, b + KV_MAX_BATCH_KEYS));\n\t\t\t}\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_IOERR_TRUNCATE;\n\t\t}\n\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xSync(fileId: number, _flags: number): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file || !file.metaDirty) {\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\ttry {\n\t\t\tawait file.options.put(file.metaKey, encodeFileMeta(file.size));\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_IOERR_FSYNC;\n\t\t}\n\t\tfile.metaDirty = false;\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xFileSize(fileId: number, pSize: number): Promise<number> {\n\t\tconst file = this.#openFiles.get(fileId);\n\t\tif (!file) {\n\t\t\treturn VFS.SQLITE_IOERR_FSTAT;\n\t\t}\n\n\t\t// Set size as 64-bit integer.\n\t\tthis.#writeBigInt64(pSize, BigInt(file.size));\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xDelete(\n\t\t_pVfs: number,\n\t\tzName: number,\n\t\t_syncDir: number,\n\t): Promise<number> {\n\t\ttry {\n\t\t\tawait this.#delete(this.#module.UTF8ToString(zName));\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_IOERR_DELETE;\n\t\t}\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\t/**\n\t * Internal delete implementation\n\t */\n\tasync #delete(path: string): Promise<void> {\n\t\tconst { options, fileTag } = this.#resolveFileOrThrow(path);\n\t\tconst metaKey = getMetaKey(fileTag);\n\n\t\t// Get file size to find out how many chunks to delete\n\t\tconst sizeData = await options.get(metaKey);\n\n\t\tif (!sizeData) {\n\t\t\t// File doesn't exist, that's OK\n\t\t\treturn;\n\t\t}\n\n\t\tconst size = decodeFileMeta(sizeData);\n\n\t\t// Delete all chunks\n\t\tconst keysToDelete: Uint8Array[] = [metaKey];\n\t\tconst numChunks = Math.ceil(size / CHUNK_SIZE);\n\t\tfor (let i = 0; i < numChunks; i++) {\n\t\t\tkeysToDelete.push(getChunkKey(fileTag, i));\n\t\t}\n\n\t\tfor (let b = 0; b < keysToDelete.length; b += KV_MAX_BATCH_KEYS) {\n\t\t\tawait options.deleteBatch(keysToDelete.slice(b, b + KV_MAX_BATCH_KEYS));\n\t\t}\n\t}\n\n\tasync xAccess(\n\t\t_pVfs: number,\n\t\tzName: number,\n\t\t_flags: number,\n\t\tpResOut: number,\n\t): Promise<number> {\n\t\t// TODO: Measure how often xAccess runs during open and whether these\n\t\t// existence checks add meaningful KV round-trip overhead. If they do,\n\t\t// consider serving file existence from in-memory state.\n\t\tconst path = this.#module.UTF8ToString(zName);\n\t\tconst resolved = this.#resolveFile(path);\n\t\tif (!resolved) {\n\t\t\t// File not registered, doesn't exist\n\t\t\tthis.#writeInt32(pResOut, 0);\n\t\t\treturn VFS.SQLITE_OK;\n\t\t}\n\n\t\tconst compactMetaKey = getMetaKey(resolved.fileTag);\n\t\tlet metaData: Uint8Array | null;\n\t\ttry {\n\t\t\tmetaData = await resolved.options.get(compactMetaKey);\n\t\t} catch {\n\t\t\treturn VFS.SQLITE_IOERR_ACCESS;\n\t\t}\n\n\t\t// Set result: 1 if file exists, 0 otherwise\n\t\tthis.#writeInt32(pResOut, metaData ? 1 : 0);\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txCheckReservedLock(_fileId: number, pResOut: number): number {\n\t\t// This VFS is actor-scoped with one writer, so there is no external\n\t\t// reserved lock state to report.\n\t\tthis.#writeInt32(pResOut, 0);\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txLock(_fileId: number, _flags: number): number {\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\txUnlock(_fileId: number, _flags: number): number {\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\tasync xFileControl(\n\t\tfileId: number,\n\t\tflags: number,\n\t\t_pArg: number,\n\t): Promise<number> {\n\t\tswitch (flags) {\n\t\t\tcase SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: {\n\t\t\t\tconst file = this.#openFiles.get(fileId);\n\t\t\t\tif (!file) return VFS.SQLITE_NOTFOUND;\n\t\t\t\tfile.savedFileSize = file.size;\n\t\t\t\tfile.batchMode = true;\n\t\t\t\tfile.metaDirty = false;\n\t\t\t\tfile.dirtyBuffer = new Map();\n\t\t\t\treturn VFS.SQLITE_OK;\n\t\t\t}\n\n\t\t\tcase SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: {\n\t\t\t\tconst file = this.#openFiles.get(fileId);\n\t\t\t\tif (!file) return VFS.SQLITE_NOTFOUND;\n\t\t\t\tconst { dirtyBuffer, options } = file;\n\n\t\t\t\t// Dynamic limit: if metadata is dirty, we need one slot for it.\n\t\t\t\t// If metadata is not dirty (file.size unchanged), all slots are available for pages.\n\t\t\t\tconst maxDirtyPages = file.metaDirty ? KV_MAX_BATCH_KEYS - 1 : KV_MAX_BATCH_KEYS;\n\t\t\t\tif (dirtyBuffer && dirtyBuffer.size > maxDirtyPages) {\n\t\t\t\t\tdirtyBuffer.clear();\n\t\t\t\t\tfile.dirtyBuffer = null;\n\t\t\t\t\tfile.size = file.savedFileSize;\n\t\t\t\t\tfile.metaDirty = false;\n\t\t\t\t\tfile.batchMode = false;\n\t\t\t\t\treturn VFS.SQLITE_IOERR;\n\t\t\t\t}\n\n\t\t\t\t// Build entries array from dirty buffer + metadata.\n\t\t\t\tconst entries: [Uint8Array, Uint8Array][] = [];\n\t\t\t\tif (dirtyBuffer) {\n\t\t\t\t\tfor (const [chunkIndex, data] of dirtyBuffer) {\n\t\t\t\t\t\tentries.push([this.#chunkKey(file, chunkIndex), data]);\n\t\t\t\t\t}\n\t\t\t\t\tdirtyBuffer.clear();\n\t\t\t\t}\n\t\t\t\tif (file.metaDirty) {\n\t\t\t\t\tentries.push([file.metaKey, encodeFileMeta(file.size)]);\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tawait options.putBatch(entries);\n\t\t\t\t} catch {\n\t\t\t\t\tfile.dirtyBuffer = null;\n\t\t\t\t\tfile.size = file.savedFileSize;\n\t\t\t\t\tfile.metaDirty = false;\n\t\t\t\t\tfile.batchMode = false;\n\t\t\t\t\treturn VFS.SQLITE_IOERR;\n\t\t\t\t}\n\n\t\t\t\tfile.dirtyBuffer = null;\n\t\t\t\tfile.metaDirty = false;\n\t\t\t\tfile.batchMode = false;\n\t\t\t\treturn VFS.SQLITE_OK;\n\t\t\t}\n\n\t\t\tcase SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: {\n\t\t\t\tconst file = this.#openFiles.get(fileId);\n\t\t\t\tif (!file || !file.batchMode) return VFS.SQLITE_OK;\n\t\t\t\tif (file.dirtyBuffer) {\n\t\t\t\t\tfile.dirtyBuffer.clear();\n\t\t\t\t\tfile.dirtyBuffer = null;\n\t\t\t\t}\n\t\t\t\tfile.size = file.savedFileSize;\n\t\t\t\tfile.metaDirty = false;\n\t\t\t\tfile.batchMode = false;\n\t\t\t\treturn VFS.SQLITE_OK;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t\treturn VFS.SQLITE_NOTFOUND;\n\t\t}\n\t}\n\n\txDeviceCharacteristics(_fileId: number): number {\n\t\treturn SQLITE_IOCAP_BATCH_ATOMIC;\n\t}\n\n\txFullPathname(\n\t\t_pVfs: number,\n\t\tzName: number,\n\t\tnOut: number,\n\t\tzOut: number,\n\t): number {\n\t\tconst path = this.#module.UTF8ToString(zName);\n\t\tconst bytes = TEXT_ENCODER.encode(path);\n\t\tconst out = this.#module.HEAPU8.subarray(zOut, zOut + nOut);\n\t\tif (bytes.length >= out.length) {\n\t\t\treturn VFS.SQLITE_IOERR;\n\t\t}\n\t\tout.set(bytes, 0);\n\t\tout[bytes.length] = 0;\n\t\treturn VFS.SQLITE_OK;\n\t}\n\n\t#decodeFilename(zName: number, flags: number): string | null {\n\t\tif (!zName) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (flags & VFS.SQLITE_OPEN_URI) {\n\t\t\t// Decode SQLite URI filename layout: path\\0key\\0value\\0...\\0\n\t\t\tlet pName = zName;\n\t\t\tlet state: 1 | 2 | 3 | null = 1;\n\t\t\tconst charCodes: number[] = [];\n\t\t\twhile (state) {\n\t\t\t\tconst charCode = this.#module.HEAPU8[pName++];\n\t\t\t\tif (charCode) {\n\t\t\t\t\tcharCodes.push(charCode);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (!this.#module.HEAPU8[pName]) {\n\t\t\t\t\tstate = null;\n\t\t\t\t}\n\t\t\t\tswitch (state) {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tcharCodes.push(\"?\".charCodeAt(0));\n\t\t\t\t\t\tstate = 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tcharCodes.push(\"=\".charCodeAt(0));\n\t\t\t\t\t\tstate = 3;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tcharCodes.push(\"&\".charCodeAt(0));\n\t\t\t\t\t\tstate = 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn TEXT_DECODER.decode(new Uint8Array(charCodes));\n\t\t}\n\n\t\treturn this.#module.UTF8ToString(zName);\n\t}\n\n\t#heapView(): DataView {\n\t\tconst heapBuffer = this.#module.HEAPU8.buffer;\n\t\tif (heapBuffer !== this.#heapDataViewBuffer) {\n\t\t\tthis.#heapDataViewBuffer = heapBuffer;\n\t\t\tthis.#heapDataView = new DataView(heapBuffer);\n\t\t}\n\t\treturn this.#heapDataView;\n\t}\n\n\t#writeInt32(pointer: number, value: number): void {\n\t\tconst heapByteOffset = this.#module.HEAPU8.byteOffset + pointer;\n\t\tthis.#heapView().setInt32(heapByteOffset, value, true);\n\t}\n\n\t#writeBigInt64(pointer: number, value: bigint): void {\n\t\tconst heapByteOffset = this.#module.HEAPU8.byteOffset + pointer;\n\t\tthis.#heapView().setBigInt64(heapByteOffset, value, true);\n\t}\n}\n\n/**\n * Rebuild an i64 from Emscripten's legalized (lo32, hi32) pair.\n * SQLite passes file offsets and sizes this way. We decode into unsigned words\n * and reject values above the VFS max file size.\n */\nfunction delegalize(lo32: number, hi32: number): number {\n\tconst hi = hi32 >>> 0;\n\tconst lo = lo32 >>> 0;\n\tif (hi > MAX_FILE_SIZE_HI32) {\n\t\treturn -1;\n\t}\n\tif (hi === MAX_FILE_SIZE_HI32 && lo > MAX_FILE_SIZE_LO32) {\n\t\treturn -1;\n\t}\n\treturn hi * UINT32_SIZE + lo;\n}\n","/**\n * Key management for SQLite VFS storage\n *\n * This module contains constants and utilities for building keys used in the\n * key-value store for SQLite file storage.\n */\n\n/**\n * Size of each file chunk stored in KV.\n *\n * Set to 4096 to match SQLite's default page size so that one SQLite page\n * maps to exactly one KV value. This avoids partial-chunk reads on page\n * boundaries.\n *\n * Larger chunk sizes (e.g. 32 KiB) would reduce the number of KV keys per\n * database and fit within FDB's recommended 10 KB value chunks (the engine\n * splits values >10 KB internally, see VALUE_CHUNK_SIZE in\n * engine/packages/pegboard/src/actor_kv/mod.rs). However, 4 KiB is kept\n * because:\n *\n * - It matches SQLite's default page_size, avoiding alignment overhead.\n * - At 128 keys per batch and 4 KiB per chunk, a single putBatch can flush\n * up to 512 KiB of dirty pages, which covers most actor databases.\n * - Changing chunk size is a breaking change for existing persisted databases.\n * - KV max value size is 128 KiB, so 4 KiB is well within limits.\n *\n * If page_size is ever changed via PRAGMA, CHUNK_SIZE must be updated to\n * match so the 1:1 page-to-chunk mapping is preserved.\n */\nexport const CHUNK_SIZE = 4096;\n\n/** Top-level SQLite prefix (must match SQLITE_PREFIX in actor KV system) */\nexport const SQLITE_PREFIX = 8;\n\n/** Schema version namespace byte after SQLITE_PREFIX */\nexport const SQLITE_SCHEMA_VERSION = 1;\n\n/** Key prefix byte for file metadata (after SQLITE_PREFIX + version) */\nexport const META_PREFIX = 0;\n\n/** Key prefix byte for file chunks (after SQLITE_PREFIX + version) */\nexport const CHUNK_PREFIX = 1;\n\n/** File kind tag for the actor's main database file */\nexport const FILE_TAG_MAIN = 0;\n\n/** File kind tag for the actor's rollback journal sidecar */\nexport const FILE_TAG_JOURNAL = 1;\n\n/** File kind tag for the actor's WAL sidecar */\nexport const FILE_TAG_WAL = 2;\n\n/** File kind tag for the actor's SHM sidecar */\nexport const FILE_TAG_SHM = 3;\n\nexport type SqliteFileTag =\n\t| typeof FILE_TAG_MAIN\n\t| typeof FILE_TAG_JOURNAL\n\t| typeof FILE_TAG_WAL\n\t| typeof FILE_TAG_SHM;\n\n/**\n * Gets the key for file metadata\n * Format: [SQLITE_PREFIX (1 byte), version (1 byte), META_PREFIX (1 byte), file tag (1 byte)]\n */\nexport function getMetaKey(fileTag: SqliteFileTag): Uint8Array {\n\tconst key = new Uint8Array(4);\n\tkey[0] = SQLITE_PREFIX;\n\tkey[1] = SQLITE_SCHEMA_VERSION;\n\tkey[2] = META_PREFIX;\n\tkey[3] = fileTag;\n\treturn key;\n}\n\n/**\n * Gets the key for one chunk of file data.\n * Format: [SQLITE_PREFIX, CHUNK_PREFIX, file tag, chunk index (u32 big-endian)]\n *\n * The chunk index is derived from byte offset as floor(offset / CHUNK_SIZE),\n * which is how SQLite byte ranges map onto KV keys.\n */\nexport function getChunkKey(\n\tfileTag: SqliteFileTag,\n\tchunkIndex: number,\n): Uint8Array {\n\tconst key = new Uint8Array(8);\n\tkey[0] = SQLITE_PREFIX;\n\tkey[1] = SQLITE_SCHEMA_VERSION;\n\tkey[2] = CHUNK_PREFIX;\n\tkey[3] = fileTag;\n\tkey[4] = (chunkIndex >>> 24) & 0xff;\n\tkey[5] = (chunkIndex >>> 16) & 0xff;\n\tkey[6] = (chunkIndex >>> 8) & 0xff;\n\tkey[7] = chunkIndex & 0xff;\n\treturn key;\n}\n","// Auto-generated by scripts/generate-empty-db-page.ts\n// DO NOT EDIT. Re-generate with: pnpm run generate:empty-db-page\n//\n// This is page 1 of a valid empty SQLite database (page_size=4096,\n// journal_mode=DELETE, encoding=UTF-8, auto_vacuum=NONE). The VFS\n// pre-writes this page into KV for new databases so that SQLite sees\n// dbSize > 0 on first open. This enables BATCH_ATOMIC from the first\n// write transaction and eliminates the need for a dummy table workaround.\n//\n// Only the first 108 bytes are non-zero (100-byte database header +\n// 8-byte b-tree leaf page header for the empty sqlite_master table).\n\nconst 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]);\n\n/**\n * A complete 4096-byte page 1 for an empty SQLite database.\n * Generated from wa-sqlite with our production PRAGMAs.\n */\nexport const EMPTY_DB_PAGE: Uint8Array = (() => {\n\tconst page = new Uint8Array(4096);\n\tpage.set(HEADER_PREFIX);\n\treturn page;\n})();\n","import { createVersionedDataHandler } from \"vbare\";\nimport * as v1 from \"../../dist/schemas/file-meta/v1\";\n\nexport const CURRENT_VERSION = 1;\n\nexport const FILE_META_VERSIONED = createVersionedDataHandler<v1.FileMeta>({\n\tdeserializeVersion: (bytes, version) => {\n\t\tswitch (version) {\n\t\t\tcase 1:\n\t\t\t\treturn v1.decodeFileMeta(bytes);\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown version ${version}`);\n\t\t}\n\t},\n\tserializeVersion: (data, version) => {\n\t\tswitch (version) {\n\t\t\tcase 1:\n\t\t\t\treturn v1.encodeFileMeta(data as v1.FileMeta);\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown version ${version}`);\n\t\t}\n\t},\n\tdeserializeConverters: () => [],\n\tserializeConverters: () => [],\n});\n","// @generated - post-processed by compile-bare.ts\nimport * as bare from \"@rivetkit/bare-ts\"\n\nconst config = /* @__PURE__ */ bare.Config({})\n\nexport type u64 = bigint\n\nexport type FileMeta = {\n readonly size: u64,\n}\n\nexport function readFileMeta(bc: bare.ByteCursor): FileMeta {\n return {\n size: bare.readU64(bc),\n }\n}\n\nexport function writeFileMeta(bc: bare.ByteCursor, x: FileMeta): void {\n bare.writeU64(bc, x.size)\n}\n\nexport function encodeFileMeta(x: FileMeta): Uint8Array {\n const bc = new bare.ByteCursor(\n new Uint8Array(config.initialBufferLength),\n config\n )\n writeFileMeta(bc, x)\n return new Uint8Array(bc.view.buffer, bc.view.byteOffset, bc.offset)\n}\n\nexport function decodeFileMeta(bytes: Uint8Array): FileMeta {\n const bc = new bare.ByteCursor(bytes, config)\n const result = readFileMeta(bc)\n if (bc.offset < bc.view.byteLength) {\n throw new bare.BareError(bc.offset, \"remaining bytes\")\n }\n return result\n}\n\n\nfunction assert(condition: boolean, message?: string): asserts condition {\n if (!condition) throw new Error(message ?? \"Assertion failed\")\n}\n","/**\n * SQLite VFS Pool - shares WASM SQLite instances across actors to reduce\n * memory overhead. Instead of one WASM module per actor, multiple actors\n * share a single instance, with short file names routing to separate KV\n * namespaces.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { SqliteVfs } from \"./vfs\";\nimport type { ISqliteVfs, IDatabase } from \"./vfs\";\nimport type { KvVfsOptions } from \"./types\";\n\nexport interface SqliteVfsPoolConfig {\n\tactorsPerInstance: number;\n\tidleDestroyMs?: number;\n}\n\n/**\n * Internal state for a single WASM SQLite instance shared by multiple actors.\n */\ninterface PoolInstance {\n\tvfs: SqliteVfs;\n\t/** Actor IDs currently assigned to this instance. */\n\tactors: Set<string>;\n\t/** Monotonically increasing counter for generating short file names. */\n\tshortNameCounter: number;\n\t/** Maps actorId to the short name assigned within this instance. */\n\tactorShortNames: Map<string, string>;\n\t/** Short names released by actors that closed successfully, available for reuse. */\n\tavailableShortNames: Set<string>;\n\t/** Short names that failed to close cleanly. Not reused until instance is destroyed. */\n\tpoisonedShortNames: Set<string>;\n\t/** Number of in-flight operations (e.g. open calls) on this instance. */\n\topsInFlight: number;\n\t/** Handle for the idle destruction timer, or null if not scheduled. */\n\tidleTimer: ReturnType<typeof setTimeout> | null;\n\t/** True once destruction has started. Prevents double-destroy. */\n\tdestroying: boolean;\n}\n\n/**\n * Manages a pool of SqliteVfs instances, assigning actors to instances using\n * bin-packing to maximize density. The WASM module is compiled once and\n * reused across all instances.\n */\nexport class SqliteVfsPool {\n\treadonly #config: SqliteVfsPoolConfig;\n\t#modulePromise: Promise<WebAssembly.Module> | null = null;\n\treadonly #instances: Set<PoolInstance> = new Set();\n\treadonly #actorToInstance: Map<string, PoolInstance> = new Map();\n\treadonly #actorToHandle: Map<string, PooledSqliteHandle> = new Map();\n\t#shuttingDown = false;\n\n\tconstructor(config: SqliteVfsPoolConfig) {\n\t\tif (\n\t\t\t!Number.isInteger(config.actorsPerInstance) ||\n\t\t\tconfig.actorsPerInstance < 1\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`actorsPerInstance must be a positive integer, got ${config.actorsPerInstance}`,\n\t\t\t);\n\t\t}\n\t\tthis.#config = config;\n\t}\n\n\t/**\n\t * Compile the WASM module once and cache the promise. Subsequent calls\n\t * return the same promise, avoiding redundant compilation.\n\t */\n\t#getModule(): Promise<WebAssembly.Module> {\n\t\tif (!this.#modulePromise) {\n\t\t\tthis.#modulePromise = (async () => {\n\t\t\t\tconst require = createRequire(import.meta.url);\n\t\t\t\tconst wasmPath = require.resolve(\n\t\t\t\t\t\"@rivetkit/sqlite/dist/wa-sqlite-async.wasm\",\n\t\t\t\t);\n\t\t\t\tconst wasmBinary = readFileSync(wasmPath);\n\t\t\t\treturn WebAssembly.compile(wasmBinary);\n\t\t\t})();\n\t\t\t// Clear the cached promise on rejection so subsequent calls retry\n\t\t\t// compilation instead of returning the same rejected promise forever.\n\t\t\tthis.#modulePromise.catch(() => {\n\t\t\t\tthis.#modulePromise = null;\n\t\t\t});\n\t\t}\n\t\treturn this.#modulePromise;\n\t}\n\n\t/** Number of live WASM instances in the pool. */\n\tget instanceCount(): number {\n\t\treturn this.#instances.size;\n\t}\n\n\t/** Number of actors currently assigned to pool instances. */\n\tget actorCount(): number {\n\t\treturn this.#actorToInstance.size;\n\t}\n\n\t/**\n\t * Acquire a pooled VFS handle for the given actor. Returns a\n\t * PooledSqliteHandle with sticky assignment. If the actor is already\n\t * assigned, the existing handle is returned.\n\t *\n\t * Bin-packing: picks the instance with the most actors that still has\n\t * capacity. If all instances are full, creates a new one using the\n\t * cached WASM module.\n\t */\n\tasync acquire(actorId: string): Promise<PooledSqliteHandle> {\n\t\tif (this.#shuttingDown) {\n\t\t\tthrow new Error(\"SqliteVfsPool is shutting down\");\n\t\t}\n\n\t\t// Sticky assignment: return existing handle.\n\t\tconst existingHandle = this.#actorToHandle.get(actorId);\n\t\tif (existingHandle) {\n\t\t\treturn existingHandle;\n\t\t}\n\n\t\t// Bin-packing: pick instance with most actors that still has capacity.\n\t\t// Skip instances that are being destroyed.\n\t\tlet bestInstance: PoolInstance | null = null;\n\t\tlet bestCount = -1;\n\t\tfor (const instance of this.#instances) {\n\t\t\tif (instance.destroying) continue;\n\t\t\tconst count = instance.actors.size;\n\t\t\tif (count < this.#config.actorsPerInstance && count > bestCount) {\n\t\t\t\tbestInstance = instance;\n\t\t\t\tbestCount = count;\n\t\t\t}\n\t\t}\n\n\t\t// If all instances are full, compile the module and re-check capacity.\n\t\t// Multiple concurrent acquire() calls may all reach this point. After\n\t\t// awaiting the module, re-scan for capacity that another caller may\n\t\t// have created during the await, to avoid creating duplicate instances.\n\t\tif (!bestInstance) {\n\t\t\tconst wasmModule = await this.#getModule();\n\t\t\tif (this.#shuttingDown) {\n\t\t\t\tthrow new Error(\"SqliteVfsPool is shutting down\");\n\t\t\t}\n\n\t\t\t// Re-check sticky assignment: another concurrent acquire() for the\n\t\t\t// same actorId may have completed during the await.\n\t\t\tconst existingHandleAfterAwait = this.#actorToHandle.get(actorId);\n\t\t\tif (existingHandleAfterAwait) {\n\t\t\t\treturn existingHandleAfterAwait;\n\t\t\t}\n\n\t\t\t// Re-scan for an instance with available capacity that was created\n\t\t\t// by another concurrent acquire() during the module compilation.\n\t\t\tfor (const instance of this.#instances) {\n\t\t\t\tif (instance.destroying) continue;\n\t\t\t\tconst count = instance.actors.size;\n\t\t\t\tif (count < this.#config.actorsPerInstance && count > bestCount) {\n\t\t\t\t\tbestInstance = instance;\n\t\t\t\t\tbestCount = count;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!bestInstance) {\n\t\t\t\tconst vfs = new SqliteVfs(wasmModule);\n\t\t\t\tbestInstance = {\n\t\t\t\t\tvfs,\n\t\t\t\t\tactors: new Set(),\n\t\t\t\t\tshortNameCounter: 0,\n\t\t\t\t\tactorShortNames: new Map(),\n\t\t\t\t\tavailableShortNames: new Set(),\n\t\t\t\t\tpoisonedShortNames: new Set(),\n\t\t\t\t\topsInFlight: 0,\n\t\t\t\t\tidleTimer: null,\n\t\t\t\t\tdestroying: false,\n\t\t\t\t};\n\t\t\t\tthis.#instances.add(bestInstance);\n\t\t\t}\n\t\t}\n\n\t\t// Cancel idle timer synchronously since this instance is getting a\n\t\t// new actor and should not be destroyed.\n\t\tthis.#cancelIdleTimer(bestInstance);\n\n\t\t// Assign actor to instance with a short file name. Prefer recycled\n\t\t// names from the available set before generating a new one.\n\t\tlet shortName: string;\n\t\tconst recycled = bestInstance.availableShortNames.values().next();\n\t\tif (!recycled.done) {\n\t\t\tshortName = recycled.value;\n\t\t\tbestInstance.availableShortNames.delete(shortName);\n\t\t} else {\n\t\t\tshortName = String(bestInstance.shortNameCounter++);\n\t\t}\n\t\tbestInstance.actors.add(actorId);\n\t\tbestInstance.actorShortNames.set(actorId, shortName);\n\t\tthis.#actorToInstance.set(actorId, bestInstance);\n\n\t\tconst handle = new PooledSqliteHandle(\n\t\t\tshortName,\n\t\t\tactorId,\n\t\t\tthis,\n\t\t);\n\t\tthis.#actorToHandle.set(actorId, handle);\n\n\t\treturn handle;\n\t}\n\n\t/**\n\t * Release an actor's assignment from the pool. Force-closes all database\n\t * handles for the actor, recycles or poisons the short name, and\n\t * decrements the instance refcount.\n\t */\n\tasync release(actorId: string): Promise<void> {\n\t\tconst instance = this.#actorToInstance.get(actorId);\n\t\tif (!instance) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst shortName = instance.actorShortNames.get(actorId);\n\t\tif (shortName === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Force-close all Database handles for this actor's short name.\n\t\tconst { allSucceeded } =\n\t\t\tawait instance.vfs.forceCloseByFileName(shortName);\n\n\t\tif (allSucceeded) {\n\t\t\tinstance.availableShortNames.add(shortName);\n\t\t} else {\n\t\t\tinstance.poisonedShortNames.add(shortName);\n\t\t}\n\n\t\t// Remove actor from instance tracking.\n\t\tinstance.actors.delete(actorId);\n\t\tinstance.actorShortNames.delete(actorId);\n\t\tthis.#actorToInstance.delete(actorId);\n\t\tthis.#actorToHandle.delete(actorId);\n\n\t\t// Start idle timer if instance has no actors and no in-flight ops.\n\t\t// Skip if shutting down to avoid leaking timers after shutdown\n\t\t// completes.\n\t\tif (instance.actors.size === 0 && instance.opsInFlight === 0 && !this.#shuttingDown) {\n\t\t\tthis.#startIdleTimer(instance);\n\t\t}\n\t}\n\n\t/**\n\t * Track an in-flight operation on an instance. Increments opsInFlight\n\t * before running fn, decrements after using try/finally to prevent\n\t * drift from exceptions. If the decrement brings opsInFlight to 0\n\t * with refcount also 0, starts the idle timer.\n\t */\n\tasync #trackOp<T>(\n\t\tinstance: PoolInstance,\n\t\tfn: () => Promise<T>,\n\t): Promise<T> {\n\t\tinstance.opsInFlight++;\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tinstance.opsInFlight--;\n\t\t\tif (\n\t\t\t\tinstance.actors.size === 0 &&\n\t\t\t\tinstance.opsInFlight === 0 &&\n\t\t\t\t!instance.destroying &&\n\t\t\t\t!this.#shuttingDown\n\t\t\t) {\n\t\t\t\tthis.#startIdleTimer(instance);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Open a database on behalf of an actor, tracked as an in-flight\n\t * operation. Used by PooledSqliteHandle to avoid exposing PoolInstance.\n\t */\n\tasync openForActor(\n\t\tactorId: string,\n\t\tshortName: string,\n\t\toptions: KvVfsOptions,\n\t): Promise<IDatabase> {\n\t\tconst instance = this.#actorToInstance.get(actorId);\n\t\tif (!instance) {\n\t\t\tthrow new Error(`Actor ${actorId} is not assigned to any pool instance`);\n\t\t}\n\t\treturn this.#trackOp(instance, () =>\n\t\t\tinstance.vfs.open(shortName, options),\n\t\t);\n\t}\n\n\t/**\n\t * Track an in-flight database operation for the given actor. Resolves the\n\t * actor's pool instance and wraps the operation with opsInFlight tracking.\n\t * If the actor has already been released, the operation runs without\n\t * tracking since the instance may already be destroyed.\n\t */\n\tasync trackOpForActor<T>(\n\t\tactorId: string,\n\t\tfn: () => Promise<T>,\n\t): Promise<T> {\n\t\tconst instance = this.#actorToInstance.get(actorId);\n\t\tif (!instance) {\n\t\t\treturn fn();\n\t\t}\n\t\treturn this.#trackOp(instance, fn);\n\t}\n\n\t#startIdleTimer(instance: PoolInstance): void {\n\t\tif (instance.idleTimer || instance.destroying) return;\n\t\tconst idleDestroyMs = this.#config.idleDestroyMs ?? 30_000;\n\t\tinstance.idleTimer = setTimeout(() => {\n\t\t\tinstance.idleTimer = null;\n\t\t\t// Check opsInFlight in addition to actors.size. With tracked\n\t\t\t// database operations (TrackedDatabase), opsInFlight can be >0\n\t\t\t// while actors.size is 0 if the last operation is still in-flight\n\t\t\t// after release. The #trackOp finally block will re-start the\n\t\t\t// idle timer when ops drain to 0.\n\t\t\tif (\n\t\t\t\tinstance.actors.size === 0 &&\n\t\t\t\tinstance.opsInFlight === 0 &&\n\t\t\t\t!instance.destroying\n\t\t\t) {\n\t\t\t\tthis.#destroyInstance(instance);\n\t\t\t}\n\t\t}, idleDestroyMs);\n\t}\n\n\t#cancelIdleTimer(instance: PoolInstance): void {\n\t\tif (instance.idleTimer) {\n\t\t\tclearTimeout(instance.idleTimer);\n\t\t\tinstance.idleTimer = null;\n\t\t}\n\t}\n\n\tasync #destroyInstance(instance: PoolInstance): Promise<void> {\n\t\tinstance.destroying = true;\n\t\tthis.#cancelIdleTimer(instance);\n\t\t// Remove from pool map first so no new actors can be assigned.\n\t\tthis.#instances.delete(instance);\n\t\ttry {\n\t\t\tawait instance.vfs.forceCloseAll();\n\t\t\tawait instance.vfs.destroy();\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"SqliteVfsPool: failed to destroy instance\", error);\n\t\t}\n\t}\n\n\t/**\n\t * Graceful shutdown. Rejects new acquire() calls, cancels idle timers,\n\t * force-closes all databases, destroys all VFS instances, and clears pool\n\t * state.\n\t */\n\tasync shutdown(): Promise<void> {\n\t\tthis.#shuttingDown = true;\n\n\t\t// Snapshot instances to array since we mutate the set during iteration.\n\t\tconst instances = [...this.#instances];\n\n\t\tfor (const instance of instances) {\n\t\t\tthis.#cancelIdleTimer(instance);\n\t\t\tthis.#instances.delete(instance);\n\n\t\t\t// Check for in-flight operations (e.g. a concurrent release() call\n\t\t\t// mid-forceCloseByFileName). Database.close() is idempotent\n\t\t\t// (US-019), so concurrent close from shutdown + release is safe,\n\t\t\t// but log a warning for observability.\n\t\t\tif (instance.opsInFlight > 0) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`SqliteVfsPool: shutting down instance with ${instance.opsInFlight} in-flight operation(s). ` +\n\t\t\t\t\t\"Concurrent close is safe due to Database.close() idempotency.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait instance.vfs.forceCloseAll();\n\t\t\t\tawait instance.vfs.destroy();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\"SqliteVfsPool: failed to destroy instance during shutdown\", error);\n\t\t\t}\n\t\t}\n\n\t\tthis.#actorToInstance.clear();\n\t\tthis.#actorToHandle.clear();\n\t}\n}\n\n/**\n * Wraps a Database with opsInFlight tracking so the pool's idle timer\n * does not destroy instances while database operations are in-flight.\n * The unwrapped Database remains in SqliteVfs's #openDatabases set\n * for force-close purposes.\n */\nclass TrackedDatabase implements IDatabase {\n\treadonly #inner: IDatabase;\n\treadonly #pool: SqliteVfsPool;\n\treadonly #actorId: string;\n\n\tconstructor(inner: IDatabase, pool: SqliteVfsPool, actorId: string) {\n\t\tthis.#inner = inner;\n\t\tthis.#pool = pool;\n\t\tthis.#actorId = actorId;\n\t}\n\n\tasync exec(\n\t\t...args: Parameters<IDatabase[\"exec\"]>\n\t): ReturnType<IDatabase[\"exec\"]> {\n\t\treturn this.#pool.trackOpForActor(this.#actorId, () =>\n\t\t\tthis.#inner.exec(...args),\n\t\t);\n\t}\n\n\tasync run(\n\t\t...args: Parameters<IDatabase[\"run\"]>\n\t): ReturnType<IDatabase[\"run\"]> {\n\t\treturn this.#pool.trackOpForActor(this.#actorId, () =>\n\t\t\tthis.#inner.run(...args),\n\t\t);\n\t}\n\n\tasync query(\n\t\t...args: Parameters<IDatabase[\"query\"]>\n\t): ReturnType<IDatabase[\"query\"]> {\n\t\treturn this.#pool.trackOpForActor(this.#actorId, () =>\n\t\t\tthis.#inner.query(...args),\n\t\t);\n\t}\n\n\tasync close(): ReturnType<IDatabase[\"close\"]> {\n\t\treturn this.#pool.trackOpForActor(this.#actorId, () =>\n\t\t\tthis.#inner.close(),\n\t\t);\n\t}\n\n\tget fileName(): string {\n\t\treturn this.#inner.fileName;\n\t}\n}\n\n/**\n * A pooled VFS handle for a single actor. Implements ISqliteVfs so callers\n * can use it interchangeably with a standalone SqliteVfs. The short name\n * assigned by the pool is used as the VFS file path, while the caller's\n * KvVfsOptions routes data to the correct KV namespace.\n */\nexport class PooledSqliteHandle implements ISqliteVfs {\n\treadonly #shortName: string;\n\treadonly #actorId: string;\n\treadonly #pool: SqliteVfsPool;\n\t#released = false;\n\n\tconstructor(\n\t\tshortName: string,\n\t\tactorId: string,\n\t\tpool: SqliteVfsPool,\n\t) {\n\t\tthis.#shortName = shortName;\n\t\tthis.#actorId = actorId;\n\t\tthis.#pool = pool;\n\t}\n\n\t/**\n\t * Open a database on the shared instance. Uses the pool-assigned short\n\t * name as the VFS file path, with the caller's KvVfsOptions for KV\n\t * routing. The open call itself is tracked as an in-flight operation,\n\t * and the returned Database is wrapped so that exec(), run(), query(),\n\t * and close() are also tracked via opsInFlight.\n\t */\n\tasync open(_fileName: string, options: KvVfsOptions): Promise<IDatabase> {\n\t\tif (this.#released) {\n\t\t\tthrow new Error(\"PooledSqliteHandle has been released\");\n\t\t}\n\t\tconst db = await this.#pool.openForActor(\n\t\t\tthis.#actorId,\n\t\t\tthis.#shortName,\n\t\t\toptions,\n\t\t);\n\t\treturn new TrackedDatabase(\n\t\t\tdb,\n\t\t\tthis.#pool,\n\t\t\tthis.#actorId,\n\t\t);\n\t}\n\n\t/**\n\t * Release this actor's assignment back to the pool. Idempotent: calling\n\t * destroy() more than once is a no-op, preventing double-release from\n\t * decrementing the instance refcount below actual.\n\t */\n\tasync destroy(): Promise<void> {\n\t\tif (this.#released) {\n\t\t\treturn;\n\t\t}\n\t\tthis.#released = true;\n\t\tawait this.#pool.release(this.#actorId);\n\t}\n}\n"],"mappings":"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","names":["SQLITE_OPEN_CREATE","require","encodeFileMeta","decodeFileMeta","db","SQLITE_OPEN_CREATE","newSize","previousSize","previousMetaDirty","readFileSync","createRequire","config","require","createRequire","readFileSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivetkit/sqlite-vfs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6-rc.1",
|
|
4
4
|
"description": "SQLite VFS backed by KV storage for RivetKit",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -40,9 +40,10 @@
|
|
|
40
40
|
"vitest": "^3.1.1"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
|
-
"build": "pnpm run compile:bare && tsup src/index.ts",
|
|
43
|
+
"build": "pnpm run generate:empty-db-page && pnpm run compile:bare && tsup src/index.ts",
|
|
44
|
+
"generate:empty-db-page": "tsx scripts/generate-empty-db-page.ts",
|
|
44
45
|
"compile:bare": "tsx scripts/compile-bare.ts compile schemas/file-meta/v1.bare -o dist/schemas/file-meta/v1.ts",
|
|
45
|
-
"check-types": "pnpm run compile:bare && tsc --noEmit",
|
|
46
|
+
"check-types": "pnpm run generate:empty-db-page && pnpm run compile:bare && tsc --noEmit",
|
|
46
47
|
"test": "pnpm --filter @rivetkit/sqlite-vfs-test test"
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-empty-db-page.ts
|
|
2
|
+
// DO NOT EDIT. Re-generate with: pnpm run generate:empty-db-page
|
|
3
|
+
//
|
|
4
|
+
// This is page 1 of a valid empty SQLite database (page_size=4096,
|
|
5
|
+
// journal_mode=DELETE, encoding=UTF-8, auto_vacuum=NONE). The VFS
|
|
6
|
+
// pre-writes this page into KV for new databases so that SQLite sees
|
|
7
|
+
// dbSize > 0 on first open. This enables BATCH_ATOMIC from the first
|
|
8
|
+
// write transaction and eliminates the need for a dummy table workaround.
|
|
9
|
+
//
|
|
10
|
+
// Only the first 108 bytes are non-zero (100-byte database header +
|
|
11
|
+
// 8-byte b-tree leaf page header for the empty sqlite_master table).
|
|
12
|
+
|
|
13
|
+
const 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]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A complete 4096-byte page 1 for an empty SQLite database.
|
|
17
|
+
* Generated from wa-sqlite with our production PRAGMAs.
|
|
18
|
+
*/
|
|
19
|
+
export const EMPTY_DB_PAGE: Uint8Array = (() => {
|
|
20
|
+
const page = new Uint8Array(4096);
|
|
21
|
+
page.set(HEADER_PREFIX);
|
|
22
|
+
return page;
|
|
23
|
+
})();
|
package/src/index.ts
CHANGED
package/src/kv.ts
CHANGED
|
@@ -8,9 +8,24 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* Size of each file chunk stored in KV.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Set to 4096 to match SQLite's default page size so that one SQLite page
|
|
12
|
+
* maps to exactly one KV value. This avoids partial-chunk reads on page
|
|
13
|
+
* boundaries.
|
|
14
|
+
*
|
|
15
|
+
* Larger chunk sizes (e.g. 32 KiB) would reduce the number of KV keys per
|
|
16
|
+
* database and fit within FDB's recommended 10 KB value chunks (the engine
|
|
17
|
+
* splits values >10 KB internally, see VALUE_CHUNK_SIZE in
|
|
18
|
+
* engine/packages/pegboard/src/actor_kv/mod.rs). However, 4 KiB is kept
|
|
19
|
+
* because:
|
|
20
|
+
*
|
|
21
|
+
* - It matches SQLite's default page_size, avoiding alignment overhead.
|
|
22
|
+
* - At 128 keys per batch and 4 KiB per chunk, a single putBatch can flush
|
|
23
|
+
* up to 512 KiB of dirty pages, which covers most actor databases.
|
|
24
|
+
* - Changing chunk size is a breaking change for existing persisted databases.
|
|
25
|
+
* - KV max value size is 128 KiB, so 4 KiB is well within limits.
|
|
26
|
+
*
|
|
27
|
+
* If page_size is ever changed via PRAGMA, CHUNK_SIZE must be updated to
|
|
28
|
+
* match so the 1:1 page-to-chunk mapping is preserved.
|
|
14
29
|
*/
|
|
15
30
|
export const CHUNK_SIZE = 4096;
|
|
16
31
|
|