@korajs/store 1.0.0-beta.5 → 1.0.0-beta.6
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/adapters/better-sqlite3.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/indexeddb.cjs +309 -128
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.d.cts +4 -2
- package/dist/adapters/indexeddb.d.ts +4 -2
- package/dist/adapters/indexeddb.js +7 -2
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +250 -3
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +304 -128
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +18 -7
- package/dist/adapters/sqlite-wasm.d.ts +18 -7
- package/dist/adapters/sqlite-wasm.js +2 -2
- package/dist/{chunk-Q57CHIIN.js → chunk-FKW56RVZ.js} +306 -129
- package/dist/chunk-FKW56RVZ.js.map +1 -0
- package/dist/{chunk-COJSQBXW.js → chunk-VDBJQ75K.js} +1 -1
- package/dist/chunk-VDBJQ75K.js.map +1 -0
- package/dist/{chunk-RL43UYQZ.js → chunk-YT7MRFCP.js} +5 -5
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +34 -18
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +2 -2
- package/dist/{sqlite-wasm-channel-NGSBJR44.js → sqlite-wasm-channel-GGHA2BXU.js} +2 -2
- package/dist/{sqlite-wasm-channel-Bx57s8Cq.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +2 -2
- package/dist/{sqlite-wasm-channel-Bx57s8Cq.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +2 -2
- package/dist/{types-C1kigrWz.d.ts → types-BOO9EPt1.d.ts} +1 -1
- package/dist/{types-D1MzKVmk.d.ts → types-BslDOTk0.d.cts} +16 -1
- package/dist/{types-D1MzKVmk.d.cts → types-BslDOTk0.d.ts} +16 -1
- package/dist/{types-DAo9oduO.d.cts → types-C_GDavG-.d.cts} +1 -1
- package/package.json +1 -11
- package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -345
- package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
- package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -82
- package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -82
- package/dist/adapters/sqlite-wasm-shared-host.js +0 -61
- package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
- package/dist/chunk-3Y2IERBF.js +0 -255
- package/dist/chunk-3Y2IERBF.js.map +0 -1
- package/dist/chunk-COJSQBXW.js.map +0 -1
- package/dist/chunk-Q57CHIIN.js.map +0 -1
- /package/dist/{chunk-RL43UYQZ.js.map → chunk-YT7MRFCP.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-NGSBJR44.js.map → sqlite-wasm-channel-GGHA2BXU.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/indexeddb-adapter.ts","../../src/adapters/indexeddb-persistence-scheduler.ts","../../src/adapters/sqlite-wasm-persistence.ts"],"sourcesContent":["import type { KoraEventEmitter, SchemaDefinition } from '@korajs/core'\nimport { quoteIdent } from '@korajs/core'\nimport { PersistenceError } from '../errors'\nimport type { MigrationPlan, StorageAdapter, Transaction } from '../types'\nimport { IndexedDbPersistenceScheduler } from './indexeddb-persistence-scheduler'\nimport { SqliteWasmAdapter } from './sqlite-wasm-adapter'\nimport type { WorkerBridge } from './sqlite-wasm-channel'\nimport {\n\tisIndexedDbQuotaError,\n\tloadDumpFromIndexedDB,\n\tloadFromIndexedDB,\n\tsaveDumpToIndexedDB,\n\tsaveToIndexedDB,\n} from './sqlite-wasm-persistence'\n\ninterface DatabaseDump {\n\ttables: Array<{\n\t\tname: string\n\t\tcolumns: string[]\n\t\trows: Array<Record<string, unknown>>\n\t}>\n}\n\n/**\n * Options for creating an IndexedDbAdapter.\n */\nexport interface IndexedDbAdapterOptions {\n\t/**\n\t * Database name used as the IndexedDB key for persistence.\n\t * Defaults to 'kora-db'.\n\t */\n\tdbName?: string\n\n\t/**\n\t * Injected WorkerBridge for testing. If omitted, a WebWorkerBridge is created\n\t * in browser environments.\n\t */\n\tbridge?: WorkerBridge\n\n\t/**\n\t * URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.\n\t */\n\tworkerUrl?: string | URL\n\n\t/** Timeout for worker / follower RPC responses. Defaults to 30000ms. */\n\tworkerResponseTimeoutMs?: number\n\n\t/**\n\t * Debounce interval (ms) before writing snapshots to IndexedDB. Defaults to 500.\n\t */\n\tpersistenceDebounceMs?: number\n\n\t/**\n\t * When set, persistence failures and quota errors are emitted on this emitter.\n\t */\n\temitter?: KoraEventEmitter\n}\n\n/**\n * IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes\n * the entire database to IndexedDB after mutations (coalesced/debounced).\n *\n * This is the fallback adapter for browsers where OPFS is not available.\n * It provides the same SQL interface as SqliteWasmAdapter, but persists by\n * serializing the full SQLite database to a single IndexedDB blob.\n *\n * @example\n * ```typescript\n * const adapter = new IndexedDbAdapter({ workerUrl: '/sqlite-wasm-worker.js' })\n * ```\n */\nexport class IndexedDbAdapter implements StorageAdapter {\n\tprivate inner: SqliteWasmAdapter\n\tprivate readonly dbName: string\n\tprivate readonly emitter: KoraEventEmitter | undefined\n\tprivate readonly scheduler: IndexedDbPersistenceScheduler\n\n\tconstructor(options: IndexedDbAdapterOptions = {}) {\n\t\tthis.dbName = options.dbName ?? 'kora-db'\n\t\tthis.emitter = options.emitter\n\t\tthis.inner = new SqliteWasmAdapter({\n\t\t\tbridge: options.bridge,\n\t\t\tworkerUrl: options.workerUrl,\n\t\t\tdbName: this.dbName,\n\t\t\tworkerResponseTimeoutMs: options.workerResponseTimeoutMs,\n\t\t})\n\t\tthis.scheduler = new IndexedDbPersistenceScheduler({\n\t\t\tdebounceMs: options.persistenceDebounceMs,\n\t\t\tflush: () => this.writeSnapshot(),\n\t\t\tonError: (error) => this.handlePersistenceError(error),\n\t\t})\n\t}\n\n\tasync open(schema: SchemaDefinition): Promise<void> {\n\t\tawait this.inner.open(schema)\n\n\t\tconst persisted = await loadFromIndexedDB(this.dbName)\n\t\tif (!persisted) return\n\n\t\ttry {\n\t\t\tawait this.inner.importDatabase(persisted)\n\t\t} catch {\n\t\t\tawait this.restoreFromDumpFallback()\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tawait this.scheduler.flushNow()\n\t\tthis.scheduler.dispose()\n\t\tawait this.inner.close()\n\t}\n\n\tasync execute(sql: string, params?: unknown[]): Promise<void> {\n\t\tawait this.inner.execute(sql, params)\n\t\tthis.scheduler.schedule()\n\t}\n\n\tasync query<T>(sql: string, params?: unknown[]): Promise<T[]> {\n\t\treturn this.inner.query<T>(sql, params)\n\t}\n\n\tasync transaction(fn: (tx: Transaction) => Promise<void>): Promise<void> {\n\t\tawait this.inner.transaction(fn)\n\t\tthis.scheduler.schedule()\n\t}\n\n\tasync migrate(from: number, to: number, migration: MigrationPlan): Promise<void> {\n\t\tawait this.inner.migrate(from, to, migration)\n\t\tthis.scheduler.schedule()\n\t}\n\n\t/**\n\t * Force an immediate snapshot write to IndexedDB (skips debounce).\n\t * Useful before tab unload or in tests.\n\t */\n\tasync flushPersistence(): Promise<void> {\n\t\tawait this.scheduler.flushNow()\n\t}\n\n\tprivate async writeSnapshot(): Promise<void> {\n\t\tconst data = await this.inner.exportDatabase()\n\t\tawait saveToIndexedDB(this.dbName, data)\n\t\tconst dump = await this.exportDump()\n\t\tawait saveDumpToIndexedDB(this.dbName, dump)\n\t}\n\n\tprivate handlePersistenceError(error: unknown): void {\n\t\tconst message = error instanceof Error ? error.message : 'IndexedDB persistence failed'\n\t\tconst code = error instanceof PersistenceError ? error.code : 'PERSISTENCE_FAILED'\n\t\tconst quotaExceeded = isIndexedDbQuotaError(error)\n\n\t\tif (quotaExceeded) {\n\t\t\tthis.emitter?.emit({\n\t\t\t\ttype: 'store:quota-exceeded',\n\t\t\t\tdbName: this.dbName,\n\t\t\t\tmessage,\n\t\t\t})\n\t\t}\n\n\t\tthis.emitter?.emit({\n\t\t\ttype: 'store:persistence-error',\n\t\t\tdbName: this.dbName,\n\t\t\tmessage,\n\t\t\tcode: quotaExceeded ? 'QUOTA_EXCEEDED' : code,\n\t\t})\n\t}\n\n\tprivate async restoreFromDumpFallback(): Promise<void> {\n\t\tconst dump = await loadDumpFromIndexedDB<DatabaseDump>(this.dbName)\n\t\tif (!dump) return\n\n\t\tfor (const table of dump.tables) {\n\t\t\tconst name = ensureSafeIdentifier(table.name)\n\t\t\tawait this.inner.execute(`DELETE FROM ${quoteIdent(name)}`)\n\n\t\t\tif (table.rows.length === 0) continue\n\n\t\t\tfor (const row of table.rows) {\n\t\t\t\tconst columns = table.columns.filter((column) =>\n\t\t\t\t\tObject.prototype.hasOwnProperty.call(row, column),\n\t\t\t\t)\n\t\t\t\tif (columns.length === 0) continue\n\n\t\t\t\tconst placeholders = columns.map(() => '?').join(', ')\n\t\t\t\tconst quotedColumns = columns\n\t\t\t\t\t.map((column) => quoteIdent(ensureSafeIdentifier(column)))\n\t\t\t\t\t.join(', ')\n\t\t\t\tconst values = columns.map((column) => row[column])\n\n\t\t\t\tawait this.inner.execute(\n\t\t\t\t\t`INSERT INTO ${quoteIdent(name)} (${quotedColumns}) VALUES (${placeholders})`,\n\t\t\t\t\tvalues,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async exportDump(): Promise<DatabaseDump> {\n\t\tconst tableRows = await this.inner.query<{ name: string }>(\n\t\t\t\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\n\t\t)\n\n\t\tconst tables: DatabaseDump['tables'] = []\n\t\tfor (const tableRow of tableRows) {\n\t\t\tconst tableName = ensureSafeIdentifier(tableRow.name)\n\t\t\tconst columns = await this.inner.query<{ name: string }>(\n\t\t\t\t`PRAGMA table_info(${quoteIdent(tableName)})`,\n\t\t\t)\n\t\t\tconst columnNames = columns.map((column) => column.name)\n\t\t\tconst rows = await this.inner.query<Record<string, unknown>>(\n\t\t\t\t`SELECT * FROM ${quoteIdent(tableName)}`,\n\t\t\t)\n\n\t\t\ttables.push({\n\t\t\t\tname: tableName,\n\t\t\t\tcolumns: columnNames,\n\t\t\t\trows,\n\t\t\t})\n\t\t}\n\n\t\treturn { tables }\n\t}\n}\n\nfunction ensureSafeIdentifier(identifier: string): string {\n\tif (!/^[a-zA-Z0-9_]+$/.test(identifier)) {\n\t\tthrow new PersistenceError(`Unsafe SQL identifier: ${identifier}`, {\n\t\t\tcode: 'UNSAFE_IDENTIFIER',\n\t\t\tidentifier,\n\t\t})\n\t}\n\treturn identifier\n}\n","/// <reference lib=\"dom\" />\n\nconst DEFAULT_DEBOUNCE_MS = 500\n\nexport interface IndexedDbPersistenceSchedulerOptions {\n\t/** Debounce interval before writing a snapshot. Defaults to 500ms. */\n\tdebounceMs?: number\n\t/** Persist the in-memory database to IndexedDB. */\n\tflush: () => Promise<void>\n\t/** Called when persistence fails (mutation already committed in memory). */\n\tonError?: (error: unknown) => void\n}\n\n/**\n * Coalesces IndexedDB snapshot writes: debounces rapid mutations and flushes\n * immediately on tab hide (`visibilitychange`) or explicit {@link flushNow}.\n */\nexport class IndexedDbPersistenceScheduler {\n\tprivate readonly debounceMs: number\n\tprivate readonly flush: () => Promise<void>\n\tprivate readonly onError: ((error: unknown) => void) | undefined\n\tprivate timer: ReturnType<typeof setTimeout> | null = null\n\tprivate inFlight: Promise<void> | null = null\n\tprivate disposed = false\n\tprivate readonly onVisibilityChange: () => void\n\n\tconstructor(options: IndexedDbPersistenceSchedulerOptions) {\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS\n\t\tthis.flush = options.flush\n\t\tthis.onError = options.onError\n\t\tthis.onVisibilityChange = () => {\n\t\t\tif (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n\t\t\t\tvoid this.flushNow()\n\t\t\t}\n\t\t}\n\t\tif (typeof document !== 'undefined') {\n\t\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t\t}\n\t}\n\n\t/** Schedule a debounced snapshot write. */\n\tschedule(): void {\n\t\tif (this.disposed) return\n\t\tif (this.debounceMs <= 0) {\n\t\t\tvoid this.flushNow()\n\t\t\treturn\n\t\t}\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer)\n\t\t}\n\t\tthis.timer = setTimeout(() => {\n\t\t\tthis.timer = null\n\t\t\tvoid this.flushNow()\n\t\t}, this.debounceMs)\n\t}\n\n\t/** Cancel any pending debounce and persist immediately. */\n\tasync flushNow(): Promise<void> {\n\t\tif (this.disposed) return\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer)\n\t\t\tthis.timer = null\n\t\t}\n\t\tif (this.inFlight) {\n\t\t\tawait this.inFlight\n\t\t\treturn\n\t\t}\n\t\tthis.inFlight = this.runFlush()\n\t\ttry {\n\t\t\tawait this.inFlight\n\t\t} finally {\n\t\t\tthis.inFlight = null\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer)\n\t\t\tthis.timer = null\n\t\t}\n\t\tif (typeof document !== 'undefined') {\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t}\n\t}\n\n\tprivate async runFlush(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.flush()\n\t\t} catch (error) {\n\t\t\tthis.onError?.(error)\n\t\t}\n\t}\n}\n","/// <reference lib=\"dom\" />\nimport { PersistenceError } from '../errors'\n\nconst IDB_DATABASE_NAME = 'kora-persistence'\nconst IDB_STORE_NAME = 'databases'\nconst IDB_VERSION = 1\n\nconst DUMP_SUFFIX = '::dump'\n\n/**\n * Returns true when an IndexedDB error indicates storage quota was exceeded.\n */\nexport function isIndexedDbQuotaError(error: unknown): boolean {\n\tif (error instanceof DOMException && error.name === 'QuotaExceededError') {\n\t\treturn true\n\t}\n\tif (error instanceof PersistenceError && error.context?.quotaExceeded === true) {\n\t\treturn true\n\t}\n\tif (error instanceof Error && /quota/i.test(error.message)) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunction rejectWithIdbError(\n\treject: (reason: unknown) => void,\n\tmessage: string,\n\tcontext: Record<string, unknown>,\n\tevent: Event,\n): void {\n\tconst domError = (event.target as IDBRequest | IDBTransaction | undefined)?.error\n\tif (domError && isIndexedDbQuotaError(domError)) {\n\t\treject(\n\t\t\tnew PersistenceError(message, {\n\t\t\t\t...context,\n\t\t\t\tquotaExceeded: true,\n\t\t\t\tcause: domError.message,\n\t\t\t}),\n\t\t)\n\t\treturn\n\t}\n\treject(new PersistenceError(message, context))\n}\n\n/**\n * Open the IndexedDB database used for SQLite persistence.\n * Creates the object store on first access.\n */\nfunction openIdb(): Promise<IDBDatabase> {\n\treturn new Promise<IDBDatabase>((resolve, reject) => {\n\t\tconst request = indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\trequest.onupgradeneeded = () => {\n\t\t\tconst db = request.result\n\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t}\n\t\t}\n\t\trequest.onsuccess = () => resolve(request.result)\n\t\trequest.onerror = () =>\n\t\t\treject(\n\t\t\t\tnew PersistenceError(\n\t\t\t\t\t`Failed to open IndexedDB: ${request.error?.message ?? 'unknown error'}`,\n\t\t\t\t\t{ database: IDB_DATABASE_NAME },\n\t\t\t\t),\n\t\t\t)\n\t})\n}\n\n/**\n * Save a serialized SQLite database to IndexedDB.\n *\n * @param dbName - Key under which to store the data\n * @param data - Serialized database as Uint8Array\n */\nexport async function saveToIndexedDB(dbName: string, data: Uint8Array): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.put(data, dbName)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = (event) =>\n\t\t\t\trejectWithIdbError(\n\t\t\t\t\treject,\n\t\t\t\t\t`Failed to save database \"${dbName}\" to IndexedDB`,\n\t\t\t\t\t{ dbName },\n\t\t\t\t\tevent,\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Save a logical SQL dump payload to IndexedDB for import-fallback restore.\n */\nexport async function saveDumpToIndexedDB(dbName: string, dump: unknown): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.put(dump, `${dbName}${DUMP_SUFFIX}`)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = (event) =>\n\t\t\t\trejectWithIdbError(\n\t\t\t\t\treject,\n\t\t\t\t\t`Failed to save dump for database \"${dbName}\"`,\n\t\t\t\t\t{ dbName },\n\t\t\t\t\tevent,\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Load a serialized SQLite database from IndexedDB.\n *\n * @param dbName - Key under which the data was stored\n * @returns The serialized database, or null if not found\n */\nexport async function loadFromIndexedDB(dbName: string): Promise<Uint8Array | null> {\n\tconst idb = await openIdb()\n\ttry {\n\t\treturn await new Promise<Uint8Array | null>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tconst request = store.get(dbName)\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tconst result = request.result\n\t\t\t\tif (result instanceof Uint8Array) {\n\t\t\t\t\tresolve(result)\n\t\t\t\t} else if (result) {\n\t\t\t\t\t// Handle ArrayBuffer or other typed array forms\n\t\t\t\t\tresolve(new Uint8Array(result as ArrayBuffer))\n\t\t\t\t} else {\n\t\t\t\t\tresolve(null)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequest.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to load database \"${dbName}\" from IndexedDB`, { dbName }),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Load a logical SQL dump payload from IndexedDB.\n */\nexport async function loadDumpFromIndexedDB<T>(dbName: string): Promise<T | null> {\n\tconst idb = await openIdb()\n\ttry {\n\t\treturn await new Promise<T | null>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tconst request = store.get(`${dbName}${DUMP_SUFFIX}`)\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve((request.result as T | undefined) ?? null)\n\t\t\t}\n\t\t\trequest.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to load dump for database \"${dbName}\" from IndexedDB`, {\n\t\t\t\t\t\tdbName,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Delete a serialized SQLite database from IndexedDB.\n *\n * @param dbName - Key to delete\n */\nexport async function deleteFromIndexedDB(dbName: string): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.delete(dbName)\n\t\t\tstore.delete(`${dbName}${DUMP_SUFFIX}`)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to delete database \"${dbName}\" from IndexedDB`, { dbName }),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/indexeddb-adapter.ts","../../src/adapters/indexeddb-persistence-scheduler.ts","../../src/adapters/sqlite-wasm-persistence.ts"],"sourcesContent":["import type { KoraEventEmitter, SchemaDefinition } from '@korajs/core'\nimport { quoteIdent } from '@korajs/core'\nimport { PersistenceError } from '../errors'\nimport type { MigrationPlan, StorageAdapter, StorageOpenState, Transaction } from '../types'\nimport { IndexedDbPersistenceScheduler } from './indexeddb-persistence-scheduler'\nimport { SqliteWasmAdapter } from './sqlite-wasm-adapter'\nimport type { WorkerBridge } from './sqlite-wasm-channel'\nimport {\n\tisIndexedDbQuotaError,\n\tloadDumpFromIndexedDB,\n\tloadFromIndexedDB,\n\tsaveDumpToIndexedDB,\n\tsaveToIndexedDB,\n} from './sqlite-wasm-persistence'\n\ninterface DatabaseDump {\n\ttables: Array<{\n\t\tname: string\n\t\tcolumns: string[]\n\t\trows: Array<Record<string, unknown>>\n\t}>\n}\n\n/**\n * Options for creating an IndexedDbAdapter.\n */\nexport interface IndexedDbAdapterOptions {\n\t/**\n\t * Database name used as the IndexedDB key for persistence.\n\t * Defaults to 'kora-db'.\n\t */\n\tdbName?: string\n\n\t/**\n\t * Injected WorkerBridge for testing. If omitted, a WebWorkerBridge is created\n\t * in browser environments.\n\t */\n\tbridge?: WorkerBridge\n\n\t/**\n\t * URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.\n\t */\n\tworkerUrl?: string | URL\n\n\t/** Timeout for worker / follower RPC responses. Defaults to 30000ms. */\n\tworkerResponseTimeoutMs?: number\n\n\t/**\n\t * Debounce interval (ms) before writing snapshots to IndexedDB. Defaults to 500.\n\t */\n\tpersistenceDebounceMs?: number\n\n\t/**\n\t * When set, persistence failures and quota errors are emitted on this emitter.\n\t */\n\temitter?: KoraEventEmitter\n}\n\n/**\n * IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes\n * the entire database to IndexedDB after mutations (coalesced/debounced).\n *\n * This is the fallback adapter for browsers where OPFS is not available.\n * It provides the same SQL interface as SqliteWasmAdapter, but persists by\n * serializing the full SQLite database to a single IndexedDB blob.\n *\n * @example\n * ```typescript\n * const adapter = new IndexedDbAdapter({ workerUrl: '/sqlite-wasm-worker.js' })\n * ```\n */\nexport class IndexedDbAdapter implements StorageAdapter {\n\tprivate inner: SqliteWasmAdapter\n\tprivate readonly dbName: string\n\tprivate readonly emitter: KoraEventEmitter | undefined\n\tprivate readonly scheduler: IndexedDbPersistenceScheduler\n\tprivate storageOpenState: StorageOpenState | null = null\n\n\tconstructor(options: IndexedDbAdapterOptions = {}) {\n\t\tthis.dbName = options.dbName ?? 'kora-db'\n\t\tthis.emitter = options.emitter\n\t\tthis.inner = new SqliteWasmAdapter({\n\t\t\tbridge: options.bridge,\n\t\t\tworkerUrl: options.workerUrl,\n\t\t\tdbName: this.dbName,\n\t\t\tworkerResponseTimeoutMs: options.workerResponseTimeoutMs,\n\t\t})\n\t\tthis.scheduler = new IndexedDbPersistenceScheduler({\n\t\t\tdebounceMs: options.persistenceDebounceMs,\n\t\t\tflush: () => this.writeSnapshot(),\n\t\t\tonError: (error) => this.handlePersistenceError(error),\n\t\t})\n\t}\n\n\tasync open(schema: SchemaDefinition): Promise<void> {\n\t\tawait this.inner.open(schema)\n\t\tthis.storageOpenState = { persistent: true, mode: 'indexeddb' }\n\n\t\tconst persisted = await loadFromIndexedDB(this.dbName)\n\t\tif (!persisted) return\n\n\t\ttry {\n\t\t\tawait this.inner.importDatabase(persisted)\n\t\t} catch {\n\t\t\tawait this.restoreFromDumpFallback()\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tawait this.scheduler.flushNow()\n\t\tthis.scheduler.dispose()\n\t\tawait this.inner.close()\n\t}\n\n\tasync execute(sql: string, params?: unknown[]): Promise<void> {\n\t\tawait this.inner.execute(sql, params)\n\t\tthis.scheduler.schedule()\n\t}\n\n\tasync query<T>(sql: string, params?: unknown[]): Promise<T[]> {\n\t\treturn this.inner.query<T>(sql, params)\n\t}\n\n\tasync transaction(fn: (tx: Transaction) => Promise<void>): Promise<void> {\n\t\tawait this.inner.transaction(fn)\n\t\tthis.scheduler.schedule()\n\t}\n\n\tasync migrate(from: number, to: number, migration: MigrationPlan): Promise<void> {\n\t\tawait this.inner.migrate(from, to, migration)\n\t\tthis.scheduler.schedule()\n\t}\n\n\tgetStorageOpenState(): StorageOpenState | null {\n\t\treturn this.storageOpenState\n\t}\n\n\t/**\n\t * Force an immediate snapshot write to IndexedDB (skips debounce).\n\t * Useful before tab unload or in tests.\n\t */\n\tasync flushPersistence(): Promise<void> {\n\t\tawait this.scheduler.flushNow()\n\t}\n\n\tprivate async writeSnapshot(): Promise<void> {\n\t\tconst data = await this.inner.exportDatabase()\n\t\tawait saveToIndexedDB(this.dbName, data)\n\t\tconst dump = await this.exportDump()\n\t\tawait saveDumpToIndexedDB(this.dbName, dump)\n\t}\n\n\tprivate handlePersistenceError(error: unknown): void {\n\t\tconst message = error instanceof Error ? error.message : 'IndexedDB persistence failed'\n\t\tconst code = error instanceof PersistenceError ? error.code : 'PERSISTENCE_FAILED'\n\t\tconst quotaExceeded = isIndexedDbQuotaError(error)\n\n\t\tif (quotaExceeded) {\n\t\t\tthis.emitter?.emit({\n\t\t\t\ttype: 'store:quota-exceeded',\n\t\t\t\tdbName: this.dbName,\n\t\t\t\tmessage,\n\t\t\t})\n\t\t}\n\n\t\tthis.emitter?.emit({\n\t\t\ttype: 'store:persistence-error',\n\t\t\tdbName: this.dbName,\n\t\t\tmessage,\n\t\t\tcode: quotaExceeded ? 'QUOTA_EXCEEDED' : code,\n\t\t})\n\t}\n\n\tprivate async restoreFromDumpFallback(): Promise<void> {\n\t\tconst dump = await loadDumpFromIndexedDB<DatabaseDump>(this.dbName)\n\t\tif (!dump) return\n\n\t\tfor (const table of dump.tables) {\n\t\t\tconst name = ensureSafeIdentifier(table.name)\n\t\t\tawait this.inner.execute(`DELETE FROM ${quoteIdent(name)}`)\n\n\t\t\tif (table.rows.length === 0) continue\n\n\t\t\tfor (const row of table.rows) {\n\t\t\t\tconst columns = table.columns.filter((column) =>\n\t\t\t\t\tObject.prototype.hasOwnProperty.call(row, column),\n\t\t\t\t)\n\t\t\t\tif (columns.length === 0) continue\n\n\t\t\t\tconst placeholders = columns.map(() => '?').join(', ')\n\t\t\t\tconst quotedColumns = columns\n\t\t\t\t\t.map((column) => quoteIdent(ensureSafeIdentifier(column)))\n\t\t\t\t\t.join(', ')\n\t\t\t\tconst values = columns.map((column) => row[column])\n\n\t\t\t\tawait this.inner.execute(\n\t\t\t\t\t`INSERT INTO ${quoteIdent(name)} (${quotedColumns}) VALUES (${placeholders})`,\n\t\t\t\t\tvalues,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async exportDump(): Promise<DatabaseDump> {\n\t\tconst tableRows = await this.inner.query<{ name: string }>(\n\t\t\t\"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'\",\n\t\t)\n\n\t\tconst tables: DatabaseDump['tables'] = []\n\t\tfor (const tableRow of tableRows) {\n\t\t\tconst tableName = ensureSafeIdentifier(tableRow.name)\n\t\t\tconst columns = await this.inner.query<{ name: string }>(\n\t\t\t\t`PRAGMA table_info(${quoteIdent(tableName)})`,\n\t\t\t)\n\t\t\tconst columnNames = columns.map((column) => column.name)\n\t\t\tconst rows = await this.inner.query<Record<string, unknown>>(\n\t\t\t\t`SELECT * FROM ${quoteIdent(tableName)}`,\n\t\t\t)\n\n\t\t\ttables.push({\n\t\t\t\tname: tableName,\n\t\t\t\tcolumns: columnNames,\n\t\t\t\trows,\n\t\t\t})\n\t\t}\n\n\t\treturn { tables }\n\t}\n}\n\nfunction ensureSafeIdentifier(identifier: string): string {\n\tif (!/^[a-zA-Z0-9_]+$/.test(identifier)) {\n\t\tthrow new PersistenceError(`Unsafe SQL identifier: ${identifier}`, {\n\t\t\tcode: 'UNSAFE_IDENTIFIER',\n\t\t\tidentifier,\n\t\t})\n\t}\n\treturn identifier\n}\n","/// <reference lib=\"dom\" />\n\nconst DEFAULT_DEBOUNCE_MS = 500\n\nexport interface IndexedDbPersistenceSchedulerOptions {\n\t/** Debounce interval before writing a snapshot. Defaults to 500ms. */\n\tdebounceMs?: number\n\t/** Persist the in-memory database to IndexedDB. */\n\tflush: () => Promise<void>\n\t/** Called when persistence fails (mutation already committed in memory). */\n\tonError?: (error: unknown) => void\n}\n\n/**\n * Coalesces IndexedDB snapshot writes: debounces rapid mutations and flushes\n * immediately on tab hide (`visibilitychange`) or explicit {@link flushNow}.\n */\nexport class IndexedDbPersistenceScheduler {\n\tprivate readonly debounceMs: number\n\tprivate readonly flush: () => Promise<void>\n\tprivate readonly onError: ((error: unknown) => void) | undefined\n\tprivate timer: ReturnType<typeof setTimeout> | null = null\n\tprivate inFlight: Promise<void> | null = null\n\tprivate disposed = false\n\tprivate readonly onVisibilityChange: () => void\n\n\tconstructor(options: IndexedDbPersistenceSchedulerOptions) {\n\t\tthis.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS\n\t\tthis.flush = options.flush\n\t\tthis.onError = options.onError\n\t\tthis.onVisibilityChange = () => {\n\t\t\tif (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n\t\t\t\tvoid this.flushNow()\n\t\t\t}\n\t\t}\n\t\tif (typeof document !== 'undefined') {\n\t\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t\t}\n\t}\n\n\t/** Schedule a debounced snapshot write. */\n\tschedule(): void {\n\t\tif (this.disposed) return\n\t\tif (this.debounceMs <= 0) {\n\t\t\tvoid this.flushNow()\n\t\t\treturn\n\t\t}\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer)\n\t\t}\n\t\tthis.timer = setTimeout(() => {\n\t\t\tthis.timer = null\n\t\t\tvoid this.flushNow()\n\t\t}, this.debounceMs)\n\t}\n\n\t/** Cancel any pending debounce and persist immediately. */\n\tasync flushNow(): Promise<void> {\n\t\tif (this.disposed) return\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer)\n\t\t\tthis.timer = null\n\t\t}\n\t\tif (this.inFlight) {\n\t\t\tawait this.inFlight\n\t\t\treturn\n\t\t}\n\t\tthis.inFlight = this.runFlush()\n\t\ttry {\n\t\t\tawait this.inFlight\n\t\t} finally {\n\t\t\tthis.inFlight = null\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true\n\t\tif (this.timer !== null) {\n\t\t\tclearTimeout(this.timer)\n\t\t\tthis.timer = null\n\t\t}\n\t\tif (typeof document !== 'undefined') {\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t}\n\t}\n\n\tprivate async runFlush(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.flush()\n\t\t} catch (error) {\n\t\t\tthis.onError?.(error)\n\t\t}\n\t}\n}\n","/// <reference lib=\"dom\" />\nimport { PersistenceError } from '../errors'\n\nconst IDB_DATABASE_NAME = 'kora-persistence'\nconst IDB_STORE_NAME = 'databases'\nconst IDB_VERSION = 1\n\nconst DUMP_SUFFIX = '::dump'\n\n/**\n * Returns true when an IndexedDB error indicates storage quota was exceeded.\n */\nexport function isIndexedDbQuotaError(error: unknown): boolean {\n\tif (error instanceof DOMException && error.name === 'QuotaExceededError') {\n\t\treturn true\n\t}\n\tif (error instanceof PersistenceError && error.context?.quotaExceeded === true) {\n\t\treturn true\n\t}\n\tif (error instanceof Error && /quota/i.test(error.message)) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunction rejectWithIdbError(\n\treject: (reason: unknown) => void,\n\tmessage: string,\n\tcontext: Record<string, unknown>,\n\tevent: Event,\n): void {\n\tconst domError = (event.target as IDBRequest | IDBTransaction | undefined)?.error\n\tif (domError && isIndexedDbQuotaError(domError)) {\n\t\treject(\n\t\t\tnew PersistenceError(message, {\n\t\t\t\t...context,\n\t\t\t\tquotaExceeded: true,\n\t\t\t\tcause: domError.message,\n\t\t\t}),\n\t\t)\n\t\treturn\n\t}\n\treject(new PersistenceError(message, context))\n}\n\n/**\n * Open the IndexedDB database used for SQLite persistence.\n * Creates the object store on first access.\n */\nfunction openIdb(): Promise<IDBDatabase> {\n\treturn new Promise<IDBDatabase>((resolve, reject) => {\n\t\tconst request = indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION)\n\t\trequest.onupgradeneeded = () => {\n\t\t\tconst db = request.result\n\t\t\tif (!db.objectStoreNames.contains(IDB_STORE_NAME)) {\n\t\t\t\tdb.createObjectStore(IDB_STORE_NAME)\n\t\t\t}\n\t\t}\n\t\trequest.onsuccess = () => resolve(request.result)\n\t\trequest.onerror = () =>\n\t\t\treject(\n\t\t\t\tnew PersistenceError(\n\t\t\t\t\t`Failed to open IndexedDB: ${request.error?.message ?? 'unknown error'}`,\n\t\t\t\t\t{ database: IDB_DATABASE_NAME },\n\t\t\t\t),\n\t\t\t)\n\t})\n}\n\n/**\n * Save a serialized SQLite database to IndexedDB.\n *\n * @param dbName - Key under which to store the data\n * @param data - Serialized database as Uint8Array\n */\nexport async function saveToIndexedDB(dbName: string, data: Uint8Array): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.put(data, dbName)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = (event) =>\n\t\t\t\trejectWithIdbError(\n\t\t\t\t\treject,\n\t\t\t\t\t`Failed to save database \"${dbName}\" to IndexedDB`,\n\t\t\t\t\t{ dbName },\n\t\t\t\t\tevent,\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Save a logical SQL dump payload to IndexedDB for import-fallback restore.\n */\nexport async function saveDumpToIndexedDB(dbName: string, dump: unknown): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.put(dump, `${dbName}${DUMP_SUFFIX}`)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = (event) =>\n\t\t\t\trejectWithIdbError(\n\t\t\t\t\treject,\n\t\t\t\t\t`Failed to save dump for database \"${dbName}\"`,\n\t\t\t\t\t{ dbName },\n\t\t\t\t\tevent,\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Load a serialized SQLite database from IndexedDB.\n *\n * @param dbName - Key under which the data was stored\n * @returns The serialized database, or null if not found\n */\nexport async function loadFromIndexedDB(dbName: string): Promise<Uint8Array | null> {\n\tconst idb = await openIdb()\n\ttry {\n\t\treturn await new Promise<Uint8Array | null>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tconst request = store.get(dbName)\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tconst result = request.result\n\t\t\t\tif (result instanceof Uint8Array) {\n\t\t\t\t\tresolve(result)\n\t\t\t\t} else if (result) {\n\t\t\t\t\t// Handle ArrayBuffer or other typed array forms\n\t\t\t\t\tresolve(new Uint8Array(result as ArrayBuffer))\n\t\t\t\t} else {\n\t\t\t\t\tresolve(null)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequest.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to load database \"${dbName}\" from IndexedDB`, { dbName }),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Load a logical SQL dump payload from IndexedDB.\n */\nexport async function loadDumpFromIndexedDB<T>(dbName: string): Promise<T | null> {\n\tconst idb = await openIdb()\n\ttry {\n\t\treturn await new Promise<T | null>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readonly')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tconst request = store.get(`${dbName}${DUMP_SUFFIX}`)\n\t\t\trequest.onsuccess = () => {\n\t\t\t\tresolve((request.result as T | undefined) ?? null)\n\t\t\t}\n\t\t\trequest.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to load dump for database \"${dbName}\" from IndexedDB`, {\n\t\t\t\t\t\tdbName,\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n\n/**\n * Delete a serialized SQLite database from IndexedDB.\n *\n * @param dbName - Key to delete\n */\nexport async function deleteFromIndexedDB(dbName: string): Promise<void> {\n\tconst idb = await openIdb()\n\ttry {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst tx = idb.transaction(IDB_STORE_NAME, 'readwrite')\n\t\t\tconst store = tx.objectStore(IDB_STORE_NAME)\n\t\t\tstore.delete(dbName)\n\t\t\tstore.delete(`${dbName}${DUMP_SUFFIX}`)\n\t\t\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to delete database \"${dbName}\" from IndexedDB`, { dbName }),\n\t\t\t\t)\n\t\t})\n\t} finally {\n\t\tidb.close()\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/sqlite-wasm-worker-core.ts","../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Reusable SQLite WASM core, decoupled from any specific worker global.\n *\n * The dedicated worker ({@link file://./sqlite-wasm-worker.ts}) wires one core to\n * `self.onmessage`/`self.postMessage`. The SharedWorker host\n * ({@link file://./sqlite-wasm-shared-host.ts}) runs one core PER database inside\n * a single SharedWorker scope, because a SharedWorker cannot spawn a nested\n * `Worker` (the `Worker` constructor is not defined in `SharedWorkerGlobalScope`\n * in Chromium), so it must run SQLite directly.\n *\n * The WASM module and the OPFS SyncAccessHandle pool are cached at module scope\n * and shared across every core in the same worker/SharedWorker: the pool holds\n * multiple database files keyed by filename, so installing it once and opening\n * each database within it avoids the single-writer conflict that installing the\n * pool twice in one scope would cause.\n *\n * This file cannot be unit-tested in Node (no WASM/OPFS); it is exercised through\n * the browser E2E suite and its dispatch is covered via the shared-host tests.\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ninterface SqliteDb {\n\texec(opts: {\n\t\tsql: string\n\t\tbind?: unknown[]\n\t\treturnValue?: string\n\t\trowMode?: string\n\t\tcallback?: (row: Record<string, unknown>) => void\n\t}): void\n\tclose(): void\n\tdeserialize?: (data: Uint8Array) => void\n}\n\ninterface OpfsPool {\n\tOpfsSAHPoolDb: new (filename: string) => SqliteDb\n}\n\ninterface Sqlite3Api {\n\too1: { DB: new (opts: { filename: string }) => SqliteDb }\n\tinstallOpfsSAHPoolVfs?: (opts: { name: string }) => Promise<OpfsPool>\n\tcapi?: { sqlite3_deserialize?: unknown }\n}\n\n/** OPFS SAH pool name. Shared across all databases on the origin (the pool holds\n * multiple db files keyed by filename), so it must NOT be namespaced per database\n * or existing persisted data would be orphaned. */\nconst OPFS_POOL_NAME = 'kora-opfs'\n\n/** Headless browsers and some profiles hang on OPFS VFS install; fall back to memory. */\nconst OPFS_INIT_TIMEOUT_MS = 10_000\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined\n\ttry {\n\t\treturn await Promise.race([\n\t\t\tpromise,\n\t\t\tnew Promise<T>((_, reject) => {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t)\n\t\t\t}),\n\t\t])\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer)\n\t}\n}\n\nfunction opfsDatabaseFilename(dbName: string): string {\n\tconst base = dbName.replace(/[^a-zA-Z0-9._-]/g, '_')\n\treturn base.endsWith('.db') ? base : `${base}.db`\n}\n\n/**\n * Classify why the OPFS SyncAccessHandle pool could not be installed, so the\n * fallback to in-memory is explainable rather than silent.\n */\nfunction classifyOpfsFailure(error: unknown): 'lock-conflict' | 'timeout' | 'unsupported' {\n\tconst message = error instanceof Error ? error.message : String(error)\n\tif (/timed out|timeout/i.test(message)) {\n\t\treturn 'timeout'\n\t}\n\tif (\n\t\t/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(\n\t\t\tmessage,\n\t\t)\n\t) {\n\t\treturn 'lock-conflict'\n\t}\n\treturn 'unsupported'\n}\n\n// Scope-global caches shared by every core in this worker/SharedWorker.\nlet sqlite3Promise: Promise<Sqlite3Api> | null = null\nlet opfsPoolPromise: Promise<OpfsPool | null> | null = null\nlet opfsFallbackReason: 'lock-conflict' | 'timeout' | 'unsupported' | undefined\n\nasync function loadSqlite3(): Promise<Sqlite3Api> {\n\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\t// In production builds, Vite hashes asset filenames (e.g. sqlite3-[hash].wasm).\n\t// The sqlite3 module's default locateFile resolves the unhashed name, causing a\n\t// 404. The worker/host entry sets __KORA_SQLITE_WASM_URL via a `?url` import so\n\t// we can override locateFile with the correct hashed URL.\n\tconst wasmUrl = (globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL as\n\t\t| string\n\t\t| undefined\n\tconst initOptions = wasmUrl\n\t\t? {\n\t\t\t\tlocateFile: (file: string): string => (file.endsWith('.wasm') ? wasmUrl : file),\n\t\t\t}\n\t\t: undefined\n\tconst initFn = sqlite3InitModule as unknown as (\n\t\topts?: Record<string, unknown>,\n\t) => Promise<unknown>\n\treturn (await withTimeout(initFn(initOptions), 60_000, 'SQLite3 module init')) as Sqlite3Api\n}\n\nfunction getSqlite3(): Promise<Sqlite3Api> {\n\tif (!sqlite3Promise) {\n\t\tsqlite3Promise = loadSqlite3()\n\t}\n\treturn sqlite3Promise\n}\n\nasync function installOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!sqlite3.installOpfsSAHPoolVfs) {\n\t\topfsFallbackReason = 'unsupported'\n\t\treturn null\n\t}\n\ttry {\n\t\treturn await withTimeout(\n\t\t\tsqlite3.installOpfsSAHPoolVfs({ name: OPFS_POOL_NAME }),\n\t\t\tOPFS_INIT_TIMEOUT_MS,\n\t\t\t'OPFS VFS install',\n\t\t)\n\t} catch (error) {\n\t\topfsFallbackReason = classifyOpfsFailure(error)\n\t\treturn null\n\t}\n}\n\nfunction getOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!opfsPoolPromise) {\n\t\topfsPoolPromise = installOpfsPool(sqlite3)\n\t}\n\treturn opfsPoolPromise\n}\n\n/** Handle to a single database, dispatching the worker protocol for it. */\nexport interface SqliteWasmCore {\n\thandle(request: WorkerRequest): Promise<WorkerResponse>\n}\n\n/**\n * Creates a core bound to one database. `handle` resolves with the response for\n * each request; the caller routes it back to whichever transport delivered the\n * request (the dedicated worker's `postMessage`, or a SharedWorker client port).\n */\nexport function createSqliteWasmCore(): SqliteWasmCore {\n\tlet db: SqliteDb | null = null\n\tlet sqlite3Api: Sqlite3Api | null = null\n\tlet persistent = false\n\n\tasync function open(\n\t\tid: number,\n\t\tddlStatements: string[],\n\t\tdbName?: string,\n\t): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\t// A shared core (one per database in a SharedWorker) may receive `open`\n\t\t\t// from more than one tab. Re-run the idempotent DDL on the existing handle\n\t\t\t// rather than opening the database a second time.\n\t\t\tif (db) {\n\t\t\t\tapplyDdl(db, ddlStatements)\n\t\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t\t}\n\n\t\t\tconst sqlite3 = await getSqlite3()\n\t\t\tsqlite3Api = sqlite3\n\t\t\tconst pool = await getOpfsPool(sqlite3)\n\n\t\t\tif (pool) {\n\t\t\t\tdb = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? 'kora-db'))\n\t\t\t\tpersistent = true\n\t\t\t} else {\n\t\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t\t\tpersistent = false\n\t\t\t}\n\n\t\t\tdb.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\t\tdb.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t\tapplyDdl(db, ddlStatements)\n\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'INIT_ERROR' }\n\t\t}\n\t}\n\n\tfunction buildOpenData(): { persistent: boolean; fallbackReason?: string } {\n\t\treturn {\n\t\t\tpersistent,\n\t\t\t...(persistent ? {} : { fallbackReason: opfsFallbackReason ?? 'unsupported' }),\n\t\t}\n\t}\n\n\tfunction applyDdl(target: SqliteDb, ddlStatements: string[]): void {\n\t\tfor (const sql of ddlStatements) {\n\t\t\tif (sql.startsWith('--kora:safe-alter')) {\n\t\t\t\ttry {\n\t\t\t\t\ttarget.exec({ sql: sql.replace('--kora:safe-alter\\n', '') })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = (error as Error).message || ''\n\t\t\t\t\tif (!msg.includes('duplicate column name')) {\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttarget.exec({ sql })\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction execute(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tdb.exec({ sql, bind: params })\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' }\n\t\t}\n\t}\n\n\tfunction query(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tdb.exec({\n\t\t\t\tsql,\n\t\t\t\tbind: params,\n\t\t\t\trowMode: 'object',\n\t\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\t\trows.push({ ...row })\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn { id, type: 'success', data: rows }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' }\n\t\t}\n\t}\n\n\tfunction close(id: number): WorkerResponse {\n\t\tif (db) {\n\t\t\tdb.close()\n\t\t\tdb = null\n\t\t}\n\t\treturn { id, type: 'success' }\n\t}\n\n\tfunction migrate(id: number, statements: string[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tfor (const sql of statements) {\n\t\t\t\tdb.exec({ sql })\n\t\t\t}\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'MIGRATE_ERROR' }\n\t\t}\n\t}\n\n\tfunction importData(id: number, data: Uint8Array): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\t\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\t\ttry {\n\t\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\t\treturn { id, type: 'success' }\n\t\t\t} catch (error) {\n\t\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' }\n\t\t\t}\n\t\t}\n\t\tif (!sqlite3Api || typeof sqlite3Api.capi?.sqlite3_deserialize === 'undefined') {\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage:\n\t\t\t\t'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t}\n\t}\n\n\tasync function handle(request: WorkerRequest): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\tswitch (request.type) {\n\t\t\t\tcase 'open':\n\t\t\t\t\treturn await open(request.id, request.ddlStatements, request.dbName)\n\t\t\t\tcase 'close':\n\t\t\t\t\treturn close(request.id)\n\t\t\t\tcase 'execute':\n\t\t\t\t\treturn execute(request.id, request.sql, request.params)\n\t\t\t\tcase 'query':\n\t\t\t\t\treturn query(request.id, request.sql, request.params)\n\t\t\t\tcase 'begin':\n\t\t\t\t\treturn execute(request.id, 'BEGIN')\n\t\t\t\tcase 'commit':\n\t\t\t\t\treturn execute(request.id, 'COMMIT')\n\t\t\t\tcase 'rollback':\n\t\t\t\t\treturn execute(request.id, 'ROLLBACK')\n\t\t\t\tcase 'migrate':\n\t\t\t\t\treturn migrate(request.id, request.statements)\n\t\t\t\tcase 'import':\n\t\t\t\t\treturn importData(request.id, request.data)\n\t\t\t\tcase 'export':\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: (error as Error).message,\n\t\t\t\tcode: 'WORKER_ERROR',\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { handle }\n}\n","/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM in a dedicated worker.\n *\n * This file is intended to run inside a Web Worker in browsers. It wires one\n * {@link createSqliteWasmCore} to the worker's message channel. The database\n * logic lives in `sqlite-wasm-worker-core.ts` so it can be reused by the\n * SharedWorker host, which cannot spawn a nested worker.\n *\n * This script cannot be tested in Node.js; it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest } from './sqlite-wasm-channel'\nimport { createSqliteWasmCore } from './sqlite-wasm-worker-core'\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nconst core = createSqliteWasmCore()\n\nself.onmessage = (event: MessageEvent<WorkerRequest>): void => {\n\tvoid core.handle(event.data).then((response) => {\n\t\tself.postMessage(response)\n\t})\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/sqlite-wasm-worker-core.ts","../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Reusable SQLite WASM core, decoupled from any specific worker global.\n *\n * The dedicated worker ({@link file://./sqlite-wasm-worker.ts}) wires one core to\n * `self.onmessage`/`self.postMessage`.\n *\n * The WASM module and the OPFS SyncAccessHandle pool are cached at module scope\n * and shared across every core in the same worker: the pool holds\n * multiple database files keyed by filename, so installing it once and opening\n * each database within it avoids the single-writer conflict that installing the\n * pool twice in one scope would cause.\n *\n * This file cannot be unit-tested in Node (no WASM/OPFS); it is exercised through\n * the browser E2E suite.\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ninterface SqliteDb {\n\texec(opts: {\n\t\tsql: string\n\t\tbind?: unknown[]\n\t\treturnValue?: string\n\t\trowMode?: string\n\t\tcallback?: (row: Record<string, unknown>) => void\n\t}): void\n\tclose(): void\n\tdeserialize?: (data: Uint8Array) => void\n}\n\ninterface OpfsPool {\n\tOpfsSAHPoolDb: new (filename: string) => SqliteDb\n}\n\ninterface Sqlite3Api {\n\too1: { DB: new (opts: { filename: string }) => SqliteDb }\n\tinstallOpfsSAHPoolVfs?: (opts: { name: string }) => Promise<OpfsPool>\n\tcapi?: { sqlite3_deserialize?: unknown }\n}\n\n/** OPFS SAH pool name. Shared across all databases on the origin (the pool holds\n * multiple db files keyed by filename), so it must NOT be namespaced per database\n * or existing persisted data would be orphaned. */\nconst OPFS_POOL_NAME = 'kora-opfs'\n\n/** Headless browsers and some profiles hang on OPFS VFS install; fall back to memory. */\nconst OPFS_INIT_TIMEOUT_MS = 10_000\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined\n\ttry {\n\t\treturn await Promise.race([\n\t\t\tpromise,\n\t\t\tnew Promise<T>((_, reject) => {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t)\n\t\t\t}),\n\t\t])\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer)\n\t}\n}\n\nfunction opfsDatabaseFilename(dbName: string): string {\n\tconst base = dbName.replace(/[^a-zA-Z0-9._-]/g, '_')\n\treturn base.endsWith('.db') ? base : `${base}.db`\n}\n\n/**\n * Classify why the OPFS SyncAccessHandle pool could not be installed, so the\n * fallback to in-memory is explainable rather than silent.\n */\nfunction classifyOpfsFailure(error: unknown): 'lock-conflict' | 'timeout' | 'unsupported' {\n\tconst message = error instanceof Error ? error.message : String(error)\n\tif (/timed out|timeout/i.test(message)) {\n\t\treturn 'timeout'\n\t}\n\tif (\n\t\t/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(\n\t\t\tmessage,\n\t\t)\n\t) {\n\t\treturn 'lock-conflict'\n\t}\n\treturn 'unsupported'\n}\n\n// Scope-global caches shared by every core in this worker.\nlet sqlite3Promise: Promise<Sqlite3Api> | null = null\nlet opfsPoolPromise: Promise<OpfsPool | null> | null = null\nlet opfsFallbackReason: 'lock-conflict' | 'timeout' | 'unsupported' | undefined\n\nasync function loadSqlite3(): Promise<Sqlite3Api> {\n\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\t// In production builds, Vite hashes asset filenames (e.g. sqlite3-[hash].wasm).\n\t// The sqlite3 module's default locateFile resolves the unhashed name, causing a\n\t// 404. The worker/host entry sets __KORA_SQLITE_WASM_URL via a `?url` import so\n\t// we can override locateFile with the correct hashed URL.\n\tconst wasmUrl = (globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL as\n\t\t| string\n\t\t| undefined\n\tconst initOptions = wasmUrl\n\t\t? {\n\t\t\t\tlocateFile: (file: string): string => (file.endsWith('.wasm') ? wasmUrl : file),\n\t\t\t}\n\t\t: undefined\n\tconst initFn = sqlite3InitModule as unknown as (\n\t\topts?: Record<string, unknown>,\n\t) => Promise<unknown>\n\treturn (await withTimeout(initFn(initOptions), 60_000, 'SQLite3 module init')) as Sqlite3Api\n}\n\nfunction getSqlite3(): Promise<Sqlite3Api> {\n\tif (!sqlite3Promise) {\n\t\tsqlite3Promise = loadSqlite3()\n\t}\n\treturn sqlite3Promise\n}\n\nasync function installOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!sqlite3.installOpfsSAHPoolVfs) {\n\t\topfsFallbackReason = 'unsupported'\n\t\treturn null\n\t}\n\ttry {\n\t\treturn await withTimeout(\n\t\t\tsqlite3.installOpfsSAHPoolVfs({ name: OPFS_POOL_NAME }),\n\t\t\tOPFS_INIT_TIMEOUT_MS,\n\t\t\t'OPFS VFS install',\n\t\t)\n\t} catch (error) {\n\t\topfsFallbackReason = classifyOpfsFailure(error)\n\t\treturn null\n\t}\n}\n\nfunction getOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!opfsPoolPromise) {\n\t\topfsPoolPromise = installOpfsPool(sqlite3)\n\t}\n\treturn opfsPoolPromise\n}\n\n/** Handle to a single database, dispatching the worker protocol for it. */\nexport interface SqliteWasmCore {\n\thandle(request: WorkerRequest): Promise<WorkerResponse>\n}\n\n/**\n * Creates a core bound to one database. `handle` resolves with the response for\n * each request; the caller routes it back through the dedicated worker's\n * `postMessage`.\n */\nexport function createSqliteWasmCore(): SqliteWasmCore {\n\tlet db: SqliteDb | null = null\n\tlet sqlite3Api: Sqlite3Api | null = null\n\tlet persistent = false\n\n\tasync function open(\n\t\tid: number,\n\t\tddlStatements: string[],\n\t\tdbName?: string,\n\t): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\t// Re-run idempotent DDL on the existing handle rather than opening the\n\t\t\t// database a second time.\n\t\t\tif (db) {\n\t\t\t\tapplyDdl(db, ddlStatements)\n\t\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t\t}\n\n\t\t\tconst sqlite3 = await getSqlite3()\n\t\t\tsqlite3Api = sqlite3\n\t\t\tconst pool = await getOpfsPool(sqlite3)\n\n\t\t\tif (pool) {\n\t\t\t\tdb = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? 'kora-db'))\n\t\t\t\tpersistent = true\n\t\t\t} else {\n\t\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t\t\tpersistent = false\n\t\t\t}\n\n\t\t\tdb.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\t\tdb.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t\tapplyDdl(db, ddlStatements)\n\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'INIT_ERROR' }\n\t\t}\n\t}\n\n\tfunction buildOpenData(): { persistent: boolean; fallbackReason?: string } {\n\t\treturn {\n\t\t\tpersistent,\n\t\t\t...(persistent ? {} : { fallbackReason: opfsFallbackReason ?? 'unsupported' }),\n\t\t}\n\t}\n\n\tfunction applyDdl(target: SqliteDb, ddlStatements: string[]): void {\n\t\tfor (const sql of ddlStatements) {\n\t\t\tif (sql.startsWith('--kora:safe-alter')) {\n\t\t\t\ttry {\n\t\t\t\t\ttarget.exec({ sql: sql.replace('--kora:safe-alter\\n', '') })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = (error as Error).message || ''\n\t\t\t\t\tif (!msg.includes('duplicate column name')) {\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttarget.exec({ sql })\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction execute(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tdb.exec({ sql, bind: params })\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' }\n\t\t}\n\t}\n\n\tfunction query(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tdb.exec({\n\t\t\t\tsql,\n\t\t\t\tbind: params,\n\t\t\t\trowMode: 'object',\n\t\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\t\trows.push({ ...row })\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn { id, type: 'success', data: rows }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' }\n\t\t}\n\t}\n\n\tfunction close(id: number): WorkerResponse {\n\t\tif (db) {\n\t\t\tdb.close()\n\t\t\tdb = null\n\t\t}\n\t\treturn { id, type: 'success' }\n\t}\n\n\tfunction migrate(id: number, statements: string[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tfor (const sql of statements) {\n\t\t\t\tdb.exec({ sql })\n\t\t\t}\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'MIGRATE_ERROR' }\n\t\t}\n\t}\n\n\tfunction importData(id: number, data: Uint8Array): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\t\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\t\ttry {\n\t\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\t\treturn { id, type: 'success' }\n\t\t\t} catch (error) {\n\t\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' }\n\t\t\t}\n\t\t}\n\t\tif (!sqlite3Api || typeof sqlite3Api.capi?.sqlite3_deserialize === 'undefined') {\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage:\n\t\t\t\t'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t}\n\t}\n\n\tasync function handle(request: WorkerRequest): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\tswitch (request.type) {\n\t\t\t\tcase 'open':\n\t\t\t\t\treturn await open(request.id, request.ddlStatements, request.dbName)\n\t\t\t\tcase 'close':\n\t\t\t\t\treturn close(request.id)\n\t\t\t\tcase 'execute':\n\t\t\t\t\treturn execute(request.id, request.sql, request.params)\n\t\t\t\tcase 'query':\n\t\t\t\t\treturn query(request.id, request.sql, request.params)\n\t\t\t\tcase 'begin':\n\t\t\t\t\treturn execute(request.id, 'BEGIN')\n\t\t\t\tcase 'commit':\n\t\t\t\t\treturn execute(request.id, 'COMMIT')\n\t\t\t\tcase 'rollback':\n\t\t\t\t\treturn execute(request.id, 'ROLLBACK')\n\t\t\t\tcase 'migrate':\n\t\t\t\t\treturn migrate(request.id, request.statements)\n\t\t\t\tcase 'import':\n\t\t\t\t\treturn importData(request.id, request.data)\n\t\t\t\tcase 'export':\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: (error as Error).message,\n\t\t\t\tcode: 'WORKER_ERROR',\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { handle }\n}\n","/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM in a dedicated worker.\n *\n * This file is intended to run inside a Web Worker in browsers. It wires one\n * {@link createSqliteWasmCore} to the worker's message channel.\n *\n * This script cannot be tested in Node.js; it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest } from './sqlite-wasm-channel'\nimport { createSqliteWasmCore } from './sqlite-wasm-worker-core'\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nconst core = createSqliteWasmCore()\n\nself.onmessage = (event: MessageEvent<WorkerRequest>): void => {\n\tvoid core.handle(event.data).then((response) => {\n\t\tself.postMessage(response)\n\t})\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAM,iBAAiB;AAGvB,IAAM,uBAAuB;AAE7B,eAAe,YAAe,SAAqB,WAAmB,OAA2B;AAChG,MAAI;AACJ,MAAI;AACH,WAAO,MAAM,QAAQ,KAAK;AAAA,MACzB;AAAA,MACA,IAAI,QAAW,CAAC,GAAG,WAAW;AAC7B,gBAAQ;AAAA,UACP,MAAM,OAAO,IAAI,MAAM,GAAG,KAAK,oBAAoB,SAAS,IAAI,CAAC;AAAA,UACjE;AAAA,QACD;AAAA,MACD,CAAC;AAAA,IACF,CAAC;AAAA,EACF,UAAE;AACD,QAAI,UAAU,OAAW,cAAa,KAAK;AAAA,EAC5C;AACD;AAEA,SAAS,qBAAqB,QAAwB;AACrD,QAAM,OAAO,OAAO,QAAQ,oBAAoB,GAAG;AACnD,SAAO,KAAK,SAAS,KAAK,IAAI,OAAO,GAAG,IAAI;AAC7C;AAMA,SAAS,oBAAoB,OAA6D;AACzF,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,MAAI,qBAAqB,KAAK,OAAO,GAAG;AACvC,WAAO;AAAA,EACR;AACA,MACC,2FAA2F;AAAA,IAC1F;AAAA,EACD,GACC;AACD,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAGA,IAAI,iBAA6C;AACjD,IAAI,kBAAmD;AACvD,IAAI;AAEJ,eAAe,cAAmC;AACjD,QAAM,qBAAqB,MAAM,OAAO,yBAAyB,GAAG;AAKpE,QAAM,UAAW,WAAuC;AAGxD,QAAM,cAAc,UACjB;AAAA,IACA,YAAY,CAAC,SAA0B,KAAK,SAAS,OAAO,IAAI,UAAU;AAAA,EAC3E,IACC;AACH,QAAM,SAAS;AAGf,SAAQ,MAAM,YAAY,OAAO,WAAW,GAAG,KAAQ,qBAAqB;AAC7E;AAEA,SAAS,aAAkC;AAC1C,MAAI,CAAC,gBAAgB;AACpB,qBAAiB,YAAY;AAAA,EAC9B;AACA,SAAO;AACR;AAEA,eAAe,gBAAgB,SAA+C;AAC7E,MAAI,CAAC,QAAQ,uBAAuB;AACnC,yBAAqB;AACrB,WAAO;AAAA,EACR;AACA,MAAI;AACH,WAAO,MAAM;AAAA,MACZ,QAAQ,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAAA,MACtD;AAAA,MACA;AAAA,IACD;AAAA,EACD,SAAS,OAAO;AACf,yBAAqB,oBAAoB,KAAK;AAC9C,WAAO;AAAA,EACR;AACD;AAEA,SAAS,YAAY,SAA+C;AACnE,MAAI,CAAC,iBAAiB;AACrB,sBAAkB,gBAAgB,OAAO;AAAA,EAC1C;AACA,SAAO;AACR;AAYO,SAAS,uBAAuC;AACtD,MAAI,KAAsB;AAC1B,MAAI,aAAgC;AACpC,MAAI,aAAa;AAEjB,iBAAe,KACd,IACA,eACA,QAC0B;AAC1B,QAAI;AAGH,UAAI,IAAI;AACP,iBAAS,IAAI,aAAa;AAC1B,eAAO,EAAE,IAAI,MAAM,WAAW,MAAM,cAAc,EAAE;AAAA,MACrD;AAEA,YAAM,UAAU,MAAM,WAAW;AACjC,mBAAa;AACb,YAAM,OAAO,MAAM,YAAY,OAAO;AAEtC,UAAI,MAAM;AACT,aAAK,IAAI,KAAK,cAAc,qBAAqB,UAAU,SAAS,CAAC;AACrE,qBAAa;AAAA,MACd,OAAO;AACN,aAAK,IAAI,QAAQ,IAAI,GAAG,EAAE,UAAU,WAAW,CAAC;AAChD,qBAAa;AAAA,MACd;AAEA,SAAG,KAAK,EAAE,KAAK,4BAA4B,CAAC;AAC5C,SAAG,KAAK,EAAE,KAAK,2BAA2B,CAAC;AAE3C,eAAS,IAAI,aAAa;AAC1B,aAAO,EAAE,IAAI,MAAM,WAAW,MAAM,cAAc,EAAE;AAAA,IACrD,SAAS,OAAO;AACf,aAAO,EAAE,IAAI,MAAM,SAAS,SAAU,MAAgB,SAAS,MAAM,aAAa;AAAA,IACnF;AAAA,EACD;AAEA,WAAS,gBAAkE;AAC1E,WAAO;AAAA,MACN;AAAA,MACA,GAAI,aAAa,CAAC,IAAI,EAAE,gBAAgB,sBAAsB,cAAc;AAAA,IAC7E;AAAA,EACD;AAEA,WAAS,SAAS,QAAkB,eAA+B;AAClE,eAAW,OAAO,eAAe;AAChC,UAAI,IAAI,WAAW,mBAAmB,GAAG;AACxC,YAAI;AACH,iBAAO,KAAK,EAAE,KAAK,IAAI,QAAQ,uBAAuB,EAAE,EAAE,CAAC;AAAA,QAC5D,SAAS,OAAO;AACf,gBAAM,MAAO,MAAgB,WAAW;AACxC,cAAI,CAAC,IAAI,SAAS,uBAAuB,GAAG;AAC3C,kBAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD,OAAO;AACN,eAAO,KAAK,EAAE,IAAI,CAAC;AAAA,MACpB;AAAA,IACD;AAAA,EACD;AAEA,WAAS,QAAQ,IAAY,KAAa,QAAoC;AAC7E,QAAI,CAAC,IAAI;AACR,aAAO,EAAE,IAAI,MAAM,SAAS,SAAS,wBAAwB,MAAM,cAAc;AAAA,IAClF;AACA,QAAI;AACH,SAAG,KAAK,EAAE,KAAK,MAAM,OAAO,CAAC;AAC7B,aAAO,EAAE,IAAI,MAAM,UAAU;AAAA,IAC9B,SAAS,OAAO;AACf,aAAO,EAAE,IAAI,MAAM,SAAS,SAAU,MAAgB,SAAS,MAAM,aAAa;AAAA,IACnF;AAAA,EACD;AAEA,WAAS,MAAM,IAAY,KAAa,QAAoC;AAC3E,QAAI,CAAC,IAAI;AACR,aAAO,EAAE,IAAI,MAAM,SAAS,SAAS,wBAAwB,MAAM,cAAc;AAAA,IAClF;AACA,QAAI;AACH,YAAM,OAAkC,CAAC;AACzC,SAAG,KAAK;AAAA,QACP;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,QACT,UAAU,CAAC,QAAiC;AAC3C,eAAK,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,QACrB;AAAA,MACD,CAAC;AACD,aAAO,EAAE,IAAI,MAAM,WAAW,MAAM,KAAK;AAAA,IAC1C,SAAS,OAAO;AACf,aAAO,EAAE,IAAI,MAAM,SAAS,SAAU,MAAgB,SAAS,MAAM,cAAc;AAAA,IACpF;AAAA,EACD;AAEA,WAAS,MAAM,IAA4B;AAC1C,QAAI,IAAI;AACP,SAAG,MAAM;AACT,WAAK;AAAA,IACN;AACA,WAAO,EAAE,IAAI,MAAM,UAAU;AAAA,EAC9B;AAEA,WAAS,QAAQ,IAAY,YAAsC;AAClE,QAAI,CAAC,IAAI;AACR,aAAO,EAAE,IAAI,MAAM,SAAS,SAAS,wBAAwB,MAAM,cAAc;AAAA,IAClF;AACA,QAAI;AACH,iBAAW,OAAO,YAAY;AAC7B,WAAG,KAAK,EAAE,IAAI,CAAC;AAAA,MAChB;AACA,aAAO,EAAE,IAAI,MAAM,UAAU;AAAA,IAC9B,SAAS,OAAO;AACf,aAAO,EAAE,IAAI,MAAM,SAAS,SAAU,MAAgB,SAAS,MAAM,gBAAgB;AAAA,IACtF;AAAA,EACD;AAEA,WAAS,WAAW,IAAY,MAAkC;AACjE,QAAI,CAAC,IAAI;AACR,aAAO,EAAE,IAAI,MAAM,SAAS,SAAS,wBAAwB,MAAM,cAAc;AAAA,IAClF;AACA,UAAM,oBAAoB;AAC1B,QAAI,OAAO,kBAAkB,gBAAgB,YAAY;AACxD,UAAI;AACH,0BAAkB,YAAY,IAAI;AAClC,eAAO,EAAE,IAAI,MAAM,UAAU;AAAA,MAC9B,SAAS,OAAO;AACf,eAAO,EAAE,IAAI,MAAM,SAAS,SAAU,MAAgB,SAAS,MAAM,eAAe;AAAA,MACrF;AAAA,IACD;AACA,QAAI,CAAC,cAAc,OAAO,WAAW,MAAM,wBAAwB,aAAa;AAC/E,aAAO;AAAA,QACN;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,MACP;AAAA,IACD;AACA,WAAO;AAAA,MACN;AAAA,MACA,MAAM;AAAA,MACN,SACC;AAAA,MACD,MAAM;AAAA,IACP;AAAA,EACD;AAEA,iBAAe,OAAO,SAAiD;AACtE,QAAI;AACH,cAAQ,QAAQ,MAAM;AAAA,QACrB,KAAK;AACJ,iBAAO,MAAM,KAAK,QAAQ,IAAI,QAAQ,eAAe,QAAQ,MAAM;AAAA,QACpE,KAAK;AACJ,iBAAO,MAAM,QAAQ,EAAE;AAAA,QACxB,KAAK;AACJ,iBAAO,QAAQ,QAAQ,IAAI,QAAQ,KAAK,QAAQ,MAAM;AAAA,QACvD,KAAK;AACJ,iBAAO,MAAM,QAAQ,IAAI,QAAQ,KAAK,QAAQ,MAAM;AAAA,QACrD,KAAK;AACJ,iBAAO,QAAQ,QAAQ,IAAI,OAAO;AAAA,QACnC,KAAK;AACJ,iBAAO,QAAQ,QAAQ,IAAI,QAAQ;AAAA,QACpC,KAAK;AACJ,iBAAO,QAAQ,QAAQ,IAAI,UAAU;AAAA,QACtC,KAAK;AACJ,iBAAO,QAAQ,QAAQ,IAAI,QAAQ,UAAU;AAAA,QAC9C,KAAK;AACJ,iBAAO,WAAW,QAAQ,IAAI,QAAQ,IAAI;AAAA,QAC3C,KAAK;AACJ,iBAAO;AAAA,YACN,IAAI,QAAQ;AAAA,YACZ,MAAM;AAAA,YACN,SAAS;AAAA,YACT,MAAM;AAAA,UACP;AAAA,QACD;AACC,iBAAO;AAAA,YACN,IAAK,QAA0B;AAAA,YAC/B,MAAM;AAAA,YACN,SAAS;AAAA,YACT,MAAM;AAAA,UACP;AAAA,MACF;AAAA,IACD,SAAS,OAAO;AACf,aAAO;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,MAAM;AAAA,QACN,SAAU,MAAgB;AAAA,QAC1B,MAAM;AAAA,MACP;AAAA,IACD;AAAA,EACD;AAEA,SAAO,EAAE,OAAO;AACjB;;;AChVA,IAAM,OAAO,qBAAqB;AAElC,KAAK,YAAY,CAAC,UAA6C;AAC9D,OAAK,KAAK,OAAO,MAAM,IAAI,EAAE,KAAK,CAAC,aAAa;AAC/C,SAAK,YAAY,QAAQ;AAAA,EAC1B,CAAC;AACF;","names":[]}
|
|
@@ -1,6 +1,253 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/adapters/sqlite-wasm-worker-core.ts
|
|
2
|
+
var OPFS_POOL_NAME = "kora-opfs";
|
|
3
|
+
var OPFS_INIT_TIMEOUT_MS = 1e4;
|
|
4
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
5
|
+
let timer;
|
|
6
|
+
try {
|
|
7
|
+
return await Promise.race([
|
|
8
|
+
promise,
|
|
9
|
+
new Promise((_, reject) => {
|
|
10
|
+
timer = setTimeout(
|
|
11
|
+
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
|
|
12
|
+
timeoutMs
|
|
13
|
+
);
|
|
14
|
+
})
|
|
15
|
+
]);
|
|
16
|
+
} finally {
|
|
17
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function opfsDatabaseFilename(dbName) {
|
|
21
|
+
const base = dbName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
22
|
+
return base.endsWith(".db") ? base : `${base}.db`;
|
|
23
|
+
}
|
|
24
|
+
function classifyOpfsFailure(error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
if (/timed out|timeout/i.test(message)) {
|
|
27
|
+
return "timeout";
|
|
28
|
+
}
|
|
29
|
+
if (/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(
|
|
30
|
+
message
|
|
31
|
+
)) {
|
|
32
|
+
return "lock-conflict";
|
|
33
|
+
}
|
|
34
|
+
return "unsupported";
|
|
35
|
+
}
|
|
36
|
+
var sqlite3Promise = null;
|
|
37
|
+
var opfsPoolPromise = null;
|
|
38
|
+
var opfsFallbackReason;
|
|
39
|
+
async function loadSqlite3() {
|
|
40
|
+
const sqlite3InitModule = (await import("@sqlite.org/sqlite-wasm")).default;
|
|
41
|
+
const wasmUrl = globalThis.__KORA_SQLITE_WASM_URL;
|
|
42
|
+
const initOptions = wasmUrl ? {
|
|
43
|
+
locateFile: (file) => file.endsWith(".wasm") ? wasmUrl : file
|
|
44
|
+
} : void 0;
|
|
45
|
+
const initFn = sqlite3InitModule;
|
|
46
|
+
return await withTimeout(initFn(initOptions), 6e4, "SQLite3 module init");
|
|
47
|
+
}
|
|
48
|
+
function getSqlite3() {
|
|
49
|
+
if (!sqlite3Promise) {
|
|
50
|
+
sqlite3Promise = loadSqlite3();
|
|
51
|
+
}
|
|
52
|
+
return sqlite3Promise;
|
|
53
|
+
}
|
|
54
|
+
async function installOpfsPool(sqlite3) {
|
|
55
|
+
if (!sqlite3.installOpfsSAHPoolVfs) {
|
|
56
|
+
opfsFallbackReason = "unsupported";
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return await withTimeout(
|
|
61
|
+
sqlite3.installOpfsSAHPoolVfs({ name: OPFS_POOL_NAME }),
|
|
62
|
+
OPFS_INIT_TIMEOUT_MS,
|
|
63
|
+
"OPFS VFS install"
|
|
64
|
+
);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
opfsFallbackReason = classifyOpfsFailure(error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function getOpfsPool(sqlite3) {
|
|
71
|
+
if (!opfsPoolPromise) {
|
|
72
|
+
opfsPoolPromise = installOpfsPool(sqlite3);
|
|
73
|
+
}
|
|
74
|
+
return opfsPoolPromise;
|
|
75
|
+
}
|
|
76
|
+
function createSqliteWasmCore() {
|
|
77
|
+
let db = null;
|
|
78
|
+
let sqlite3Api = null;
|
|
79
|
+
let persistent = false;
|
|
80
|
+
async function open(id, ddlStatements, dbName) {
|
|
81
|
+
try {
|
|
82
|
+
if (db) {
|
|
83
|
+
applyDdl(db, ddlStatements);
|
|
84
|
+
return { id, type: "success", data: buildOpenData() };
|
|
85
|
+
}
|
|
86
|
+
const sqlite3 = await getSqlite3();
|
|
87
|
+
sqlite3Api = sqlite3;
|
|
88
|
+
const pool = await getOpfsPool(sqlite3);
|
|
89
|
+
if (pool) {
|
|
90
|
+
db = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? "kora-db"));
|
|
91
|
+
persistent = true;
|
|
92
|
+
} else {
|
|
93
|
+
db = new sqlite3.oo1.DB({ filename: ":memory:" });
|
|
94
|
+
persistent = false;
|
|
95
|
+
}
|
|
96
|
+
db.exec({ sql: "PRAGMA journal_mode = WAL" });
|
|
97
|
+
db.exec({ sql: "PRAGMA foreign_keys = ON" });
|
|
98
|
+
applyDdl(db, ddlStatements);
|
|
99
|
+
return { id, type: "success", data: buildOpenData() };
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return { id, type: "error", message: error.message, code: "INIT_ERROR" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function buildOpenData() {
|
|
105
|
+
return {
|
|
106
|
+
persistent,
|
|
107
|
+
...persistent ? {} : { fallbackReason: opfsFallbackReason ?? "unsupported" }
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function applyDdl(target, ddlStatements) {
|
|
111
|
+
for (const sql of ddlStatements) {
|
|
112
|
+
if (sql.startsWith("--kora:safe-alter")) {
|
|
113
|
+
try {
|
|
114
|
+
target.exec({ sql: sql.replace("--kora:safe-alter\n", "") });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const msg = error.message || "";
|
|
117
|
+
if (!msg.includes("duplicate column name")) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
target.exec({ sql });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function execute(id, sql, params) {
|
|
127
|
+
if (!db) {
|
|
128
|
+
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
db.exec({ sql, bind: params });
|
|
132
|
+
return { id, type: "success" };
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return { id, type: "error", message: error.message, code: "EXEC_ERROR" };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function query(id, sql, params) {
|
|
138
|
+
if (!db) {
|
|
139
|
+
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const rows = [];
|
|
143
|
+
db.exec({
|
|
144
|
+
sql,
|
|
145
|
+
bind: params,
|
|
146
|
+
rowMode: "object",
|
|
147
|
+
callback: (row) => {
|
|
148
|
+
rows.push({ ...row });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return { id, type: "success", data: rows };
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return { id, type: "error", message: error.message, code: "QUERY_ERROR" };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function close(id) {
|
|
157
|
+
if (db) {
|
|
158
|
+
db.close();
|
|
159
|
+
db = null;
|
|
160
|
+
}
|
|
161
|
+
return { id, type: "success" };
|
|
162
|
+
}
|
|
163
|
+
function migrate(id, statements) {
|
|
164
|
+
if (!db) {
|
|
165
|
+
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
for (const sql of statements) {
|
|
169
|
+
db.exec({ sql });
|
|
170
|
+
}
|
|
171
|
+
return { id, type: "success" };
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return { id, type: "error", message: error.message, code: "MIGRATE_ERROR" };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function importData(id, data) {
|
|
177
|
+
if (!db) {
|
|
178
|
+
return { id, type: "error", message: "Database is not open", code: "DB_NOT_OPEN" };
|
|
179
|
+
}
|
|
180
|
+
const dbWithDeserialize = db;
|
|
181
|
+
if (typeof dbWithDeserialize.deserialize === "function") {
|
|
182
|
+
try {
|
|
183
|
+
dbWithDeserialize.deserialize(data);
|
|
184
|
+
return { id, type: "success" };
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return { id, type: "error", message: error.message, code: "IMPORT_ERROR" };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!sqlite3Api || typeof sqlite3Api.capi?.sqlite3_deserialize === "undefined") {
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
type: "error",
|
|
193
|
+
message: "Import not supported in this SQLite WASM runtime",
|
|
194
|
+
code: "IMPORT_NOT_SUPPORTED"
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
id,
|
|
199
|
+
type: "error",
|
|
200
|
+
message: "Import requires runtime-specific deserialize wiring and is unavailable in this worker build",
|
|
201
|
+
code: "IMPORT_NOT_SUPPORTED"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function handle(request) {
|
|
205
|
+
try {
|
|
206
|
+
switch (request.type) {
|
|
207
|
+
case "open":
|
|
208
|
+
return await open(request.id, request.ddlStatements, request.dbName);
|
|
209
|
+
case "close":
|
|
210
|
+
return close(request.id);
|
|
211
|
+
case "execute":
|
|
212
|
+
return execute(request.id, request.sql, request.params);
|
|
213
|
+
case "query":
|
|
214
|
+
return query(request.id, request.sql, request.params);
|
|
215
|
+
case "begin":
|
|
216
|
+
return execute(request.id, "BEGIN");
|
|
217
|
+
case "commit":
|
|
218
|
+
return execute(request.id, "COMMIT");
|
|
219
|
+
case "rollback":
|
|
220
|
+
return execute(request.id, "ROLLBACK");
|
|
221
|
+
case "migrate":
|
|
222
|
+
return migrate(request.id, request.statements);
|
|
223
|
+
case "import":
|
|
224
|
+
return importData(request.id, request.data);
|
|
225
|
+
case "export":
|
|
226
|
+
return {
|
|
227
|
+
id: request.id,
|
|
228
|
+
type: "error",
|
|
229
|
+
message: "Export not yet supported in browser worker",
|
|
230
|
+
code: "EXPORT_NOT_SUPPORTED"
|
|
231
|
+
};
|
|
232
|
+
default:
|
|
233
|
+
return {
|
|
234
|
+
id: request.id,
|
|
235
|
+
type: "error",
|
|
236
|
+
message: "Unknown request type",
|
|
237
|
+
code: "UNKNOWN_REQUEST"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
id: request.id,
|
|
243
|
+
type: "error",
|
|
244
|
+
message: error.message,
|
|
245
|
+
code: "WORKER_ERROR"
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { handle };
|
|
250
|
+
}
|
|
4
251
|
|
|
5
252
|
// src/adapters/sqlite-wasm-worker.ts
|
|
6
253
|
var core = createSqliteWasmCore();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM in a dedicated worker.\n *\n * This file is intended to run inside a Web Worker in browsers. It wires one\n * {@link createSqliteWasmCore} to the worker's message channel. The database\n * logic lives in `sqlite-wasm-worker-core.ts` so it can be reused by the\n * SharedWorker host, which cannot spawn a nested worker.\n *\n * This script cannot be tested in Node.js; it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest } from './sqlite-wasm-channel'\nimport { createSqliteWasmCore } from './sqlite-wasm-worker-core'\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nconst core = createSqliteWasmCore()\n\nself.onmessage = (event: MessageEvent<WorkerRequest>): void => {\n\tvoid core.handle(event.data).then((response) => {\n\t\tself.postMessage(response)\n\t})\n}\n"],"mappings":";;;;;AAiBA,IAAM,OAAO,qBAAqB;AAElC,KAAK,YAAY,CAAC,UAA6C;AAC9D,OAAK,KAAK,OAAO,MAAM,IAAI,EAAE,KAAK,CAAC,aAAa;AAC/C,SAAK,YAAY,QAAQ;AAAA,EAC1B,CAAC;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/sqlite-wasm-worker-core.ts","../../src/adapters/sqlite-wasm-worker.ts"],"sourcesContent":["/// <reference lib=\"webworker\" />\n/**\n * Reusable SQLite WASM core, decoupled from any specific worker global.\n *\n * The dedicated worker ({@link file://./sqlite-wasm-worker.ts}) wires one core to\n * `self.onmessage`/`self.postMessage`.\n *\n * The WASM module and the OPFS SyncAccessHandle pool are cached at module scope\n * and shared across every core in the same worker: the pool holds\n * multiple database files keyed by filename, so installing it once and opening\n * each database within it avoids the single-writer conflict that installing the\n * pool twice in one scope would cause.\n *\n * This file cannot be unit-tested in Node (no WASM/OPFS); it is exercised through\n * the browser E2E suite.\n */\n\nimport type { WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ninterface SqliteDb {\n\texec(opts: {\n\t\tsql: string\n\t\tbind?: unknown[]\n\t\treturnValue?: string\n\t\trowMode?: string\n\t\tcallback?: (row: Record<string, unknown>) => void\n\t}): void\n\tclose(): void\n\tdeserialize?: (data: Uint8Array) => void\n}\n\ninterface OpfsPool {\n\tOpfsSAHPoolDb: new (filename: string) => SqliteDb\n}\n\ninterface Sqlite3Api {\n\too1: { DB: new (opts: { filename: string }) => SqliteDb }\n\tinstallOpfsSAHPoolVfs?: (opts: { name: string }) => Promise<OpfsPool>\n\tcapi?: { sqlite3_deserialize?: unknown }\n}\n\n/** OPFS SAH pool name. Shared across all databases on the origin (the pool holds\n * multiple db files keyed by filename), so it must NOT be namespaced per database\n * or existing persisted data would be orphaned. */\nconst OPFS_POOL_NAME = 'kora-opfs'\n\n/** Headless browsers and some profiles hang on OPFS VFS install; fall back to memory. */\nconst OPFS_INIT_TIMEOUT_MS = 10_000\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined\n\ttry {\n\t\treturn await Promise.race([\n\t\t\tpromise,\n\t\t\tnew Promise<T>((_, reject) => {\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),\n\t\t\t\t\ttimeoutMs,\n\t\t\t\t)\n\t\t\t}),\n\t\t])\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer)\n\t}\n}\n\nfunction opfsDatabaseFilename(dbName: string): string {\n\tconst base = dbName.replace(/[^a-zA-Z0-9._-]/g, '_')\n\treturn base.endsWith('.db') ? base : `${base}.db`\n}\n\n/**\n * Classify why the OPFS SyncAccessHandle pool could not be installed, so the\n * fallback to in-memory is explainable rather than silent.\n */\nfunction classifyOpfsFailure(error: unknown): 'lock-conflict' | 'timeout' | 'unsupported' {\n\tconst message = error instanceof Error ? error.message : String(error)\n\tif (/timed out|timeout/i.test(message)) {\n\t\treturn 'timeout'\n\t}\n\tif (\n\t\t/lock|already|in use|in-use|NoModificationAllowed|acquire|SyncAccessHandle|being created/i.test(\n\t\t\tmessage,\n\t\t)\n\t) {\n\t\treturn 'lock-conflict'\n\t}\n\treturn 'unsupported'\n}\n\n// Scope-global caches shared by every core in this worker.\nlet sqlite3Promise: Promise<Sqlite3Api> | null = null\nlet opfsPoolPromise: Promise<OpfsPool | null> | null = null\nlet opfsFallbackReason: 'lock-conflict' | 'timeout' | 'unsupported' | undefined\n\nasync function loadSqlite3(): Promise<Sqlite3Api> {\n\tconst sqlite3InitModule = (await import('@sqlite.org/sqlite-wasm')).default\n\t// In production builds, Vite hashes asset filenames (e.g. sqlite3-[hash].wasm).\n\t// The sqlite3 module's default locateFile resolves the unhashed name, causing a\n\t// 404. The worker/host entry sets __KORA_SQLITE_WASM_URL via a `?url` import so\n\t// we can override locateFile with the correct hashed URL.\n\tconst wasmUrl = (globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL as\n\t\t| string\n\t\t| undefined\n\tconst initOptions = wasmUrl\n\t\t? {\n\t\t\t\tlocateFile: (file: string): string => (file.endsWith('.wasm') ? wasmUrl : file),\n\t\t\t}\n\t\t: undefined\n\tconst initFn = sqlite3InitModule as unknown as (\n\t\topts?: Record<string, unknown>,\n\t) => Promise<unknown>\n\treturn (await withTimeout(initFn(initOptions), 60_000, 'SQLite3 module init')) as Sqlite3Api\n}\n\nfunction getSqlite3(): Promise<Sqlite3Api> {\n\tif (!sqlite3Promise) {\n\t\tsqlite3Promise = loadSqlite3()\n\t}\n\treturn sqlite3Promise\n}\n\nasync function installOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!sqlite3.installOpfsSAHPoolVfs) {\n\t\topfsFallbackReason = 'unsupported'\n\t\treturn null\n\t}\n\ttry {\n\t\treturn await withTimeout(\n\t\t\tsqlite3.installOpfsSAHPoolVfs({ name: OPFS_POOL_NAME }),\n\t\t\tOPFS_INIT_TIMEOUT_MS,\n\t\t\t'OPFS VFS install',\n\t\t)\n\t} catch (error) {\n\t\topfsFallbackReason = classifyOpfsFailure(error)\n\t\treturn null\n\t}\n}\n\nfunction getOpfsPool(sqlite3: Sqlite3Api): Promise<OpfsPool | null> {\n\tif (!opfsPoolPromise) {\n\t\topfsPoolPromise = installOpfsPool(sqlite3)\n\t}\n\treturn opfsPoolPromise\n}\n\n/** Handle to a single database, dispatching the worker protocol for it. */\nexport interface SqliteWasmCore {\n\thandle(request: WorkerRequest): Promise<WorkerResponse>\n}\n\n/**\n * Creates a core bound to one database. `handle` resolves with the response for\n * each request; the caller routes it back through the dedicated worker's\n * `postMessage`.\n */\nexport function createSqliteWasmCore(): SqliteWasmCore {\n\tlet db: SqliteDb | null = null\n\tlet sqlite3Api: Sqlite3Api | null = null\n\tlet persistent = false\n\n\tasync function open(\n\t\tid: number,\n\t\tddlStatements: string[],\n\t\tdbName?: string,\n\t): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\t// Re-run idempotent DDL on the existing handle rather than opening the\n\t\t\t// database a second time.\n\t\t\tif (db) {\n\t\t\t\tapplyDdl(db, ddlStatements)\n\t\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t\t}\n\n\t\t\tconst sqlite3 = await getSqlite3()\n\t\t\tsqlite3Api = sqlite3\n\t\t\tconst pool = await getOpfsPool(sqlite3)\n\n\t\t\tif (pool) {\n\t\t\t\tdb = new pool.OpfsSAHPoolDb(opfsDatabaseFilename(dbName ?? 'kora-db'))\n\t\t\t\tpersistent = true\n\t\t\t} else {\n\t\t\t\tdb = new sqlite3.oo1.DB({ filename: ':memory:' })\n\t\t\t\tpersistent = false\n\t\t\t}\n\n\t\t\tdb.exec({ sql: 'PRAGMA journal_mode = WAL' })\n\t\t\tdb.exec({ sql: 'PRAGMA foreign_keys = ON' })\n\n\t\t\tapplyDdl(db, ddlStatements)\n\t\t\treturn { id, type: 'success', data: buildOpenData() }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'INIT_ERROR' }\n\t\t}\n\t}\n\n\tfunction buildOpenData(): { persistent: boolean; fallbackReason?: string } {\n\t\treturn {\n\t\t\tpersistent,\n\t\t\t...(persistent ? {} : { fallbackReason: opfsFallbackReason ?? 'unsupported' }),\n\t\t}\n\t}\n\n\tfunction applyDdl(target: SqliteDb, ddlStatements: string[]): void {\n\t\tfor (const sql of ddlStatements) {\n\t\t\tif (sql.startsWith('--kora:safe-alter')) {\n\t\t\t\ttry {\n\t\t\t\t\ttarget.exec({ sql: sql.replace('--kora:safe-alter\\n', '') })\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = (error as Error).message || ''\n\t\t\t\t\tif (!msg.includes('duplicate column name')) {\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttarget.exec({ sql })\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction execute(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tdb.exec({ sql, bind: params })\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'EXEC_ERROR' }\n\t\t}\n\t}\n\n\tfunction query(id: number, sql: string, params?: unknown[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tdb.exec({\n\t\t\t\tsql,\n\t\t\t\tbind: params,\n\t\t\t\trowMode: 'object',\n\t\t\t\tcallback: (row: Record<string, unknown>) => {\n\t\t\t\t\trows.push({ ...row })\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn { id, type: 'success', data: rows }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'QUERY_ERROR' }\n\t\t}\n\t}\n\n\tfunction close(id: number): WorkerResponse {\n\t\tif (db) {\n\t\t\tdb.close()\n\t\t\tdb = null\n\t\t}\n\t\treturn { id, type: 'success' }\n\t}\n\n\tfunction migrate(id: number, statements: string[]): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\ttry {\n\t\t\tfor (const sql of statements) {\n\t\t\t\tdb.exec({ sql })\n\t\t\t}\n\t\t\treturn { id, type: 'success' }\n\t\t} catch (error) {\n\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'MIGRATE_ERROR' }\n\t\t}\n\t}\n\n\tfunction importData(id: number, data: Uint8Array): WorkerResponse {\n\t\tif (!db) {\n\t\t\treturn { id, type: 'error', message: 'Database is not open', code: 'DB_NOT_OPEN' }\n\t\t}\n\t\tconst dbWithDeserialize = db as SqliteDb & { deserialize?: (bytes: Uint8Array) => void }\n\t\tif (typeof dbWithDeserialize.deserialize === 'function') {\n\t\t\ttry {\n\t\t\t\tdbWithDeserialize.deserialize(data)\n\t\t\t\treturn { id, type: 'success' }\n\t\t\t} catch (error) {\n\t\t\t\treturn { id, type: 'error', message: (error as Error).message, code: 'IMPORT_ERROR' }\n\t\t\t}\n\t\t}\n\t\tif (!sqlite3Api || typeof sqlite3Api.capi?.sqlite3_deserialize === 'undefined') {\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Import not supported in this SQLite WASM runtime',\n\t\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tid,\n\t\t\ttype: 'error',\n\t\t\tmessage:\n\t\t\t\t'Import requires runtime-specific deserialize wiring and is unavailable in this worker build',\n\t\t\tcode: 'IMPORT_NOT_SUPPORTED',\n\t\t}\n\t}\n\n\tasync function handle(request: WorkerRequest): Promise<WorkerResponse> {\n\t\ttry {\n\t\t\tswitch (request.type) {\n\t\t\t\tcase 'open':\n\t\t\t\t\treturn await open(request.id, request.ddlStatements, request.dbName)\n\t\t\t\tcase 'close':\n\t\t\t\t\treturn close(request.id)\n\t\t\t\tcase 'execute':\n\t\t\t\t\treturn execute(request.id, request.sql, request.params)\n\t\t\t\tcase 'query':\n\t\t\t\t\treturn query(request.id, request.sql, request.params)\n\t\t\t\tcase 'begin':\n\t\t\t\t\treturn execute(request.id, 'BEGIN')\n\t\t\t\tcase 'commit':\n\t\t\t\t\treturn execute(request.id, 'COMMIT')\n\t\t\t\tcase 'rollback':\n\t\t\t\t\treturn execute(request.id, 'ROLLBACK')\n\t\t\t\tcase 'migrate':\n\t\t\t\t\treturn migrate(request.id, request.statements)\n\t\t\t\tcase 'import':\n\t\t\t\t\treturn importData(request.id, request.data)\n\t\t\t\tcase 'export':\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Export not yet supported in browser worker',\n\t\t\t\t\t\tcode: 'EXPORT_NOT_SUPPORTED',\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\treturn {\n\t\t\t\t\t\tid: (request as WorkerRequest).id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage: 'Unknown request type',\n\t\t\t\t\t\tcode: 'UNKNOWN_REQUEST',\n\t\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: (error as Error).message,\n\t\t\t\tcode: 'WORKER_ERROR',\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { handle }\n}\n","/// <reference lib=\"webworker\" />\n/**\n * Web Worker script for running SQLite WASM in a dedicated worker.\n *\n * This file is intended to run inside a Web Worker in browsers. It wires one\n * {@link createSqliteWasmCore} to the worker's message channel.\n *\n * This script cannot be tested in Node.js; it is validated in E2E browser tests.\n */\n\nimport type { WorkerRequest } from './sqlite-wasm-channel'\nimport { createSqliteWasmCore } from './sqlite-wasm-worker-core'\n\ndeclare const self: DedicatedWorkerGlobalScope\n\nconst core = createSqliteWasmCore()\n\nself.onmessage = (event: MessageEvent<WorkerRequest>): void => {\n\tvoid core.handle(event.data).then((response) => {\n\t\tself.postMessage(response)\n\t})\n}\n"],"mappings":"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","names":[]}
|