@korajs/store 1.0.0-beta.0 → 1.0.0-beta.10
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.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.d.cts +1 -1
- package/dist/adapters/better-sqlite3.d.ts +1 -1
- package/dist/adapters/better-sqlite3.js +1 -1
- package/dist/adapters/indexeddb.cjs +523 -148
- 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 +55 -11
- package/dist/adapters/indexeddb.js.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.cjs +207 -187
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +208 -188
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs +474 -140
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.d.cts +45 -8
- package/dist/adapters/sqlite-wasm.d.ts +45 -8
- package/dist/adapters/sqlite-wasm.js +3 -3
- package/dist/{backup-5XZ7THNJ.js → backup-5S7K2Z35.js} +3 -3
- package/dist/blob-fs.cjs +166 -0
- package/dist/blob-fs.cjs.map +1 -0
- package/dist/blob-fs.d.cts +29 -0
- package/dist/blob-fs.d.ts +29 -0
- package/dist/blob-fs.js +128 -0
- package/dist/blob-fs.js.map +1 -0
- package/dist/{chunk-EIVXWF4B.js → chunk-CYO3YKFL.js} +4 -3
- package/dist/chunk-CYO3YKFL.js.map +1 -0
- package/dist/{chunk-ETDJEH2I.js → chunk-GSJ7EEUC.js} +12 -1
- package/dist/chunk-GSJ7EEUC.js.map +1 -0
- package/dist/{chunk-5R4QMY3Z.js → chunk-HS6DBMST.js} +13 -10
- package/dist/chunk-HS6DBMST.js.map +1 -0
- package/dist/chunk-RJIEKEN4.js +751 -0
- package/dist/chunk-RJIEKEN4.js.map +1 -0
- package/dist/{chunk-QL2XVZRN.js → chunk-TUR7RGEP.js} +12 -1
- package/dist/{chunk-QL2XVZRN.js.map → chunk-TUR7RGEP.js.map} +1 -1
- package/dist/{chunk-IOEFNHHP.js → chunk-VDBJQ75K.js} +2 -2
- package/dist/chunk-VDBJQ75K.js.map +1 -0
- package/dist/chunk-W4MTTNEZ.js +55 -0
- package/dist/chunk-W4MTTNEZ.js.map +1 -0
- package/dist/{chunk-OUQDC5Z6.js → chunk-YT7MRFCP.js} +55 -35
- package/dist/chunk-YT7MRFCP.js.map +1 -0
- package/dist/content-addressed-blob-store-Dcj3SB9r.d.cts +60 -0
- package/dist/content-addressed-blob-store-Dcj3SB9r.d.ts +60 -0
- package/dist/{export-audit-VPGQWNR6.js → export-audit-ODGQTQIK.js} +3 -3
- package/dist/index.cjs +923 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +443 -6
- package/dist/index.d.ts +443 -6
- package/dist/index.js +737 -49
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +69 -43
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +3 -3
- package/dist/sqlite-wasm-channel-GGHA2BXU.js +12 -0
- package/dist/{sqlite-wasm-channel-DUDPclTE.d.cts → sqlite-wasm-channel-L6o3bWQK.d.cts} +1 -1
- package/dist/{sqlite-wasm-channel-DUDPclTE.d.ts → sqlite-wasm-channel-L6o3bWQK.d.ts} +1 -1
- package/dist/{types-CyjXwrjW.d.cts → types-BOO9EPt1.d.ts} +10 -3
- package/dist/{types-CJOVKxLw.d.ts → types-BslDOTk0.d.cts} +32 -2
- package/dist/{types-CJOVKxLw.d.cts → types-BslDOTk0.d.ts} +32 -2
- package/dist/{types-DAxCladl.d.ts → types-C_GDavG-.d.cts} +10 -3
- package/package.json +13 -12
- package/dist/adapters/sqlite-wasm-shared-host.cjs +0 -68
- package/dist/adapters/sqlite-wasm-shared-host.cjs.map +0 -1
- package/dist/adapters/sqlite-wasm-shared-host.d.cts +0 -2
- package/dist/adapters/sqlite-wasm-shared-host.d.ts +0 -2
- package/dist/adapters/sqlite-wasm-shared-host.js +0 -66
- package/dist/adapters/sqlite-wasm-shared-host.js.map +0 -1
- package/dist/chunk-5R4QMY3Z.js.map +0 -1
- package/dist/chunk-EIVXWF4B.js.map +0 -1
- package/dist/chunk-ETDJEH2I.js.map +0 -1
- package/dist/chunk-IOEFNHHP.js.map +0 -1
- package/dist/chunk-OUQDC5Z6.js.map +0 -1
- package/dist/chunk-RBPVEAVM.js +0 -425
- package/dist/chunk-RBPVEAVM.js.map +0 -1
- package/dist/sqlite-wasm-channel-H43KEZLM.js +0 -12
- /package/dist/{backup-5XZ7THNJ.js.map → backup-5S7K2Z35.js.map} +0 -0
- /package/dist/{export-audit-VPGQWNR6.js.map → export-audit-ODGQTQIK.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-H43KEZLM.js.map → sqlite-wasm-channel-GGHA2BXU.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/mutex.ts","../../src/adapters/sqlite-wasm-channel.ts","../../src/adapters/indexeddb.ts","../../src/adapters/indexeddb-adapter.ts","../../src/adapters/indexeddb-persistence-scheduler.ts","../../src/adapters/sqlite-wasm-adapter.ts","../../src/multi-tab/shared-worker-bridge.ts","../../src/multi-tab/tab-storage.ts","../../src/adapters/sqlite-wasm-persistence.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\n\n/**\n * Thrown when a query is invalid (bad field names, invalid operators, etc.).\n */\nexport class QueryError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'QUERY_ERROR', context)\n\t\tthis.name = 'QueryError'\n\t}\n}\n\n/**\n * Thrown when a record is not found by ID (findById, update, delete on missing record).\n */\nexport class RecordNotFoundError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(`Record \"${recordId}\" not found in collection \"${collection}\"`, 'RECORD_NOT_FOUND', {\n\t\t\tcollection,\n\t\t\trecordId,\n\t\t})\n\t\tthis.name = 'RecordNotFoundError'\n\t}\n}\n\n/**\n * Thrown inside `applyRemoteOperation` when the row's version state changed\n * between the caller's snapshot read and the guarded write (a concurrent local\n * mutation landed in the window). The transaction is rolled back with nothing\n * written; the caller re-reads fresh state, recomputes its merge, and retries.\n * This is the optimistic-concurrency guard that keeps merge-engine results\n * (richtext, add-wins arrays, constraint resolutions) from clobbering newer\n * local edits they never saw.\n */\nexport class OptimisticLockError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(\n\t\t\t`Row version changed while merging record \"${recordId}\" in \"${collection}\"; retry with fresh state`,\n\t\t\t'OPTIMISTIC_LOCK',\n\t\t\t{ collection, recordId },\n\t\t)\n\t\tthis.name = 'OptimisticLockError'\n\t}\n}\n\n/**\n * Thrown when a storage adapter operation fails.\n */\nexport class AdapterError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ADAPTER_ERROR', context)\n\t\tthis.name = 'AdapterError'\n\t}\n}\n\n/**\n * Thrown when an operation is attempted on a store that has not been opened.\n */\nexport class StoreNotOpenError extends KoraError {\n\tconstructor() {\n\t\tsuper('Store is not open. Call store.open() before performing operations.', 'STORE_NOT_OPEN')\n\t\tthis.name = 'StoreNotOpenError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker fails to initialize (WASM load failure, OPFS unavailable, etc.).\n */\nexport class WorkerInitError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Worker initialization failed: ${message}`, 'WORKER_INIT_ERROR', context)\n\t\tthis.name = 'WorkerInitError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker does not respond within the configured timeout.\n */\nexport class WorkerTimeoutError extends KoraError {\n\tconstructor(operation: string, timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Worker did not respond within ${timeoutMs}ms for operation \"${operation}\"`,\n\t\t\t'WORKER_TIMEOUT',\n\t\t\t{ operation, timeoutMs },\n\t\t)\n\t\tthis.name = 'WorkerTimeoutError'\n\t}\n}\n\n/**\n * Thrown when IndexedDB persistence operations fail (serialize/deserialize).\n */\nexport class PersistenceError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Persistence error: ${message}`, 'PERSISTENCE_ERROR', context)\n\t\tthis.name = 'PersistenceError'\n\t}\n}\n","/**\n * Async mutex for serializing access to a resource across `await` boundaries.\n *\n * This exists because an adapter's `transaction()` is async (its callback may\n * await), but the underlying database transaction (BEGIN…COMMIT) is not\n * reentrant: starting a second transaction while one is open is an error. When\n * a client applies a relayed remote operation while a local write is in flight,\n * the two `transaction()` calls interleave at their `await` points, and without\n * serialization the second `BEGIN` collides with the first, silently dropping\n * the operation. Serializing every transaction through this mutex guarantees at\n * most one is active at a time.\n */\nexport class Mutex {\n\tprivate locked = false\n\tprivate readonly waiters: Array<() => void> = []\n\n\t/**\n\t * Acquire the mutex. Resolves with a release function once the lock is held.\n\t * If the mutex is already held, the caller waits (FIFO) until it is released.\n\t */\n\tasync acquire(): Promise<() => void> {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true\n\t\t\treturn this.createRelease()\n\t\t}\n\n\t\treturn new Promise<() => void>((resolve) => {\n\t\t\tthis.waiters.push(() => {\n\t\t\t\tresolve(this.createRelease())\n\t\t\t})\n\t\t})\n\t}\n\n\tprivate createRelease(): () => void {\n\t\tlet released = false\n\t\treturn () => {\n\t\t\tif (released) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\treleased = true\n\t\t\tconst next = this.waiters.shift()\n\t\t\tif (next) {\n\t\t\t\tnext()\n\t\t\t} else {\n\t\t\t\tthis.locked = false\n\t\t\t}\n\t\t}\n\t}\n}\n","/// <reference lib=\"dom\" />\nimport { WorkerTimeoutError } from '../errors'\n\n// === Message Protocol ===\n\n/**\n * Request message sent from the main thread to the SQLite WASM worker.\n * Each request carries a unique `id` for response correlation.\n */\nexport type WorkerRequest =\n\t| { id: number; type: 'open'; ddlStatements: string[]; dbName?: string }\n\t| { id: number; type: 'close' }\n\t| { id: number; type: 'execute'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'query'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'begin' }\n\t| { id: number; type: 'commit' }\n\t| { id: number; type: 'rollback' }\n\t| { id: number; type: 'migrate'; from: number; to: number; statements: string[] }\n\t| { id: number; type: 'export' }\n\t| { id: number; type: 'import'; data: Uint8Array }\n\n/**\n * Response message sent from the worker back to the main thread.\n * Matches the request `id` for correlation.\n */\nexport type WorkerResponse =\n\t| { id: number; type: 'success'; data?: unknown }\n\t| { id: number; type: 'error'; message: string; code: string; context?: Record<string, unknown> }\n\n// === WorkerBridge Interface ===\n\n/**\n * Abstraction over the communication channel with the SQLite WASM worker.\n * In browsers, this is backed by a real Web Worker via MessagePort.\n * In Node.js tests, this is backed by better-sqlite3 via MockWorkerBridge.\n */\nexport interface WorkerBridge {\n\t/** Send a request to the worker and wait for a response. */\n\tsend(request: WorkerRequest): Promise<WorkerResponse>\n\n\t/** Terminate the worker. Safe to call multiple times. */\n\tterminate(): void\n}\n\n// === Mutex ===\n\n// The Mutex now lives in its own module so non-browser adapters (better-sqlite3)\n// can serialize transactions without importing browser-worker code.\nexport { Mutex } from './mutex'\n\n// === WebWorkerBridge ===\n\n/**\n * WorkerBridge implementation for browser environments.\n * Communicates with an actual Web Worker running SQLite WASM.\n */\nexport class WebWorkerBridge implements WorkerBridge {\n\tprivate worker: Worker\n\tprivate pending = new Map<\n\t\tnumber,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate nextId = 1\n\tprivate terminated = false\n\tprivate timeoutMs: number\n\n\t/**\n\t * @param workerUrl - URL to the sqlite-wasm-worker script\n\t * @param timeoutMs - Timeout for worker responses in milliseconds (default: 30000)\n\t */\n\tconstructor(workerUrl: string | URL, timeoutMs = 30000) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.worker = new Worker(workerUrl, { type: 'module' })\n\t\tthis.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\tconst response = event.data\n\t\t\tconst entry = this.pending.get(response.id)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(response.id)\n\t\t\t\tentry.resolve(response)\n\t\t\t}\n\t\t}\n\t\tthis.worker.onerror = (event) => {\n\t\t\t// Reject all pending requests on worker error\n\t\t\tconst error = new Error(`Worker error: ${event.message}`)\n\t\t\tfor (const [id, entry] of this.pending) {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\tentry.reject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Worker has been terminated',\n\t\t\t\tcode: 'WORKER_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst id = this.nextId++\n\t\tconst req = { ...request, id }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\treject(new WorkerTimeoutError(req.type, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.worker.postMessage(req)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) return\n\t\tthis.terminated = true\n\t\tthis.worker.terminate()\n\t\t// Reject any pending requests\n\t\tfor (const [id, entry] of this.pending) {\n\t\t\tthis.pending.delete(id)\n\t\t\tentry.reject(new Error('Worker terminated'))\n\t\t}\n\t}\n}\n","// Entry point for @korajs/store/indexeddb\nexport { IndexedDbAdapter } from './indexeddb-adapter'\nexport type { IndexedDbAdapterOptions } from './indexeddb-adapter'\n","import type { KoraEventEmitter, SchemaDefinition } 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 ${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.map((column) => ensureSafeIdentifier(column)).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 ${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 }>(`PRAGMA table_info(${tableName})`)\n\t\t\tconst columnNames = columns.map((column) => column.name)\n\t\t\tconst rows = await this.inner.query<Record<string, unknown>>(`SELECT * FROM ${tableName}`)\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","import { generateFullDDL } from '@korajs/core'\nimport type { SchemaDefinition } from '@korajs/core'\nimport { AdapterError, StoreNotOpenError } from '../errors'\nimport { SharedWorkerClientBridge } from '../multi-tab/shared-worker-bridge'\nimport {\n\tFollowerBroadcastBridge,\n\tacquireTabStorageSession,\n\tisSharedWorkerStorageSupported,\n\tstartLeaderRpcRelay,\n} from '../multi-tab/tab-storage'\nimport type { MigrationPlan, StorageAdapter, Transaction } from '../types'\nimport { Mutex } from './sqlite-wasm-channel'\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\n/**\n * Options for creating a SqliteWasmAdapter.\n */\nexport interface SqliteWasmAdapterOptions {\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 * Database name for persistence. Used as the OPFS file name or IDB key.\n\t */\n\tdbName?: string\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 * Optional SharedWorker host script (`sqlite-wasm-shared-host`). When set and\n\t * {@link isSharedWorkerStorageSupported}, one DedicatedWorker per `dbName` is pooled per origin.\n\t * Requires `workerUrl` for the inner SQLite worker. Falls back to leader election when omitted.\n\t */\n\tsharedWorkerUrl?: string | URL\n}\n\n/**\n * SQLite WASM adapter that communicates with a SQLite instance through a WorkerBridge.\n *\n * In browsers, the bridge is backed by a Web Worker running SQLite WASM with OPFS persistence.\n * In Node.js tests, the bridge is backed by MockWorkerBridge wrapping better-sqlite3.\n *\n * @example\n * ```typescript\n * // Browser usage\n * const adapter = new SqliteWasmAdapter({ workerUrl: '/sqlite-wasm-worker.js' })\n *\n * // Test usage with MockWorkerBridge\n * import { MockWorkerBridge } from './sqlite-wasm-mock-bridge'\n * const adapter = new SqliteWasmAdapter({ bridge: new MockWorkerBridge() })\n * ```\n */\nexport class SqliteWasmAdapter implements StorageAdapter {\n\tprivate bridge: WorkerBridge | null = null\n\tprivate opened = false\n\tprivate readonly mutex = new Mutex()\n\tprivate readonly injectedBridge: WorkerBridge | undefined\n\tprivate readonly workerUrl: string | URL | undefined\n\tprivate readonly sharedWorkerUrl: string | URL | undefined\n\tprivate readonly workerResponseTimeoutMs: number\n\tprivate readonly dbName: string\n\tprivate tabSession: Awaited<ReturnType<typeof acquireTabStorageSession>> | null = null\n\tprivate sharedWorker: SharedWorker | null = null\n\n\tconstructor(options: SqliteWasmAdapterOptions = {}) {\n\t\tthis.injectedBridge = options.bridge\n\t\tthis.workerUrl = options.workerUrl\n\t\tthis.sharedWorkerUrl = options.sharedWorkerUrl\n\t\tthis.workerResponseTimeoutMs = options.workerResponseTimeoutMs ?? 30_000\n\t\tthis.dbName = options.dbName ?? 'kora-db'\n\t}\n\n\tasync open(schema: SchemaDefinition): Promise<void> {\n\t\tif (this.opened) return\n\n\t\tif (this.injectedBridge) {\n\t\t\tthis.bridge = this.injectedBridge\n\t\t} else if (this.sharedWorkerUrl && this.workerUrl && isSharedWorkerStorageSupported()) {\n\t\t\tconst workerUrlString =\n\t\t\t\ttypeof this.workerUrl === 'string' ? this.workerUrl : this.workerUrl.href\n\t\t\tconst sharedUrl =\n\t\t\t\ttypeof this.sharedWorkerUrl === 'string' ? this.sharedWorkerUrl : this.sharedWorkerUrl.href\n\t\t\tthis.sharedWorker = new SharedWorker(sharedUrl, { name: `kora-sw-${this.dbName}` })\n\t\t\tthis.bridge = new SharedWorkerClientBridge(this.sharedWorker, this.dbName, workerUrlString)\n\t\t} else if (this.workerUrl) {\n\t\t\tthis.tabSession = await acquireTabStorageSession(this.dbName)\n\t\t\tconst { WebWorkerBridge } = await import('./sqlite-wasm-channel')\n\n\t\t\tif (this.tabSession.role === 'leader') {\n\t\t\t\tconst workerBridge = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs)\n\t\t\t\tthis.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge)\n\t\t\t\tthis.bridge = workerBridge\n\t\t\t} else {\n\t\t\t\tthis.bridge = new FollowerBroadcastBridge(\n\t\t\t\t\tthis.tabSession.channelName,\n\t\t\t\t\tthis.workerResponseTimeoutMs,\n\t\t\t\t)\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new AdapterError(\n\t\t\t\t'SqliteWasmAdapter requires either a bridge (for testing) or a workerUrl (for browsers). ' +\n\t\t\t\t\t'Pass { bridge: new MockWorkerBridge() } for tests, or { workerUrl: \"/worker.js\" } for browsers.',\n\t\t\t)\n\t\t}\n\n\t\tconst ddlStatements = generateFullDDL(schema)\n\t\tconst response = await this.sendRequest({\n\t\t\tid: 0,\n\t\t\ttype: 'open',\n\t\t\tddlStatements,\n\t\t\tdbName: this.dbName,\n\t\t})\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Failed to open database: ${response.message}`, {\n\t\t\t\tcode: response.code,\n\t\t\t\tdbName: this.dbName,\n\t\t\t})\n\t\t}\n\t\tthis.opened = true\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (!this.bridge) return\n\n\t\ttry {\n\t\t\tawait this.sendRequest({ id: 0, type: 'close' })\n\t\t} finally {\n\t\t\tthis.tabSession?.stopRelay?.()\n\t\t\tif (this.tabSession?.releaseLock) {\n\t\t\t\tawait this.tabSession.releaseLock()\n\t\t\t}\n\t\t\tthis.tabSession = null\n\t\t\tthis.sharedWorker = null\n\t\t\tthis.bridge.terminate()\n\t\t\tthis.bridge = null\n\t\t\tthis.opened = false\n\t\t}\n\t}\n\n\tasync execute(sql: string, params?: unknown[]): Promise<void> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql, params })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Execute failed: ${response.message}`, { sql, params })\n\t\t}\n\t}\n\n\tasync query<T>(sql: string, params?: unknown[]): Promise<T[]> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'query', sql, params })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Query failed: ${response.message}`, { sql, params })\n\t\t}\n\t\treturn (response.data as T[]) ?? []\n\t}\n\n\tasync transaction(fn: (tx: Transaction) => Promise<void>): Promise<void> {\n\t\tthis.guardOpen()\n\n\t\tconst release = await this.mutex.acquire()\n\t\ttry {\n\t\t\tawait this.sendChecked({ id: 0, type: 'begin' }, 'BEGIN transaction')\n\n\t\t\tconst tx: Transaction = {\n\t\t\t\texecute: async (sql: string, params?: unknown[]): Promise<void> => {\n\t\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql, params })\n\t\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\t\tthrow new AdapterError(`Transaction execute failed: ${response.message}`, {\n\t\t\t\t\t\t\tsql,\n\t\t\t\t\t\t\tparams,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tquery: async <T>(sql: string, params?: unknown[]): Promise<T[]> => {\n\t\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'query', sql, params })\n\t\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\t\tthrow new AdapterError(`Transaction query failed: ${response.message}`, { sql, params })\n\t\t\t\t\t}\n\t\t\t\t\treturn (response.data as T[]) ?? []\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tawait fn(tx)\n\t\t\tawait this.sendChecked({ id: 0, type: 'commit' }, 'COMMIT transaction')\n\t\t} catch (error) {\n\t\t\t// Attempt rollback, but don't mask the original error\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest({ id: 0, type: 'rollback' })\n\t\t\t} catch {\n\t\t\t\t// Rollback failure is secondary to the original error\n\t\t\t}\n\t\t\tthrow error\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\tasync migrate(from: number, to: number, migration: MigrationPlan): Promise<void> {\n\t\tthis.guardOpen()\n\n\t\tconst release = await this.mutex.acquire()\n\t\ttry {\n\t\t\tawait this.sendChecked({ id: 0, type: 'begin' }, 'BEGIN migration')\n\n\t\t\tfor (const sql of migration.statements) {\n\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql })\n\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\tthrow new AdapterError(`Migration from v${from} to v${to} failed: ${response.message}`, {\n\t\t\t\t\t\tfrom,\n\t\t\t\t\t\tto,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this.sendChecked({ id: 0, type: 'commit' }, 'COMMIT migration')\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest({ id: 0, type: 'rollback' })\n\t\t\t} catch {\n\t\t\t\t// Rollback failure is secondary\n\t\t\t}\n\t\t\tif (error instanceof AdapterError) throw error\n\t\t\tthrow new AdapterError(\n\t\t\t\t`Migration from v${from} to v${to} failed: ${(error as Error).message}`,\n\t\t\t\t{ from, to },\n\t\t\t)\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\t/**\n\t * Export the database as a Uint8Array (for IndexedDB persistence).\n\t * Only available when the database is open.\n\t */\n\tasync exportDatabase(): Promise<Uint8Array> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'export' })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Export failed: ${response.message}`)\n\t\t}\n\t\treturn response.data as Uint8Array\n\t}\n\n\t/**\n\t * Import a serialized database snapshot.\n\t */\n\tasync importDatabase(data: Uint8Array): Promise<void> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'import', data })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Import failed: ${response.message}`)\n\t\t}\n\t}\n\n\tprivate guardOpen(): void {\n\t\tif (!this.opened || !this.bridge) {\n\t\t\tthrow new StoreNotOpenError()\n\t\t}\n\t}\n\n\tprivate async sendRequest(request: WorkerRequest): Promise<WorkerResponse> {\n\t\t// guardOpen() is always called before sendRequest, so bridge is guaranteed non-null\n\t\tconst bridge = this.bridge\n\t\tif (!bridge) {\n\t\t\tthrow new StoreNotOpenError()\n\t\t}\n\t\treturn bridge.send(request)\n\t}\n\n\tprivate async sendChecked(request: WorkerRequest, description: string): Promise<void> {\n\t\tconst response = await this.sendRequest(request)\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`${description} failed: ${response.message}`)\n\t\t}\n\t}\n}\n","/// <reference lib=\"dom\" />\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from '../adapters/sqlite-wasm-channel'\nimport { WorkerTimeoutError } from '../errors'\n\nconst SW_REQUEST = 'kora-sw-request'\nconst SW_RESPONSE = 'kora-sw-response'\n\nexport interface SharedWorkerRpcRequest {\n\ttype: typeof SW_REQUEST\n\trequestId: string\n\tdbName: string\n\tworkerUrl: string\n\trequest: WorkerRequest\n}\n\nexport interface SharedWorkerRpcResponse {\n\ttype: typeof SW_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\n/**\n * Forwards {@link WorkerBridge} RPC to a SharedWorker that hosts one DedicatedWorker per `dbName`.\n * Pair with the bundled `sqlite-wasm-shared-host` worker script.\n */\nexport class SharedWorkerClientBridge implements WorkerBridge {\n\tprivate readonly port: MessagePort\n\tprivate readonly pending = new Map<\n\t\tstring,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate readonly timeoutMs: number\n\tprivate terminated = false\n\n\tconstructor(\n\t\tsharedWorker: SharedWorker,\n\t\tprivate readonly dbName: string,\n\t\tprivate readonly workerUrl: string,\n\t\ttimeoutMs = 30000,\n\t) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.port = sharedWorker.port\n\t\tthis.port.start()\n\t\tthis.port.addEventListener('message', (event: MessageEvent<SharedWorkerRpcResponse>) => {\n\t\t\tconst data = event.data\n\t\t\tif (data?.type !== SW_RESPONSE) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.pending.get(data.requestId)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(data.requestId)\n\t\t\t\tentry.resolve(data.response)\n\t\t\t}\n\t\t})\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'SharedWorker bridge terminated',\n\t\t\t\tcode: 'BRIDGE_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst requestId = crypto.randomUUID()\n\t\tconst msg: SharedWorkerRpcRequest = {\n\t\t\ttype: SW_REQUEST,\n\t\t\trequestId,\n\t\t\tdbName: this.dbName,\n\t\t\tworkerUrl: this.workerUrl,\n\t\t\trequest,\n\t\t}\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(requestId)\n\t\t\t\treject(new WorkerTimeoutError(`shared-worker:${request.type}`, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(requestId, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.port.postMessage(msg)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tthis.terminated = true\n\t\tthis.port.close()\n\t\tfor (const [, entry] of this.pending) {\n\t\t\tentry.reject(new Error('SharedWorker bridge terminated'))\n\t\t}\n\t\tthis.pending.clear()\n\t}\n}\n","/**\n * Multi-tab SQLite storage coordination via `navigator.locks` and `BroadcastChannel`.\n *\n * One tab holds the exclusive `kora-leader-${dbName}` lock and owns the SQLite worker.\n * Other tabs send worker RPC over a named broadcast channel.\n */\n\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from '../adapters/sqlite-wasm-channel'\nimport { WorkerTimeoutError } from '../errors'\n\nconst RPC_REQUEST = 'kora-worker-request'\nconst RPC_RESPONSE = 'kora-worker-response'\n\ninterface RpcRequestMessage {\n\ttype: typeof RPC_REQUEST\n\trequestId: string\n\trequest: WorkerRequest\n}\n\ninterface RpcResponseMessage {\n\ttype: typeof RPC_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\nexport type TabStorageRole = 'leader' | 'follower'\n\nexport interface TabStorageSession {\n\trole: TabStorageRole\n\tchannelName: string\n\t/** Leader only: release the navigator lock when closing the database. */\n\treleaseLock?: () => Promise<void>\n\t/** Leader only: stop the broadcast RPC relay. */\n\tstopRelay?: () => void\n}\n\n/**\n * Returns whether a SharedWorker could host a single SQLite WASM instance per origin.\n * Not implemented yet — use {@link isMultiTabStorageSupported} + leader election today.\n */\nexport function isSharedWorkerStorageSupported(): boolean {\n\treturn typeof globalThis !== 'undefined' && typeof SharedWorker !== 'undefined'\n}\n\n/**\n * Returns whether multi-tab coordination APIs exist in this runtime.\n */\nexport function isMultiTabStorageSupported(): boolean {\n\treturn (\n\t\ttypeof globalThis !== 'undefined' &&\n\t\ttypeof BroadcastChannel !== 'undefined' &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\ttypeof navigator.locks?.request === 'function'\n\t)\n}\n\n/**\n * Resolve leader vs follower for a database name.\n * Without lock APIs, every instance is treated as leader (single-tab / Node).\n */\nexport async function acquireTabStorageSession(dbName: string): Promise<TabStorageSession> {\n\tconst channelName = `kora-storage-${dbName}`\n\n\tif (!isMultiTabStorageSupported()) {\n\t\treturn { role: 'leader', channelName }\n\t}\n\n\treturn new Promise<TabStorageSession>((resolve) => {\n\t\tlet releaseHeld: (() => void) | undefined\n\n\t\tvoid navigator.locks.request(\n\t\t\t`kora-leader-${dbName}`,\n\t\t\t{ mode: 'exclusive', ifAvailable: true },\n\t\t\t(lock) => {\n\t\t\t\tif (lock === null) {\n\t\t\t\t\tresolve({ role: 'follower', channelName })\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tresolve({\n\t\t\t\t\trole: 'leader',\n\t\t\t\t\tchannelName,\n\t\t\t\t\treleaseLock: async () => {\n\t\t\t\t\t\treleaseHeld?.()\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\treturn new Promise<void>((release) => {\n\t\t\t\t\treleaseHeld = release\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\t})\n}\n\n/**\n * Leader tab: forward follower RPC to the real worker bridge.\n */\nexport function startLeaderRpcRelay(channelName: string, bridge: WorkerBridge): () => void {\n\tconst channel = new BroadcastChannel(channelName)\n\n\tconst onMessage = (event: MessageEvent<RpcRequestMessage>): void => {\n\t\tconst data = event.data\n\t\tif (data?.type !== RPC_REQUEST) {\n\t\t\treturn\n\t\t}\n\n\t\tvoid bridge\n\t\t\t.send(data.request)\n\t\t\t.then((response) => {\n\t\t\t\tconst msg: RpcResponseMessage = {\n\t\t\t\t\ttype: RPC_RESPONSE,\n\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\tresponse,\n\t\t\t\t}\n\t\t\t\tchannel.postMessage(msg)\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : 'Worker RPC failed'\n\t\t\t\tconst msg: RpcResponseMessage = {\n\t\t\t\t\ttype: RPC_RESPONSE,\n\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\tresponse: {\n\t\t\t\t\t\tid: data.request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\tcode: 'LEADER_RPC_ERROR',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tchannel.postMessage(msg)\n\t\t\t})\n\t}\n\n\tchannel.addEventListener('message', onMessage)\n\treturn () => {\n\t\tchannel.removeEventListener('message', onMessage)\n\t\tchannel.close()\n\t}\n}\n\n/**\n * Follower tab: proxy {@link WorkerBridge} over BroadcastChannel to the leader.\n */\nexport class FollowerBroadcastBridge implements WorkerBridge {\n\tprivate readonly channel: BroadcastChannel\n\tprivate readonly pending = new Map<\n\t\tstring,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate readonly timeoutMs: number\n\tprivate terminated = false\n\n\tconstructor(channelName: string, timeoutMs = 30000) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.channel = new BroadcastChannel(channelName)\n\t\tthis.channel.addEventListener('message', (event: MessageEvent<RpcResponseMessage>) => {\n\t\t\tconst data = event.data\n\t\t\tif (data?.type !== RPC_RESPONSE) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.pending.get(data.requestId)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(data.requestId)\n\t\t\t\tentry.resolve(data.response)\n\t\t\t}\n\t\t})\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Follower bridge terminated',\n\t\t\t\tcode: 'BRIDGE_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst requestId = crypto.randomUUID()\n\t\tconst msg: RpcRequestMessage = { type: RPC_REQUEST, requestId, request }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(requestId)\n\t\t\t\treject(new WorkerTimeoutError(`follower-rpc:${request.type}`, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(requestId, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.channel.postMessage(msg)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tthis.terminated = true\n\t\tthis.channel.close()\n\t\tfor (const [, entry] of this.pending) {\n\t\t\tentry.reject(new Error('Follower bridge terminated'))\n\t\t}\n\t\tthis.pending.clear()\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":["import_core","WebWorkerBridge"]}
|
|
1
|
+
{"version":3,"sources":["../../src/errors.ts","../../src/adapters/mutex.ts","../../src/adapters/sqlite-wasm-channel.ts","../../src/adapters/indexeddb.ts","../../src/adapters/indexeddb-adapter.ts","../../src/adapters/indexeddb-persistence-scheduler.ts","../../src/adapters/sqlite-wasm-adapter.ts","../../src/multi-tab/tab-storage.ts","../../src/adapters/sqlite-wasm-persistence.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\n\n/**\n * Thrown when a query is invalid (bad field names, invalid operators, etc.).\n */\nexport class QueryError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'QUERY_ERROR', context)\n\t\tthis.name = 'QueryError'\n\t}\n}\n\n/**\n * Thrown when a record is not found by ID (findById, update, delete on missing record).\n */\nexport class RecordNotFoundError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(`Record \"${recordId}\" not found in collection \"${collection}\"`, 'RECORD_NOT_FOUND', {\n\t\t\tcollection,\n\t\t\trecordId,\n\t\t})\n\t\tthis.name = 'RecordNotFoundError'\n\t}\n}\n\n/**\n * Thrown inside `applyRemoteOperation` when the row's version state changed\n * between the caller's snapshot read and the guarded write (a concurrent local\n * mutation landed in the window). The transaction is rolled back with nothing\n * written; the caller re-reads fresh state, recomputes its merge, and retries.\n * This is the optimistic-concurrency guard that keeps merge-engine results\n * (richtext, add-wins arrays, constraint resolutions) from clobbering newer\n * local edits they never saw.\n */\nexport class OptimisticLockError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(\n\t\t\t`Row version changed while merging record \"${recordId}\" in \"${collection}\"; retry with fresh state`,\n\t\t\t'OPTIMISTIC_LOCK',\n\t\t\t{ collection, recordId },\n\t\t)\n\t\tthis.name = 'OptimisticLockError'\n\t}\n}\n\n/**\n * Thrown when a storage adapter operation fails.\n */\nexport class AdapterError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ADAPTER_ERROR', context)\n\t\tthis.name = 'AdapterError'\n\t}\n}\n\n/**\n * Thrown when an operation is attempted on a store that has not been opened.\n */\nexport class StoreNotOpenError extends KoraError {\n\tconstructor() {\n\t\tsuper('Store is not open. Call store.open() before performing operations.', 'STORE_NOT_OPEN')\n\t\tthis.name = 'StoreNotOpenError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker fails to initialize (WASM load failure, OPFS unavailable, etc.).\n */\nexport class WorkerInitError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Worker initialization failed: ${message}`, 'WORKER_INIT_ERROR', context)\n\t\tthis.name = 'WorkerInitError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker does not respond within the configured timeout.\n */\nexport class WorkerTimeoutError extends KoraError {\n\tconstructor(operation: string, timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Worker did not respond within ${timeoutMs}ms for operation \"${operation}\"`,\n\t\t\t'WORKER_TIMEOUT',\n\t\t\t{ operation, timeoutMs },\n\t\t)\n\t\tthis.name = 'WorkerTimeoutError'\n\t}\n}\n\n/**\n * Thrown when a follower tab cannot reach a live leader tab over the multi-tab\n * broadcast channel (the leader tab was closed, crashed, or is unresponsive).\n * Distinct from {@link WorkerTimeoutError}: this fails fast on a confirmed-absent\n * leader instead of waiting out the full RPC timeout.\n */\nexport class NoLeaderError extends KoraError {\n\tconstructor(operation: string) {\n\t\tsuper(\n\t\t\t`No live leader tab is answering multi-tab storage RPC for operation \"${operation}\"`,\n\t\t\t'NO_LEADER',\n\t\t\t{ operation },\n\t\t)\n\t\tthis.name = 'NoLeaderError'\n\t}\n}\n\n/**\n * Thrown when IndexedDB persistence operations fail (serialize/deserialize).\n */\nexport class PersistenceError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Persistence error: ${message}`, 'PERSISTENCE_ERROR', context)\n\t\tthis.name = 'PersistenceError'\n\t}\n}\n","/**\n * Async mutex for serializing access to a resource across `await` boundaries.\n *\n * This exists because an adapter's `transaction()` is async (its callback may\n * await), but the underlying database transaction (BEGIN…COMMIT) is not\n * reentrant: starting a second transaction while one is open is an error. When\n * a client applies a relayed remote operation while a local write is in flight,\n * the two `transaction()` calls interleave at their `await` points, and without\n * serialization the second `BEGIN` collides with the first, silently dropping\n * the operation. Serializing every transaction through this mutex guarantees at\n * most one is active at a time.\n */\nexport class Mutex {\n\tprivate locked = false\n\tprivate readonly waiters: Array<() => void> = []\n\n\t/**\n\t * Acquire the mutex. Resolves with a release function once the lock is held.\n\t * If the mutex is already held, the caller waits (FIFO) until it is released.\n\t */\n\tasync acquire(): Promise<() => void> {\n\t\tif (!this.locked) {\n\t\t\tthis.locked = true\n\t\t\treturn this.createRelease()\n\t\t}\n\n\t\treturn new Promise<() => void>((resolve) => {\n\t\t\tthis.waiters.push(() => {\n\t\t\t\tresolve(this.createRelease())\n\t\t\t})\n\t\t})\n\t}\n\n\tprivate createRelease(): () => void {\n\t\tlet released = false\n\t\treturn () => {\n\t\t\tif (released) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\treleased = true\n\t\t\tconst next = this.waiters.shift()\n\t\t\tif (next) {\n\t\t\t\tnext()\n\t\t\t} else {\n\t\t\t\tthis.locked = false\n\t\t\t}\n\t\t}\n\t}\n}\n","/// <reference lib=\"dom\" />\nimport { WorkerTimeoutError } from '../errors'\n\n// === Message Protocol ===\n\n/**\n * Request message sent from the main thread to the SQLite WASM worker.\n * Each request carries a unique `id` for response correlation.\n */\nexport type WorkerRequest =\n\t| { id: number; type: 'open'; ddlStatements: string[]; dbName?: string }\n\t| { id: number; type: 'close' }\n\t| { id: number; type: 'execute'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'query'; sql: string; params?: unknown[] }\n\t| { id: number; type: 'begin' }\n\t| { id: number; type: 'commit' }\n\t| { id: number; type: 'rollback' }\n\t| { id: number; type: 'migrate'; from: number; to: number; statements: string[] }\n\t| { id: number; type: 'export' }\n\t| { id: number; type: 'import'; data: Uint8Array }\n\n/**\n * Response message sent from the worker back to the main thread.\n * Matches the request `id` for correlation.\n */\nexport type WorkerResponse =\n\t| { id: number; type: 'success'; data?: unknown }\n\t| { id: number; type: 'error'; message: string; code: string; context?: Record<string, unknown> }\n\n// === WorkerBridge Interface ===\n\n/**\n * Abstraction over the communication channel with the SQLite WASM worker.\n * In browsers, this is backed by a real Web Worker via MessagePort.\n * In Node.js tests, this is backed by better-sqlite3 via MockWorkerBridge.\n */\nexport interface WorkerBridge {\n\t/** Send a request to the worker and wait for a response. */\n\tsend(request: WorkerRequest, clientId?: string): Promise<WorkerResponse>\n\n\t/** Terminate the worker. Safe to call multiple times. */\n\tterminate(): void\n}\n\n// === Mutex ===\n\n// The Mutex now lives in its own module so non-browser adapters (better-sqlite3)\n// can serialize transactions without importing browser-worker code.\nexport { Mutex } from './mutex'\n\n// === WebWorkerBridge ===\n\n/**\n * WorkerBridge implementation for browser environments.\n * Communicates with an actual Web Worker running SQLite WASM.\n */\nexport class WebWorkerBridge implements WorkerBridge {\n\tprivate worker: Worker\n\tprivate pending = new Map<\n\t\tnumber,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate nextId = 1\n\tprivate terminated = false\n\tprivate timeoutMs: number\n\n\t/**\n\t * @param workerUrl - URL to the sqlite-wasm-worker script\n\t * @param timeoutMs - Timeout for worker responses in milliseconds (default: 30000)\n\t */\n\tconstructor(workerUrl: string | URL, timeoutMs = 30000) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.worker = new Worker(workerUrl, { type: 'module' })\n\t\tthis.worker.onmessage = (event: MessageEvent<WorkerResponse>) => {\n\t\t\tconst response = event.data\n\t\t\tconst entry = this.pending.get(response.id)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(response.id)\n\t\t\t\tentry.resolve(response)\n\t\t\t}\n\t\t}\n\t\tthis.worker.onerror = (event) => {\n\t\t\t// Reject all pending requests on worker error\n\t\t\tconst error = new Error(`Worker error: ${event.message}`)\n\t\t\tfor (const [id, entry] of this.pending) {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\tentry.reject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Worker has been terminated',\n\t\t\t\tcode: 'WORKER_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst id = this.nextId++\n\t\tconst req = { ...request, id }\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tthis.pending.delete(id)\n\t\t\t\treject(new WorkerTimeoutError(req.type, this.timeoutMs))\n\t\t\t}, this.timeoutMs)\n\n\t\t\tthis.pending.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\tresolve(response)\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(error)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tthis.worker.postMessage(req)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) return\n\t\tthis.terminated = true\n\t\tthis.worker.terminate()\n\t\t// Reject any pending requests\n\t\tfor (const [id, entry] of this.pending) {\n\t\t\tthis.pending.delete(id)\n\t\t\tentry.reject(new Error('Worker terminated'))\n\t\t}\n\t}\n}\n","// Entry point for @korajs/store/indexeddb\nexport { IndexedDbAdapter } from './indexeddb-adapter'\nexport type { IndexedDbAdapterOptions } from './indexeddb-adapter'\n","import type { KoraEventEmitter, SchemaDefinition } from '@korajs/core'\nimport { quoteIdent } from '@korajs/core'\nimport { AdapterError, 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\tdeleteSnapshotFromIndexedDB,\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) {\n\t\t\tawait this.restoreFromDumpFallback()\n\t\t\treturn\n\t\t}\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 dump = await this.exportDump()\n\t\tawait saveDumpToIndexedDB(this.dbName, dump)\n\n\t\ttry {\n\t\t\tconst data = await this.inner.exportDatabase()\n\t\t\tawait saveToIndexedDB(this.dbName, data)\n\t\t} catch (error) {\n\t\t\tif (!isUnsupportedWorkerExport(error)) {\n\t\t\t\tthrow error\n\t\t\t}\n\t\t\tawait deleteSnapshotFromIndexedDB(this.dbName)\n\t\t}\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\nfunction isUnsupportedWorkerExport(error: unknown): boolean {\n\treturn error instanceof AdapterError && error.context?.code === 'EXPORT_NOT_SUPPORTED'\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","import { generateFullDDL } from '@korajs/core'\nimport type { KoraEventEmitter, SchemaDefinition } from '@korajs/core'\nimport { AdapterError, StoreNotOpenError } from '../errors'\nimport {\n\tFollowerBroadcastBridge,\n\tTransactionSerializingWorkerBridge,\n\tacquireTabStorageSession,\n\tstartLeaderRpcRelay,\n} from '../multi-tab/tab-storage'\nimport type {\n\tMigrationPlan,\n\tStorageAdapter,\n\tStorageFallbackReason,\n\tStorageOpenState,\n\tTransaction,\n} from '../types'\nimport { Mutex } from './sqlite-wasm-channel'\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from './sqlite-wasm-channel'\n\ntype WorkerSuccessResponse = Extract<WorkerResponse, { type: 'success' }>\nlet warnedSharedWorkerDeprecated = false\n\n/**\n * Options for creating a SqliteWasmAdapter.\n */\nexport interface SqliteWasmAdapterOptions {\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 * Database name for persistence. Used as the OPFS file name or IDB key.\n\t */\n\tdbName?: string\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 * @deprecated SharedWorker-hosted SQLite cannot use OPFS SyncAccessHandle and\n\t * is never durable. Kora ignores this option and uses the dedicated-worker\n\t * leader/follower path for durable multi-tab storage.\n\t */\n\tsharedWorkerUrl?: string | URL\n\n\t/**\n\t * When false, the adapter records a non-persistent open without emitting\n\t * `store:opfs-unavailable`. `createApp()` uses this while it promotes the store\n\t * to durable IndexedDB; direct adapter users keep the diagnostic by default.\n\t */\n\temitNonPersistentDiagnostic?: boolean\n\n\t/**\n\t * When set, storage diagnostics are emitted here: `store:opfs-unavailable` when\n\t * persistence silently degraded to a non-persistent in-memory database, and\n\t * `store:db-name-collision` when another runtime on this origin was already\n\t * using this database name.\n\t */\n\temitter?: KoraEventEmitter\n}\n\n/**\n * SQLite WASM adapter that communicates with a SQLite instance through a WorkerBridge.\n *\n * In browsers, the bridge is backed by a Web Worker running SQLite WASM with OPFS persistence.\n * In Node.js tests, the bridge is backed by MockWorkerBridge wrapping better-sqlite3.\n *\n * @example\n * ```typescript\n * // Browser usage\n * const adapter = new SqliteWasmAdapter({ workerUrl: '/sqlite-wasm-worker.js' })\n *\n * // Test usage with MockWorkerBridge\n * import { MockWorkerBridge } from './sqlite-wasm-mock-bridge'\n * const adapter = new SqliteWasmAdapter({ bridge: new MockWorkerBridge() })\n * ```\n */\nexport class SqliteWasmAdapter implements StorageAdapter {\n\tprivate bridge: WorkerBridge | null = null\n\tprivate opened = false\n\tprivate readonly mutex = new Mutex()\n\tprivate readonly injectedBridge: WorkerBridge | undefined\n\tprivate readonly workerUrl: string | URL | undefined\n\tprivate readonly sharedWorkerUrl: string | URL | undefined\n\tprivate readonly workerResponseTimeoutMs: number\n\tprivate readonly dbName: string\n\tprivate readonly emitter: KoraEventEmitter | undefined\n\tprivate tabSession: Awaited<ReturnType<typeof acquireTabStorageSession>> | null = null\n\tprivate readonly emitNonPersistentDiagnostic: boolean\n\tprivate storageOpenState: StorageOpenState | null = null\n\t/** Retained so a follower promoted to leader can re-open its own worker. */\n\tprivate schema: SchemaDefinition | null = null\n\tprivate promoting = false\n\n\tconstructor(options: SqliteWasmAdapterOptions = {}) {\n\t\tthis.injectedBridge = options.bridge\n\t\tthis.workerUrl = options.workerUrl\n\t\tthis.sharedWorkerUrl = options.sharedWorkerUrl\n\t\tthis.workerResponseTimeoutMs = options.workerResponseTimeoutMs ?? 30_000\n\t\tthis.dbName = options.dbName ?? 'kora-db'\n\t\tthis.emitter = options.emitter\n\t\tthis.emitNonPersistentDiagnostic = options.emitNonPersistentDiagnostic ?? true\n\t}\n\n\tasync open(schema: SchemaDefinition): Promise<void> {\n\t\tif (this.opened) return\n\n\t\tconst ddlStatements = generateFullDDL(schema)\n\t\tif (this.injectedBridge) {\n\t\t\tthis.bridge = this.injectedBridge\n\t\t\tconst response = await this.openCurrentBridge(ddlStatements)\n\t\t\tthis.reportStorageMode(response.data)\n\t\t\tthis.opened = true\n\t\t\treturn\n\t\t}\n\t\tif (this.workerUrl) {\n\t\t\tthis.warnSharedWorkerDeprecated()\n\t\t\tawait this.openDurableWorkerBridge(schema)\n\t\t\tconst response = await this.openCurrentBridge(ddlStatements)\n\t\t\tthis.reportStorageMode(response.data)\n\t\t\tthis.opened = true\n\t\t\treturn\n\t\t}\n\t\tthrow new AdapterError(\n\t\t\t'SqliteWasmAdapter requires either a bridge (for testing) or a workerUrl (for browsers). ' +\n\t\t\t\t'Pass { bridge: new MockWorkerBridge() } for tests, or { workerUrl: \"/worker.js\" } for browsers.',\n\t\t)\n\t}\n\n\tprivate async openDurableWorkerBridge(schema: SchemaDefinition): Promise<void> {\n\t\tif (!this.workerUrl) {\n\t\t\tthrow new AdapterError('Durable SQLite WASM storage requires workerUrl.')\n\t\t}\n\n\t\tthis.schema = schema\n\t\tthis.tabSession = await acquireTabStorageSession(this.dbName, {\n\t\t\tonPromote: () => {\n\t\t\t\tvoid this.promoteToLeader()\n\t\t\t},\n\t\t})\n\t\tconst { WebWorkerBridge } = await import('./sqlite-wasm-channel')\n\n\t\tif (this.tabSession.role === 'leader') {\n\t\t\tconst workerBridge = new TransactionSerializingWorkerBridge(\n\t\t\t\tnew WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs),\n\t\t\t)\n\t\t\tthis.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge)\n\t\t\tthis.bridge = workerBridge\n\t\t\treturn\n\t\t}\n\n\t\tconst followerBridge = new FollowerBroadcastBridge(\n\t\t\tthis.tabSession.channelName,\n\t\t\tthis.workerResponseTimeoutMs,\n\t\t)\n\t\tthis.bridge = followerBridge\n\t\t// Another runtime on this origin already owns this database name. That is\n\t\t// expected for multiple tabs of the same app (they share one leader),\n\t\t// but a bug if these are logically separate apps, so surface it so the\n\t\t// developer can give them distinct store names.\n\t\tthis.emitter?.emit({\n\t\t\ttype: 'store:db-name-collision',\n\t\t\tdbName: this.dbName,\n\t\t\tmessage: `Another runtime on this origin is already using database \"${this.dbName}\"; this runtime is sharing it as a follower. If these are separate apps, give each a distinct store name.`,\n\t\t})\n\t\t// Readiness handshake: give the leader relay a moment to answer before the\n\t\t// first RPC, so a follower opened during a leader's startup race retries the\n\t\t// handshake instead of firing into the void and waiting out the full timeout.\n\t\tawait followerBridge.waitForLeader()\n\t}\n\n\tprivate async openCurrentBridge(ddlStatements: string[]): Promise<WorkerSuccessResponse> {\n\t\tconst response = await this.sendRequest({\n\t\t\tid: 0,\n\t\t\ttype: 'open',\n\t\t\tddlStatements,\n\t\t\tdbName: this.dbName,\n\t\t})\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Failed to open database: ${response.message}`, {\n\t\t\t\tcode: response.code,\n\t\t\t\tdbName: this.dbName,\n\t\t\t})\n\t\t}\n\t\treturn response\n\t}\n\n\t/**\n\t * Surface a `store:opfs-unavailable` diagnostic when the worker reported that\n\t * persistence silently degraded to an in-memory database, so a data-loss\n\t * condition is observable instead of failing silently. A persistent open (or a\n\t * bridge that does not report a mode, e.g. the Node mock) emits nothing.\n\t */\n\tprivate reportStorageMode(data: unknown): void {\n\t\tthis.storageOpenState = parseStorageOpenState(data)\n\t\tif (!this.emitNonPersistentDiagnostic || !this.emitter || !this.storageOpenState) {\n\t\t\treturn\n\t\t}\n\t\tif (this.storageOpenState.persistent === false) {\n\t\t\tconst reason = this.storageOpenState.fallbackReason ?? 'unsupported'\n\t\t\tthis.emitter.emit({\n\t\t\t\ttype: 'store:opfs-unavailable',\n\t\t\t\tdbName: this.dbName,\n\t\t\t\treason,\n\t\t\t\tmessage: `OPFS persistence is unavailable (${reason}) for database \"${this.dbName}\"; the store is running in memory and data will not survive a reload.`,\n\t\t\t})\n\t\t}\n\t}\n\n\tgetStorageOpenState(): StorageOpenState | null {\n\t\treturn this.storageOpenState\n\t}\n\n\tprivate warnSharedWorkerDeprecated(): void {\n\t\tif (!this.sharedWorkerUrl || warnedSharedWorkerDeprecated) {\n\t\t\treturn\n\t\t}\n\t\twarnedSharedWorkerDeprecated = true\n\t\tconsole.warn(\n\t\t\t'[kora] sharedWorkerUrl is deprecated and ignored: SharedWorker-hosted SQLite ' +\n\t\t\t\t'cannot use OPFS and is never durable. Kora uses the durable dedicated-worker ' +\n\t\t\t\t'leader/follower path for multi-tab storage.',\n\t\t)\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (!this.bridge) return\n\n\t\ttry {\n\t\t\tawait this.sendRequest({ id: 0, type: 'close' })\n\t\t} finally {\n\t\t\tthis.tabSession?.stopRelay?.()\n\t\t\tthis.tabSession?.cancelPromotionWatch?.()\n\t\t\tif (this.tabSession?.releaseLock) {\n\t\t\t\tawait this.tabSession.releaseLock()\n\t\t\t}\n\t\t\tthis.tabSession = null\n\t\t\tthis.bridge.terminate()\n\t\t\tthis.bridge = null\n\t\t\tthis.opened = false\n\t\t}\n\t}\n\n\t/**\n\t * Promotes a follower to leader after the previous leader released the storage\n\t * lock (its tab closed or crashed). The lock is only granted once the old leader\n\t * is gone, so opening our own worker here is safe from the OPFS single-writer\n\t * rule. In-flight follower RPCs are rejected and left to the caller / reactive\n\t * layer to retry against the new leader.\n\t */\n\tprivate async promoteToLeader(): Promise<void> {\n\t\tif (this.promoting || !this.opened || this.workerUrl === undefined || this.schema === null) {\n\t\t\treturn\n\t\t}\n\t\tthis.promoting = true\n\n\t\tconst { WebWorkerBridge } = await import('./sqlite-wasm-channel')\n\t\tconst workerBridge = new TransactionSerializingWorkerBridge(\n\t\t\tnew WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs),\n\t\t)\n\n\t\tconst previousBridge = this.bridge\n\t\tthis.bridge = workerBridge\n\t\tif (this.tabSession) {\n\t\t\tthis.tabSession.role = 'leader'\n\t\t\tthis.tabSession.stopRelay = startLeaderRpcRelay(this.tabSession.channelName, workerBridge)\n\t\t}\n\t\tpreviousBridge?.terminate()\n\n\t\t// Re-open against our own worker. DDL is idempotent, and the OPFS data the old\n\t\t// leader persisted is now readable by this worker.\n\t\tconst ddlStatements = generateFullDDL(this.schema)\n\t\tawait this.sendRequest({ id: 0, type: 'open', ddlStatements, dbName: this.dbName })\n\t}\n\n\tasync execute(sql: string, params?: unknown[]): Promise<void> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql, params })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Execute failed: ${response.message}`, { sql, params })\n\t\t}\n\t}\n\n\tasync query<T>(sql: string, params?: unknown[]): Promise<T[]> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'query', sql, params })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Query failed: ${response.message}`, { sql, params })\n\t\t}\n\t\treturn (response.data as T[]) ?? []\n\t}\n\n\tasync transaction(fn: (tx: Transaction) => Promise<void>): Promise<void> {\n\t\tthis.guardOpen()\n\n\t\tconst release = await this.mutex.acquire()\n\t\ttry {\n\t\t\tawait this.sendChecked({ id: 0, type: 'begin' }, 'BEGIN transaction')\n\n\t\t\tconst tx: Transaction = {\n\t\t\t\texecute: async (sql: string, params?: unknown[]): Promise<void> => {\n\t\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql, params })\n\t\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\t\tthrow new AdapterError(`Transaction execute failed: ${response.message}`, {\n\t\t\t\t\t\t\tsql,\n\t\t\t\t\t\t\tparams,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tquery: async <T>(sql: string, params?: unknown[]): Promise<T[]> => {\n\t\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'query', sql, params })\n\t\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\t\tthrow new AdapterError(`Transaction query failed: ${response.message}`, { sql, params })\n\t\t\t\t\t}\n\t\t\t\t\treturn (response.data as T[]) ?? []\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tawait fn(tx)\n\t\t\tawait this.sendChecked({ id: 0, type: 'commit' }, 'COMMIT transaction')\n\t\t} catch (error) {\n\t\t\t// Attempt rollback, but don't mask the original error\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest({ id: 0, type: 'rollback' })\n\t\t\t} catch {\n\t\t\t\t// Rollback failure is secondary to the original error\n\t\t\t}\n\t\t\tthrow error\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\tasync migrate(from: number, to: number, migration: MigrationPlan): Promise<void> {\n\t\tthis.guardOpen()\n\n\t\tconst release = await this.mutex.acquire()\n\t\ttry {\n\t\t\tawait this.sendChecked({ id: 0, type: 'begin' }, 'BEGIN migration')\n\n\t\t\tfor (const sql of migration.statements) {\n\t\t\t\tconst response = await this.sendRequest({ id: 0, type: 'execute', sql })\n\t\t\t\tif (response.type === 'error') {\n\t\t\t\t\tthrow new AdapterError(`Migration from v${from} to v${to} failed: ${response.message}`, {\n\t\t\t\t\t\tfrom,\n\t\t\t\t\t\tto,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait this.sendChecked({ id: 0, type: 'commit' }, 'COMMIT migration')\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait this.sendRequest({ id: 0, type: 'rollback' })\n\t\t\t} catch {\n\t\t\t\t// Rollback failure is secondary\n\t\t\t}\n\t\t\tif (error instanceof AdapterError) throw error\n\t\t\tthrow new AdapterError(\n\t\t\t\t`Migration from v${from} to v${to} failed: ${(error as Error).message}`,\n\t\t\t\t{ from, to },\n\t\t\t)\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\t/**\n\t * Export the database as a Uint8Array (for IndexedDB persistence).\n\t * Only available when the database is open.\n\t */\n\tasync exportDatabase(): Promise<Uint8Array> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'export' })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Export failed: ${response.message}`, {\n\t\t\t\tcode: response.code,\n\t\t\t\tcontext: response.context,\n\t\t\t})\n\t\t}\n\t\treturn response.data as Uint8Array\n\t}\n\n\t/**\n\t * Import a serialized database snapshot.\n\t */\n\tasync importDatabase(data: Uint8Array): Promise<void> {\n\t\tthis.guardOpen()\n\t\tconst response = await this.sendRequest({ id: 0, type: 'import', data })\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`Import failed: ${response.message}`)\n\t\t}\n\t}\n\n\tprivate guardOpen(): void {\n\t\tif (!this.opened || !this.bridge) {\n\t\t\tthrow new StoreNotOpenError()\n\t\t}\n\t}\n\n\tprivate async sendRequest(request: WorkerRequest): Promise<WorkerResponse> {\n\t\t// guardOpen() is always called before sendRequest, so bridge is guaranteed non-null\n\t\tconst bridge = this.bridge\n\t\tif (!bridge) {\n\t\t\tthrow new StoreNotOpenError()\n\t\t}\n\t\treturn bridge.send(request)\n\t}\n\n\tprivate async sendChecked(request: WorkerRequest, description: string): Promise<void> {\n\t\tconst response = await this.sendRequest(request)\n\t\tif (response.type === 'error') {\n\t\t\tthrow new AdapterError(`${description} failed: ${response.message}`)\n\t\t}\n\t}\n}\n\nfunction parseStorageOpenState(data: unknown): StorageOpenState | null {\n\tif (typeof data !== 'object' || data === null) {\n\t\treturn null\n\t}\n\tconst mode = data as {\n\t\tpersistent?: boolean\n\t\tfallbackReason?: StorageFallbackReason\n\t}\n\tif (typeof mode.persistent !== 'boolean') {\n\t\treturn null\n\t}\n\treturn {\n\t\tpersistent: mode.persistent,\n\t\tmode: mode.persistent ? 'opfs' : 'memory',\n\t\t...(mode.fallbackReason ? { fallbackReason: mode.fallbackReason } : {}),\n\t}\n}\n","/**\n * Multi-tab SQLite storage coordination via `navigator.locks` and `BroadcastChannel`.\n *\n * One tab holds the exclusive `kora-leader-${dbName}` lock and owns the SQLite worker.\n * Other tabs send worker RPC over a named broadcast channel.\n */\n\nimport type { WorkerBridge, WorkerRequest, WorkerResponse } from '../adapters/sqlite-wasm-channel'\nimport { NoLeaderError, WorkerTimeoutError } from '../errors'\n\nconst RPC_REQUEST = 'kora-worker-request'\nconst RPC_RESPONSE = 'kora-worker-response'\nconst CLIENT_LEAVE = 'kora-client-leave'\nconst LEADER_PING = 'kora-leader-ping'\nconst LEADER_PONG = 'kora-leader-pong'\n\n/** Default delay before a stalled follower RPC probes the leader for liveness. */\nconst DEFAULT_LIVENESS_PROBE_MS = 2000\n/** Default idle budget for a client that owns an open transaction span. */\nconst DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS = 10_000\n\ninterface RpcRequestMessage {\n\ttype: typeof RPC_REQUEST\n\trequestId: string\n\tclientId: string\n\trequest: WorkerRequest\n}\n\ninterface RpcResponseMessage {\n\ttype: typeof RPC_RESPONSE\n\trequestId: string\n\tresponse: WorkerResponse\n}\n\ninterface ClientLeaveMessage {\n\ttype: typeof CLIENT_LEAVE\n\tclientId: string\n}\n\ninterface ReclaimingWorkerBridge extends WorkerBridge {\n\treclaimClient(clientId: string, reason: string): void\n}\n\nexport type TabStorageRole = 'leader' | 'follower'\n\nexport interface AcquireTabStorageOptions {\n\t/**\n\t * Invoked when a follower is promoted to leader because the previous leader\n\t * released the lock (its tab closed or crashed). The adapter uses this to\n\t * rebuild its bridge as a leader. Never fires for a tab that started as leader.\n\t */\n\tonPromote?: () => void\n}\n\nexport interface TabStorageSession {\n\trole: TabStorageRole\n\tchannelName: string\n\t/** Leader only: release the navigator lock when closing the database. */\n\treleaseLock?: () => Promise<void>\n\t/** Leader only: stop the broadcast RPC relay. */\n\tstopRelay?: () => void\n\t/**\n\t * Follower only: resolves when this tab has been promoted to leader (the old\n\t * leader released the lock). The adapter awaits nothing here directly; it reacts\n\t * via {@link AcquireTabStorageOptions.onPromote}. Present so callers can cancel\n\t * the promotion watch on close.\n\t */\n\tcancelPromotionWatch?: () => void\n}\n\n/**\n * Returns whether multi-tab coordination APIs exist in this runtime.\n */\nexport function isMultiTabStorageSupported(): boolean {\n\treturn (\n\t\ttypeof globalThis !== 'undefined' &&\n\t\ttypeof BroadcastChannel !== 'undefined' &&\n\t\ttypeof navigator !== 'undefined' &&\n\t\ttypeof navigator.locks?.request === 'function'\n\t)\n}\n\n/**\n * Resolve leader vs follower for a database name.\n * Without lock APIs, every instance is treated as leader (single-tab / Node).\n *\n * A follower additionally queues a blocking request for the same lock. When the\n * current leader releases it (its tab closed or crashed), the browser grants the\n * lock to this follower and {@link AcquireTabStorageOptions.onPromote} fires so the\n * adapter can rebuild itself as the new leader. This is the automatic failover\n * path; a leader that is merely throttled (backgrounded but alive) keeps the lock,\n * and followers stay resilient through the liveness probe on the RPC bridge.\n */\nexport async function acquireTabStorageSession(\n\tdbName: string,\n\toptions?: AcquireTabStorageOptions,\n): Promise<TabStorageSession> {\n\tconst channelName = `kora-storage-${dbName}`\n\tconst lockName = `kora-leader-${dbName}`\n\n\tif (!isMultiTabStorageSupported()) {\n\t\treturn { role: 'leader', channelName }\n\t}\n\n\treturn new Promise<TabStorageSession>((resolve) => {\n\t\tlet releaseHeld: (() => void) | undefined\n\n\t\tvoid navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, (lock) => {\n\t\t\tif (lock === null) {\n\t\t\t\tresolve(startFollowerSession(channelName, lockName, options))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresolve({\n\t\t\t\trole: 'leader',\n\t\t\t\tchannelName,\n\t\t\t\treleaseLock: async () => {\n\t\t\t\t\treleaseHeld?.()\n\t\t\t\t},\n\t\t\t})\n\n\t\t\treturn new Promise<void>((release) => {\n\t\t\t\treleaseHeld = release\n\t\t\t})\n\t\t})\n\t})\n}\n\n/**\n * Builds a follower session and starts watching for promotion. The follower holds\n * a queued lock request; when it is finally granted (old leader gone), it becomes\n * the leader for the rest of its lifetime and notifies via `onPromote`.\n */\nfunction startFollowerSession(\n\tchannelName: string,\n\tlockName: string,\n\toptions?: AcquireTabStorageOptions,\n): TabStorageSession {\n\tlet promoted = false\n\tlet releasePromotedLock: (() => void) | undefined\n\tconst abort = new AbortController()\n\n\tvoid navigator.locks\n\t\t.request(lockName, { mode: 'exclusive', signal: abort.signal }, () => {\n\t\t\t// Reaching here means the previous leader released the lock and this tab\n\t\t\t// won it. Hold it (never-resolving promise) so this tab is now the leader.\n\t\t\tpromoted = true\n\t\t\toptions?.onPromote?.()\n\t\t\treturn new Promise<void>((release) => {\n\t\t\t\treleasePromotedLock = release\n\t\t\t})\n\t\t})\n\t\t.catch(() => {\n\t\t\t// AbortError when the tab closes before promotion. Nothing to do.\n\t\t})\n\n\treturn {\n\t\trole: 'follower',\n\t\tchannelName,\n\t\tcancelPromotionWatch: () => {\n\t\t\tif (promoted) {\n\t\t\t\treleasePromotedLock?.()\n\t\t\t} else {\n\t\t\t\tabort.abort()\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * Leader tab: forward follower RPC to the real worker bridge.\n */\nexport function startLeaderRpcRelay(channelName: string, bridge: WorkerBridge): () => void {\n\tconst channel = new BroadcastChannel(channelName)\n\n\tconst onMessage = (\n\t\tevent: MessageEvent<RpcRequestMessage | ClientLeaveMessage | { type: typeof LEADER_PING }>,\n\t): void => {\n\t\tconst data = event.data\n\t\t// Answer liveness probes immediately, without touching the worker, so a\n\t\t// follower can distinguish a slow-but-alive leader from an absent one.\n\t\tif (data?.type === LEADER_PING) {\n\t\t\tchannel.postMessage({ type: LEADER_PONG })\n\t\t\treturn\n\t\t}\n\t\tif (data?.type === CLIENT_LEAVE) {\n\t\t\tif (hasReclaimClient(bridge)) {\n\t\t\t\tbridge.reclaimClient(data.clientId, 'client-left')\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (data?.type !== RPC_REQUEST) {\n\t\t\treturn\n\t\t}\n\n\t\tvoid bridge\n\t\t\t.send(data.request, data.clientId)\n\t\t\t.then((response) => {\n\t\t\t\tconst msg: RpcResponseMessage = {\n\t\t\t\t\ttype: RPC_RESPONSE,\n\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\tresponse,\n\t\t\t\t}\n\t\t\t\tchannel.postMessage(msg)\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : 'Worker RPC failed'\n\t\t\t\tconst msg: RpcResponseMessage = {\n\t\t\t\t\ttype: RPC_RESPONSE,\n\t\t\t\t\trequestId: data.requestId,\n\t\t\t\t\tresponse: {\n\t\t\t\t\t\tid: data.request.id,\n\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t\tcode: 'LEADER_RPC_ERROR',\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tchannel.postMessage(msg)\n\t\t\t})\n\t}\n\n\tchannel.addEventListener('message', onMessage)\n\treturn () => {\n\t\tchannel.removeEventListener('message', onMessage)\n\t\tchannel.close()\n\t}\n}\n\n/**\n * Follower tab: proxy {@link WorkerBridge} over BroadcastChannel to the leader.\n */\nexport class FollowerBroadcastBridge implements WorkerBridge {\n\tprivate readonly channel: BroadcastChannel\n\tprivate readonly clientId = createClientId()\n\tprivate readonly onPageHide = (): void => {\n\t\tthis.leaveLeader()\n\t}\n\tprivate readonly pending = new Map<\n\t\tstring,\n\t\t{ resolve: (r: WorkerResponse) => void; reject: (e: Error) => void }\n\t>()\n\tprivate readonly timeoutMs: number\n\tprivate readonly livenessProbeMs: number\n\tprivate terminated = false\n\n\tconstructor(channelName: string, timeoutMs = 30000, livenessProbeMs = DEFAULT_LIVENESS_PROBE_MS) {\n\t\tthis.timeoutMs = timeoutMs\n\t\tthis.livenessProbeMs = Math.min(livenessProbeMs, timeoutMs)\n\t\tthis.channel = new BroadcastChannel(channelName)\n\t\tif (typeof addEventListener === 'function') {\n\t\t\taddEventListener('pagehide', this.onPageHide)\n\t\t}\n\t\tthis.channel.addEventListener('message', (event: MessageEvent<RpcResponseMessage>) => {\n\t\t\tconst data = event.data\n\t\t\tif (data?.type !== RPC_RESPONSE) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst entry = this.pending.get(data.requestId)\n\t\t\tif (entry) {\n\t\t\t\tthis.pending.delete(data.requestId)\n\t\t\t\tentry.resolve(data.response)\n\t\t\t}\n\t\t})\n\t}\n\n\t/**\n\t * Readiness handshake: resolves `true` as soon as a live leader answers a ping,\n\t * or `false` if none answers within the budget. The adapter calls this before\n\t * its first RPC so a follower created before a leader relay is live retries the\n\t * handshake instead of firing into the void and waiting out the full timeout.\n\t */\n\tasync waitForLeader(timeoutMs = 3000, attempts = 3): Promise<boolean> {\n\t\tconst perAttempt = Math.max(50, Math.floor(timeoutMs / Math.max(1, attempts)))\n\t\tfor (let attempt = 0; attempt < attempts; attempt += 1) {\n\t\t\tif (this.terminated) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (await this.pingLeader(perAttempt)) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\t/** Sends one liveness ping and resolves whether a leader answered in time. */\n\tprivate pingLeader(timeoutMs: number): Promise<boolean> {\n\t\tif (this.terminated) {\n\t\t\treturn Promise.resolve(false)\n\t\t}\n\t\treturn new Promise<boolean>((resolve) => {\n\t\t\tconst onPong = (event: MessageEvent<{ type: string }>): void => {\n\t\t\t\tif (event.data?.type === LEADER_PONG) {\n\t\t\t\t\tcleanup()\n\t\t\t\t\tresolve(true)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tthis.channel.removeEventListener('message', onPong)\n\t\t\t}\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tcleanup()\n\t\t\t\tresolve(false)\n\t\t\t}, timeoutMs)\n\t\t\tthis.channel.addEventListener('message', onPong)\n\t\t\tthis.channel.postMessage({ type: LEADER_PING })\n\t\t})\n\t}\n\n\tasync send(request: WorkerRequest): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn {\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Follower bridge terminated',\n\t\t\t\tcode: 'BRIDGE_TERMINATED',\n\t\t\t}\n\t\t}\n\n\t\tconst requestId = createClientId()\n\t\tconst msg: RpcRequestMessage = {\n\t\t\ttype: RPC_REQUEST,\n\t\t\trequestId,\n\t\t\tclientId: this.clientId,\n\t\t\trequest,\n\t\t}\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tconst settle = (fn: () => void): void => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tclearTimeout(probe)\n\t\t\t\tthis.pending.delete(requestId)\n\t\t\t\tfn()\n\t\t\t}\n\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tsettle(() => reject(new WorkerTimeoutError(`follower-rpc:${request.type}`, this.timeoutMs)))\n\t\t\t}, this.timeoutMs)\n\n\t\t\t// Liveness watchdog: if the request stalls, probe the leader. A confirmed\n\t\t\t// absent leader fails fast (NoLeaderError) instead of waiting out the full\n\t\t\t// timeout; a slow-but-alive leader keeps its remaining time.\n\t\t\tconst probe = setTimeout(() => {\n\t\t\t\tvoid this.pingLeader(this.livenessProbeMs).then((alive) => {\n\t\t\t\t\tif (!alive && this.pending.has(requestId)) {\n\t\t\t\t\t\tsettle(() => reject(new NoLeaderError(`follower-rpc:${request.type}`)))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}, this.livenessProbeMs)\n\n\t\t\tthis.pending.set(requestId, {\n\t\t\t\tresolve: (response) => settle(() => resolve(response)),\n\t\t\t\treject: (error) => settle(() => reject(error)),\n\t\t\t})\n\n\t\t\tthis.channel.postMessage(msg)\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tthis.terminated = true\n\t\tthis.leaveLeader()\n\t\tif (typeof removeEventListener === 'function') {\n\t\t\tremoveEventListener('pagehide', this.onPageHide)\n\t\t}\n\t\tthis.channel.close()\n\t\tfor (const [, entry] of this.pending) {\n\t\t\tentry.reject(new Error('Follower bridge terminated'))\n\t\t}\n\t\tthis.pending.clear()\n\t}\n\n\tprivate leaveLeader(): void {\n\t\ttry {\n\t\t\tconst message: ClientLeaveMessage = {\n\t\t\t\ttype: CLIENT_LEAVE,\n\t\t\t\tclientId: this.clientId,\n\t\t\t}\n\t\t\tthis.channel.postMessage(message)\n\t\t} catch {\n\t\t\t// Best effort: the serializer's idle rollback is the guaranteed backstop.\n\t\t}\n\t}\n}\n\ninterface QueuedWorkerRequest {\n\tclientId: string\n\trequest: WorkerRequest\n\tresolve: (response: WorkerResponse) => void\n\treject: (error: Error) => void\n}\n\n/**\n * Serializes one SQLite worker across the leader tab and all follower tabs.\n *\n * SQLite transactions are represented as multiple worker messages\n * (`begin`, one or more reads/writes, then `commit`/`rollback`). Per-tab mutexes\n * cannot protect that span because follower messages converge at the leader.\n * This bridge promotes the worker boundary into the serialization point, so no\n * other client can interleave while a client owns an active transaction.\n */\nexport class TransactionSerializingWorkerBridge implements WorkerBridge {\n\tprivate readonly inner: WorkerBridge\n\tprivate readonly leaderClientId = createClientId()\n\tprivate readonly transactionIdleTimeoutMs: number\n\tprivate queue: QueuedWorkerRequest[] = []\n\tprivate activeTransactionClient: string | null = null\n\tprivate abortedClients = new Set<string>()\n\tprivate transactionIdleTimer: ReturnType<typeof setTimeout> | null = null\n\tprivate reclaiming = false\n\tprivate processing = false\n\tprivate terminated = false\n\n\tconstructor(inner: WorkerBridge, transactionIdleTimeoutMs = DEFAULT_TRANSACTION_IDLE_TIMEOUT_MS) {\n\t\tthis.inner = inner\n\t\tthis.transactionIdleTimeoutMs = transactionIdleTimeoutMs\n\t}\n\n\tsend(request: WorkerRequest, clientId = this.leaderClientId): Promise<WorkerResponse> {\n\t\tif (this.terminated) {\n\t\t\treturn Promise.resolve({\n\t\t\t\tid: request.id,\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: 'Worker has been terminated',\n\t\t\t\tcode: 'WORKER_TERMINATED',\n\t\t\t})\n\t\t}\n\t\tif (this.abortedClients.has(clientId)) {\n\t\t\tif (request.type === 'begin') {\n\t\t\t\tthis.abortedClients.delete(clientId)\n\t\t\t} else {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tid: request.id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: 'Previous transaction was aborted because the client stopped sending requests.',\n\t\t\t\t\tcode: 'TRANSACTION_ABORTED',\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\treturn new Promise<WorkerResponse>((resolve, reject) => {\n\t\t\tthis.queue.push({ clientId, request, resolve, reject })\n\t\t\tvoid this.processQueue()\n\t\t})\n\t}\n\n\tterminate(): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tthis.terminated = true\n\t\tthis.clearTransactionIdleTimer()\n\t\tthis.inner.terminate()\n\t\tconst pending = this.queue.splice(0)\n\t\tfor (const entry of pending) {\n\t\t\tentry.reject(new Error('Worker terminated'))\n\t\t}\n\t}\n\n\treclaimClient(clientId: string, reason: string): void {\n\t\tif (this.terminated) {\n\t\t\treturn\n\t\t}\n\t\tvoid this.reclaimClientNow(clientId, reason)\n\t}\n\n\tprivate async processQueue(): Promise<void> {\n\t\tif (this.processing || this.reclaiming) {\n\t\t\treturn\n\t\t}\n\t\tthis.processing = true\n\t\ttry {\n\t\t\twhile (!this.terminated) {\n\t\t\t\tconst index = this.nextRunnableIndex()\n\t\t\t\tif (index === -1) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst [entry] = this.queue.splice(index, 1)\n\t\t\t\tif (!entry) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await this.inner.send(entry.request)\n\t\t\t\t\tthis.recordTransactionState(entry, response)\n\t\t\t\t\tentry.resolve(response)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.resetFailedTransaction(entry)\n\t\t\t\t\tentry.reject(error instanceof Error ? error : new Error(String(error)))\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.processing = false\n\t\t\tif (!this.terminated && !this.reclaiming && this.nextRunnableIndex() !== -1) {\n\t\t\t\tvoid this.processQueue()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate nextRunnableIndex(): number {\n\t\tif (this.queue.length === 0) {\n\t\t\treturn -1\n\t\t}\n\t\tif (this.activeTransactionClient === null) {\n\t\t\treturn 0\n\t\t}\n\t\treturn this.queue.findIndex((entry) => entry.clientId === this.activeTransactionClient)\n\t}\n\n\tprivate recordTransactionState(entry: QueuedWorkerRequest, response: WorkerResponse): void {\n\t\tif (response.type === 'error') {\n\t\t\tthis.resetFailedTransaction(entry)\n\t\t\treturn\n\t\t}\n\t\tif (entry.request.type === 'begin') {\n\t\t\tthis.activeTransactionClient = entry.clientId\n\t\t\tthis.armTransactionIdleTimer(entry.clientId)\n\t\t\treturn\n\t\t}\n\t\tif (this.activeTransactionClient === entry.clientId) {\n\t\t\tthis.armTransactionIdleTimer(entry.clientId)\n\t\t}\n\t\tif (\n\t\t\tthis.activeTransactionClient === entry.clientId &&\n\t\t\t(entry.request.type === 'commit' || entry.request.type === 'rollback')\n\t\t) {\n\t\t\tthis.activeTransactionClient = null\n\t\t\tthis.clearTransactionIdleTimer()\n\t\t}\n\t}\n\n\tprivate resetFailedTransaction(entry: QueuedWorkerRequest): void {\n\t\tif (this.activeTransactionClient === entry.clientId) {\n\t\t\tthis.activeTransactionClient = null\n\t\t\tthis.clearTransactionIdleTimer()\n\t\t}\n\t}\n\n\tprivate armTransactionIdleTimer(clientId: string): void {\n\t\tthis.clearTransactionIdleTimer()\n\t\tthis.transactionIdleTimer = setTimeout(() => {\n\t\t\tvoid this.reclaimClientNow(clientId, 'transaction-idle-timeout')\n\t\t}, this.transactionIdleTimeoutMs)\n\t}\n\n\tprivate clearTransactionIdleTimer(): void {\n\t\tif (this.transactionIdleTimer) {\n\t\t\tclearTimeout(this.transactionIdleTimer)\n\t\t\tthis.transactionIdleTimer = null\n\t\t}\n\t}\n\n\tprivate async reclaimClientNow(clientId: string, reason: string): Promise<void> {\n\t\tif (this.reclaiming || this.activeTransactionClient !== clientId) {\n\t\t\treturn\n\t\t}\n\t\tthis.reclaiming = true\n\t\tthis.clearTransactionIdleTimer()\n\t\tthis.activeTransactionClient = null\n\t\tthis.abortedClients.add(clientId)\n\t\tthis.rejectQueuedClientRequests(clientId, reason)\n\n\t\ttry {\n\t\t\tconst response = await this.inner.send({ id: 0, type: 'rollback' })\n\t\t\tif (response.type === 'error') {\n\t\t\t\t// If there was no active SQLite transaction left, the important state\n\t\t\t\t// is already reclaimed in JS. The next request will establish a fresh\n\t\t\t\t// transaction if needed.\n\t\t\t}\n\t\t} catch {\n\t\t\t// Keep the queue moving. The inner worker will surface any unrecoverable\n\t\t\t// state through subsequent requests.\n\t\t} finally {\n\t\t\tthis.reclaiming = false\n\t\t\tif (!this.terminated && this.nextRunnableIndex() !== -1) {\n\t\t\t\tvoid this.processQueue()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate rejectQueuedClientRequests(clientId: string, reason: string): void {\n\t\tconst keep: QueuedWorkerRequest[] = []\n\t\tfor (const entry of this.queue) {\n\t\t\tif (entry.clientId === clientId) {\n\t\t\t\tentry.resolve({\n\t\t\t\t\tid: entry.request.id,\n\t\t\t\t\ttype: 'error',\n\t\t\t\t\tmessage: `Transaction client was reclaimed: ${reason}`,\n\t\t\t\t\tcode: 'TRANSACTION_ABORTED',\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tkeep.push(entry)\n\t\t\t}\n\t\t}\n\t\tthis.queue = keep\n\t}\n}\n\nfunction hasReclaimClient(bridge: WorkerBridge): bridge is ReclaimingWorkerBridge {\n\treturn typeof (bridge as Partial<ReclaimingWorkerBridge>).reclaimClient === 'function'\n}\n\nfunction createClientId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID()\n\t}\n\treturn `${Date.now()}-${Math.random().toString(36).slice(2)}`\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 only the binary SQLite database snapshot from IndexedDB.\n *\n * The logical dump is stored under a separate key and may be the authoritative\n * durable fallback when a browser worker cannot export a binary SQLite image.\n */\nexport async function deleteSnapshotFromIndexedDB(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\ttx.oncomplete = () => resolve()\n\t\t\ttx.onerror = () =>\n\t\t\t\treject(\n\t\t\t\t\tnew PersistenceError(`Failed to delete database snapshot \"${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":["import_core","import_core","WebWorkerBridge"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KoraEventEmitter, SchemaDefinition } from '@korajs/core';
|
|
2
|
-
import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-
|
|
3
|
-
import { W as WorkerBridge } from '../sqlite-wasm-channel-
|
|
2
|
+
import { S as StorageAdapter, T as Transaction, M as MigrationPlan, a as StorageOpenState } from '../types-BslDOTk0.cjs';
|
|
3
|
+
import { W as WorkerBridge } from '../sqlite-wasm-channel-L6o3bWQK.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Options for creating an IndexedDbAdapter.
|
|
@@ -49,6 +49,7 @@ declare class IndexedDbAdapter implements StorageAdapter {
|
|
|
49
49
|
private readonly dbName;
|
|
50
50
|
private readonly emitter;
|
|
51
51
|
private readonly scheduler;
|
|
52
|
+
private storageOpenState;
|
|
52
53
|
constructor(options?: IndexedDbAdapterOptions);
|
|
53
54
|
open(schema: SchemaDefinition): Promise<void>;
|
|
54
55
|
close(): Promise<void>;
|
|
@@ -56,6 +57,7 @@ declare class IndexedDbAdapter implements StorageAdapter {
|
|
|
56
57
|
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
57
58
|
transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
|
|
58
59
|
migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
|
|
60
|
+
getStorageOpenState(): StorageOpenState | null;
|
|
59
61
|
/**
|
|
60
62
|
* Force an immediate snapshot write to IndexedDB (skips debounce).
|
|
61
63
|
* Useful before tab unload or in tests.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KoraEventEmitter, SchemaDefinition } from '@korajs/core';
|
|
2
|
-
import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-
|
|
3
|
-
import { W as WorkerBridge } from '../sqlite-wasm-channel-
|
|
2
|
+
import { S as StorageAdapter, T as Transaction, M as MigrationPlan, a as StorageOpenState } from '../types-BslDOTk0.js';
|
|
3
|
+
import { W as WorkerBridge } from '../sqlite-wasm-channel-L6o3bWQK.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Options for creating an IndexedDbAdapter.
|
|
@@ -49,6 +49,7 @@ declare class IndexedDbAdapter implements StorageAdapter {
|
|
|
49
49
|
private readonly dbName;
|
|
50
50
|
private readonly emitter;
|
|
51
51
|
private readonly scheduler;
|
|
52
|
+
private storageOpenState;
|
|
52
53
|
constructor(options?: IndexedDbAdapterOptions);
|
|
53
54
|
open(schema: SchemaDefinition): Promise<void>;
|
|
54
55
|
close(): Promise<void>;
|
|
@@ -56,6 +57,7 @@ declare class IndexedDbAdapter implements StorageAdapter {
|
|
|
56
57
|
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
57
58
|
transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
|
|
58
59
|
migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
|
|
60
|
+
getStorageOpenState(): StorageOpenState | null;
|
|
59
61
|
/**
|
|
60
62
|
* Force an immediate snapshot write to IndexedDB (skips debounce).
|
|
61
63
|
* Useful before tab unload or in tests.
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SqliteWasmAdapter
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-RJIEKEN4.js";
|
|
4
|
+
import "../chunk-VDBJQ75K.js";
|
|
5
5
|
import "../chunk-TVJEC5S2.js";
|
|
6
6
|
import {
|
|
7
|
+
AdapterError,
|
|
7
8
|
PersistenceError
|
|
8
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-TUR7RGEP.js";
|
|
10
|
+
|
|
11
|
+
// src/adapters/indexeddb-adapter.ts
|
|
12
|
+
import { quoteIdent } from "@korajs/core";
|
|
9
13
|
|
|
10
14
|
// src/adapters/indexeddb-persistence-scheduler.ts
|
|
11
15
|
var DEFAULT_DEBOUNCE_MS = 500;
|
|
@@ -214,6 +218,24 @@ async function loadDumpFromIndexedDB(dbName) {
|
|
|
214
218
|
idb.close();
|
|
215
219
|
}
|
|
216
220
|
}
|
|
221
|
+
async function deleteSnapshotFromIndexedDB(dbName) {
|
|
222
|
+
const idb = await openIdb();
|
|
223
|
+
try {
|
|
224
|
+
await new Promise((resolve, reject) => {
|
|
225
|
+
const tx = idb.transaction(IDB_STORE_NAME, "readwrite");
|
|
226
|
+
const store = tx.objectStore(IDB_STORE_NAME);
|
|
227
|
+
store.delete(dbName);
|
|
228
|
+
tx.oncomplete = () => resolve();
|
|
229
|
+
tx.onerror = () => reject(
|
|
230
|
+
new PersistenceError(`Failed to delete database snapshot "${dbName}" from IndexedDB`, {
|
|
231
|
+
dbName
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
} finally {
|
|
236
|
+
idb.close();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
217
239
|
|
|
218
240
|
// src/adapters/indexeddb-adapter.ts
|
|
219
241
|
var IndexedDbAdapter = class {
|
|
@@ -221,6 +243,7 @@ var IndexedDbAdapter = class {
|
|
|
221
243
|
dbName;
|
|
222
244
|
emitter;
|
|
223
245
|
scheduler;
|
|
246
|
+
storageOpenState = null;
|
|
224
247
|
constructor(options = {}) {
|
|
225
248
|
this.dbName = options.dbName ?? "kora-db";
|
|
226
249
|
this.emitter = options.emitter;
|
|
@@ -238,8 +261,12 @@ var IndexedDbAdapter = class {
|
|
|
238
261
|
}
|
|
239
262
|
async open(schema) {
|
|
240
263
|
await this.inner.open(schema);
|
|
264
|
+
this.storageOpenState = { persistent: true, mode: "indexeddb" };
|
|
241
265
|
const persisted = await loadFromIndexedDB(this.dbName);
|
|
242
|
-
if (!persisted)
|
|
266
|
+
if (!persisted) {
|
|
267
|
+
await this.restoreFromDumpFallback();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
243
270
|
try {
|
|
244
271
|
await this.inner.importDatabase(persisted);
|
|
245
272
|
} catch {
|
|
@@ -266,6 +293,9 @@ var IndexedDbAdapter = class {
|
|
|
266
293
|
await this.inner.migrate(from, to, migration);
|
|
267
294
|
this.scheduler.schedule();
|
|
268
295
|
}
|
|
296
|
+
getStorageOpenState() {
|
|
297
|
+
return this.storageOpenState;
|
|
298
|
+
}
|
|
269
299
|
/**
|
|
270
300
|
* Force an immediate snapshot write to IndexedDB (skips debounce).
|
|
271
301
|
* Useful before tab unload or in tests.
|
|
@@ -274,10 +304,17 @@ var IndexedDbAdapter = class {
|
|
|
274
304
|
await this.scheduler.flushNow();
|
|
275
305
|
}
|
|
276
306
|
async writeSnapshot() {
|
|
277
|
-
const data = await this.inner.exportDatabase();
|
|
278
|
-
await saveToIndexedDB(this.dbName, data);
|
|
279
307
|
const dump = await this.exportDump();
|
|
280
308
|
await saveDumpToIndexedDB(this.dbName, dump);
|
|
309
|
+
try {
|
|
310
|
+
const data = await this.inner.exportDatabase();
|
|
311
|
+
await saveToIndexedDB(this.dbName, data);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (!isUnsupportedWorkerExport(error)) {
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
await deleteSnapshotFromIndexedDB(this.dbName);
|
|
317
|
+
}
|
|
281
318
|
}
|
|
282
319
|
handlePersistenceError(error) {
|
|
283
320
|
const message = error instanceof Error ? error.message : "IndexedDB persistence failed";
|
|
@@ -302,7 +339,7 @@ var IndexedDbAdapter = class {
|
|
|
302
339
|
if (!dump) return;
|
|
303
340
|
for (const table of dump.tables) {
|
|
304
341
|
const name = ensureSafeIdentifier(table.name);
|
|
305
|
-
await this.inner.execute(`DELETE FROM ${name}`);
|
|
342
|
+
await this.inner.execute(`DELETE FROM ${quoteIdent(name)}`);
|
|
306
343
|
if (table.rows.length === 0) continue;
|
|
307
344
|
for (const row of table.rows) {
|
|
308
345
|
const columns = table.columns.filter(
|
|
@@ -310,10 +347,10 @@ var IndexedDbAdapter = class {
|
|
|
310
347
|
);
|
|
311
348
|
if (columns.length === 0) continue;
|
|
312
349
|
const placeholders = columns.map(() => "?").join(", ");
|
|
313
|
-
const quotedColumns = columns.map((column) => ensureSafeIdentifier(column)).join(", ");
|
|
350
|
+
const quotedColumns = columns.map((column) => quoteIdent(ensureSafeIdentifier(column))).join(", ");
|
|
314
351
|
const values = columns.map((column) => row[column]);
|
|
315
352
|
await this.inner.execute(
|
|
316
|
-
`INSERT INTO ${name} (${quotedColumns}) VALUES (${placeholders})`,
|
|
353
|
+
`INSERT INTO ${quoteIdent(name)} (${quotedColumns}) VALUES (${placeholders})`,
|
|
317
354
|
values
|
|
318
355
|
);
|
|
319
356
|
}
|
|
@@ -326,9 +363,13 @@ var IndexedDbAdapter = class {
|
|
|
326
363
|
const tables = [];
|
|
327
364
|
for (const tableRow of tableRows) {
|
|
328
365
|
const tableName = ensureSafeIdentifier(tableRow.name);
|
|
329
|
-
const columns = await this.inner.query(
|
|
366
|
+
const columns = await this.inner.query(
|
|
367
|
+
`PRAGMA table_info(${quoteIdent(tableName)})`
|
|
368
|
+
);
|
|
330
369
|
const columnNames = columns.map((column) => column.name);
|
|
331
|
-
const rows = await this.inner.query(
|
|
370
|
+
const rows = await this.inner.query(
|
|
371
|
+
`SELECT * FROM ${quoteIdent(tableName)}`
|
|
372
|
+
);
|
|
332
373
|
tables.push({
|
|
333
374
|
name: tableName,
|
|
334
375
|
columns: columnNames,
|
|
@@ -347,6 +388,9 @@ function ensureSafeIdentifier(identifier) {
|
|
|
347
388
|
}
|
|
348
389
|
return identifier;
|
|
349
390
|
}
|
|
391
|
+
function isUnsupportedWorkerExport(error) {
|
|
392
|
+
return error instanceof AdapterError && error.context?.code === "EXPORT_NOT_SUPPORTED";
|
|
393
|
+
}
|
|
350
394
|
export {
|
|
351
395
|
IndexedDbAdapter
|
|
352
396
|
};
|