@korajs/store 0.4.0 → 0.5.0

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.
Files changed (69) hide show
  1. package/dist/adapters/better-sqlite3.d.cts +1 -1
  2. package/dist/adapters/better-sqlite3.d.ts +1 -1
  3. package/dist/adapters/indexeddb.cjs +410 -17
  4. package/dist/adapters/indexeddb.cjs.map +1 -1
  5. package/dist/adapters/indexeddb.d.cts +23 -5
  6. package/dist/adapters/indexeddb.d.ts +23 -5
  7. package/dist/adapters/indexeddb.js +163 -18
  8. package/dist/adapters/indexeddb.js.map +1 -1
  9. package/dist/adapters/sqlite-wasm-shared-host.cjs +68 -0
  10. package/dist/adapters/sqlite-wasm-shared-host.cjs.map +1 -0
  11. package/dist/adapters/sqlite-wasm-shared-host.d.cts +2 -0
  12. package/dist/adapters/sqlite-wasm-shared-host.d.ts +2 -0
  13. package/dist/adapters/sqlite-wasm-shared-host.js +66 -0
  14. package/dist/adapters/sqlite-wasm-shared-host.js.map +1 -0
  15. package/dist/adapters/sqlite-wasm-worker.cjs +34 -5
  16. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  17. package/dist/adapters/sqlite-wasm-worker.js +34 -5
  18. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  19. package/dist/adapters/sqlite-wasm.cjs +249 -2
  20. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  21. package/dist/adapters/sqlite-wasm.d.cts +15 -3
  22. package/dist/adapters/sqlite-wasm.d.ts +15 -3
  23. package/dist/adapters/sqlite-wasm.js +2 -2
  24. package/dist/audit-trace-store-AM4EWQNZ.js +15 -0
  25. package/dist/backup-JN3LB4YT.js +14 -0
  26. package/dist/backup-JN3LB4YT.js.map +1 -0
  27. package/dist/chunk-56LL4XP6.js +205 -0
  28. package/dist/chunk-56LL4XP6.js.map +1 -0
  29. package/dist/chunk-6AHYGQQT.js +425 -0
  30. package/dist/chunk-6AHYGQQT.js.map +1 -0
  31. package/dist/chunk-7BPETY5Y.js +570 -0
  32. package/dist/chunk-7BPETY5Y.js.map +1 -0
  33. package/dist/chunk-AGN3CIPB.js +188 -0
  34. package/dist/chunk-AGN3CIPB.js.map +1 -0
  35. package/dist/chunk-EZSTD2XC.js +380 -0
  36. package/dist/chunk-EZSTD2XC.js.map +1 -0
  37. package/dist/chunk-MYSJN3DF.js +117 -0
  38. package/dist/chunk-MYSJN3DF.js.map +1 -0
  39. package/dist/{chunk-DXKLAQ6P.js → chunk-XO4LMPAH.js} +1 -1
  40. package/dist/chunk-XO4LMPAH.js.map +1 -0
  41. package/dist/export-audit-5GV6VRDZ.js +15 -0
  42. package/dist/export-audit-5GV6VRDZ.js.map +1 -0
  43. package/dist/index.cjs +2183 -587
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +423 -187
  46. package/dist/index.d.ts +423 -187
  47. package/dist/index.js +886 -726
  48. package/dist/index.js.map +1 -1
  49. package/dist/internal.cjs +789 -0
  50. package/dist/internal.cjs.map +1 -0
  51. package/dist/internal.d.cts +154 -0
  52. package/dist/internal.d.ts +154 -0
  53. package/dist/internal.js +48 -0
  54. package/dist/internal.js.map +1 -0
  55. package/dist/{sqlite-wasm-channel-46AOWNPM.js → sqlite-wasm-channel-HDDW6UBH.js} +2 -2
  56. package/dist/sqlite-wasm-channel-HDDW6UBH.js.map +1 -0
  57. package/dist/{sqlite-wasm-channel-Lakjuk2E.d.cts → sqlite-wasm-channel-I9y_jObg.d.cts} +1 -0
  58. package/dist/{sqlite-wasm-channel-Lakjuk2E.d.ts → sqlite-wasm-channel-I9y_jObg.d.ts} +1 -0
  59. package/dist/types-BMyHmwvn.d.ts +163 -0
  60. package/dist/types-CTr00g_d.d.cts +163 -0
  61. package/dist/types-DepnRDk_.d.cts +212 -0
  62. package/dist/types-DepnRDk_.d.ts +212 -0
  63. package/package.json +23 -2
  64. package/dist/chunk-DXKLAQ6P.js.map +0 -1
  65. package/dist/chunk-ZP5AXQ3Z.js +0 -179
  66. package/dist/chunk-ZP5AXQ3Z.js.map +0 -1
  67. package/dist/types-DF-KDSK1.d.cts +0 -106
  68. package/dist/types-DF-KDSK1.d.ts +0 -106
  69. /package/dist/{sqlite-wasm-channel-46AOWNPM.js.map → audit-trace-store-AM4EWQNZ.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/errors.ts","../../src/adapters/sqlite-wasm-channel.ts","../../src/adapters/indexeddb.ts","../../src/adapters/indexeddb-adapter.ts","../../src/adapters/sqlite-wasm-adapter.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 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","/// <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[] }\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/**\n * Async mutex for serializing transaction access across the async worker boundary.\n * Only one transaction may be active at a time.\n */\nexport class Mutex {\n\tprivate locked = false\n\tprivate waiters: Array<() => void> = []\n\n\t/**\n\t * Acquire the mutex. Returns a release function.\n\t * If the mutex is already held, the caller waits until it's 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) return\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\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 { SchemaDefinition } from '@korajs/core'\nimport { AdapterError } from '../errors'\nimport type { MigrationPlan, StorageAdapter, Transaction } from '../types'\nimport { SqliteWasmAdapter } from './sqlite-wasm-adapter'\nimport type { WorkerBridge } from './sqlite-wasm-channel'\nimport {\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\n/**\n * IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes\n * the entire database to IndexedDB after each transaction.\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\n\tconstructor(options: IndexedDbAdapterOptions = {}) {\n\t\tthis.dbName = options.dbName ?? 'kora-db'\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})\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.persistSnapshot()\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\tawait this.persistSnapshot()\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\tawait this.persistSnapshot()\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\tawait this.persistSnapshot()\n\t}\n\n\tprivate async persistSnapshot(): Promise<void> {\n\t\ttry {\n\t\t\tconst data = await this.inner.exportDatabase()\n\t\t\tawait saveToIndexedDB(this.dbName, data)\n\t\t\tconst dump = await this.exportDump()\n\t\t\tawait saveDumpToIndexedDB(this.dbName, dump)\n\t\t} catch {\n\t\t\t// Non-fatal persistence failure. Next write/close retries persistence.\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 AdapterError(`Unsafe SQL identifier: ${identifier}`)\n\t}\n\treturn identifier\n}\n","import { generateFullDDL } from '@korajs/core'\nimport type { SchemaDefinition } from '@korajs/core'\nimport { AdapterError, StoreNotOpenError } from '../errors'\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\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 dbName: string\n\n\tconstructor(options: SqliteWasmAdapterOptions = {}) {\n\t\tthis.injectedBridge = options.bridge\n\t\tthis.workerUrl = options.workerUrl\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.workerUrl) {\n\t\t\t// Dynamic import to avoid loading WebWorkerBridge in Node.js\n\t\t\tconst { WebWorkerBridge } = await import('./sqlite-wasm-channel')\n\t\t\tthis.bridge = new WebWorkerBridge(this.workerUrl)\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({ id: 0, type: 'open', ddlStatements })\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.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 { 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 * 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 = () =>\n\t\t\t\treject(new PersistenceError(`Failed to save database \"${dbName}\" to IndexedDB`, { dbName }))\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 = () =>\n\t\t\t\treject(new PersistenceError(`Failed to save dump for database \"${dbName}\"`, { dbName }))\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/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 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","/// <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/**\n * Async mutex for serializing transaction access across the async worker boundary.\n * Only one transaction may be active at a time.\n */\nexport class Mutex {\n\tprivate locked = false\n\tprivate waiters: Array<() => void> = []\n\n\t/**\n\t * Acquire the mutex. Returns a release function.\n\t * If the mutex is already held, the caller waits until it's 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) return\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\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,6 +1,6 @@
1
- import { SchemaDefinition } from '@korajs/core';
2
- import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-DF-KDSK1.cjs';
3
- import { W as WorkerBridge } from '../sqlite-wasm-channel-Lakjuk2E.cjs';
1
+ import { KoraEventEmitter, SchemaDefinition } from '@korajs/core';
2
+ import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-DepnRDk_.cjs';
3
+ import { W as WorkerBridge } from '../sqlite-wasm-channel-I9y_jObg.cjs';
4
4
 
5
5
  /**
6
6
  * Options for creating an IndexedDbAdapter.
@@ -20,10 +20,20 @@ interface IndexedDbAdapterOptions {
20
20
  * URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.
21
21
  */
22
22
  workerUrl?: string | URL;
23
+ /** Timeout for worker / follower RPC responses. Defaults to 30000ms. */
24
+ workerResponseTimeoutMs?: number;
25
+ /**
26
+ * Debounce interval (ms) before writing snapshots to IndexedDB. Defaults to 500.
27
+ */
28
+ persistenceDebounceMs?: number;
29
+ /**
30
+ * When set, persistence failures and quota errors are emitted on this emitter.
31
+ */
32
+ emitter?: KoraEventEmitter;
23
33
  }
24
34
  /**
25
35
  * IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes
26
- * the entire database to IndexedDB after each transaction.
36
+ * the entire database to IndexedDB after mutations (coalesced/debounced).
27
37
  *
28
38
  * This is the fallback adapter for browsers where OPFS is not available.
29
39
  * It provides the same SQL interface as SqliteWasmAdapter, but persists by
@@ -37,6 +47,8 @@ interface IndexedDbAdapterOptions {
37
47
  declare class IndexedDbAdapter implements StorageAdapter {
38
48
  private inner;
39
49
  private readonly dbName;
50
+ private readonly emitter;
51
+ private readonly scheduler;
40
52
  constructor(options?: IndexedDbAdapterOptions);
41
53
  open(schema: SchemaDefinition): Promise<void>;
42
54
  close(): Promise<void>;
@@ -44,7 +56,13 @@ declare class IndexedDbAdapter implements StorageAdapter {
44
56
  query<T>(sql: string, params?: unknown[]): Promise<T[]>;
45
57
  transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
46
58
  migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
47
- private persistSnapshot;
59
+ /**
60
+ * Force an immediate snapshot write to IndexedDB (skips debounce).
61
+ * Useful before tab unload or in tests.
62
+ */
63
+ flushPersistence(): Promise<void>;
64
+ private writeSnapshot;
65
+ private handlePersistenceError;
48
66
  private restoreFromDumpFallback;
49
67
  private exportDump;
50
68
  }
@@ -1,6 +1,6 @@
1
- import { SchemaDefinition } from '@korajs/core';
2
- import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-DF-KDSK1.js';
3
- import { W as WorkerBridge } from '../sqlite-wasm-channel-Lakjuk2E.js';
1
+ import { KoraEventEmitter, SchemaDefinition } from '@korajs/core';
2
+ import { S as StorageAdapter, T as Transaction, M as MigrationPlan } from '../types-DepnRDk_.js';
3
+ import { W as WorkerBridge } from '../sqlite-wasm-channel-I9y_jObg.js';
4
4
 
5
5
  /**
6
6
  * Options for creating an IndexedDbAdapter.
@@ -20,10 +20,20 @@ interface IndexedDbAdapterOptions {
20
20
  * URL to the sqlite-wasm-worker script. Required in browsers if no bridge is provided.
21
21
  */
22
22
  workerUrl?: string | URL;
23
+ /** Timeout for worker / follower RPC responses. Defaults to 30000ms. */
24
+ workerResponseTimeoutMs?: number;
25
+ /**
26
+ * Debounce interval (ms) before writing snapshots to IndexedDB. Defaults to 500.
27
+ */
28
+ persistenceDebounceMs?: number;
29
+ /**
30
+ * When set, persistence failures and quota errors are emitted on this emitter.
31
+ */
32
+ emitter?: KoraEventEmitter;
23
33
  }
24
34
  /**
25
35
  * IndexedDB-backed adapter that uses SQLite WASM in-memory and serializes
26
- * the entire database to IndexedDB after each transaction.
36
+ * the entire database to IndexedDB after mutations (coalesced/debounced).
27
37
  *
28
38
  * This is the fallback adapter for browsers where OPFS is not available.
29
39
  * It provides the same SQL interface as SqliteWasmAdapter, but persists by
@@ -37,6 +47,8 @@ interface IndexedDbAdapterOptions {
37
47
  declare class IndexedDbAdapter implements StorageAdapter {
38
48
  private inner;
39
49
  private readonly dbName;
50
+ private readonly emitter;
51
+ private readonly scheduler;
40
52
  constructor(options?: IndexedDbAdapterOptions);
41
53
  open(schema: SchemaDefinition): Promise<void>;
42
54
  close(): Promise<void>;
@@ -44,7 +56,13 @@ declare class IndexedDbAdapter implements StorageAdapter {
44
56
  query<T>(sql: string, params?: unknown[]): Promise<T[]>;
45
57
  transaction(fn: (tx: Transaction) => Promise<void>): Promise<void>;
46
58
  migrate(from: number, to: number, migration: MigrationPlan): Promise<void>;
47
- private persistSnapshot;
59
+ /**
60
+ * Force an immediate snapshot write to IndexedDB (skips debounce).
61
+ * Useful before tab unload or in tests.
62
+ */
63
+ flushPersistence(): Promise<void>;
64
+ private writeSnapshot;
65
+ private handlePersistenceError;
48
66
  private restoreFromDumpFallback;
49
67
  private exportDump;
50
68
  }
@@ -1,17 +1,117 @@
1
1
  import {
2
2
  SqliteWasmAdapter
3
- } from "../chunk-ZP5AXQ3Z.js";
4
- import "../chunk-DXKLAQ6P.js";
3
+ } from "../chunk-6AHYGQQT.js";
4
+ import "../chunk-XO4LMPAH.js";
5
5
  import {
6
- AdapterError,
7
6
  PersistenceError
8
7
  } from "../chunk-LAWV6CFH.js";
9
8
 
9
+ // src/adapters/indexeddb-persistence-scheduler.ts
10
+ var DEFAULT_DEBOUNCE_MS = 500;
11
+ var IndexedDbPersistenceScheduler = class {
12
+ debounceMs;
13
+ flush;
14
+ onError;
15
+ timer = null;
16
+ inFlight = null;
17
+ disposed = false;
18
+ onVisibilityChange;
19
+ constructor(options) {
20
+ this.debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
21
+ this.flush = options.flush;
22
+ this.onError = options.onError;
23
+ this.onVisibilityChange = () => {
24
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
25
+ void this.flushNow();
26
+ }
27
+ };
28
+ if (typeof document !== "undefined") {
29
+ document.addEventListener("visibilitychange", this.onVisibilityChange);
30
+ }
31
+ }
32
+ /** Schedule a debounced snapshot write. */
33
+ schedule() {
34
+ if (this.disposed) return;
35
+ if (this.debounceMs <= 0) {
36
+ void this.flushNow();
37
+ return;
38
+ }
39
+ if (this.timer !== null) {
40
+ clearTimeout(this.timer);
41
+ }
42
+ this.timer = setTimeout(() => {
43
+ this.timer = null;
44
+ void this.flushNow();
45
+ }, this.debounceMs);
46
+ }
47
+ /** Cancel any pending debounce and persist immediately. */
48
+ async flushNow() {
49
+ if (this.disposed) return;
50
+ if (this.timer !== null) {
51
+ clearTimeout(this.timer);
52
+ this.timer = null;
53
+ }
54
+ if (this.inFlight) {
55
+ await this.inFlight;
56
+ return;
57
+ }
58
+ this.inFlight = this.runFlush();
59
+ try {
60
+ await this.inFlight;
61
+ } finally {
62
+ this.inFlight = null;
63
+ }
64
+ }
65
+ dispose() {
66
+ this.disposed = true;
67
+ if (this.timer !== null) {
68
+ clearTimeout(this.timer);
69
+ this.timer = null;
70
+ }
71
+ if (typeof document !== "undefined") {
72
+ document.removeEventListener("visibilitychange", this.onVisibilityChange);
73
+ }
74
+ }
75
+ async runFlush() {
76
+ try {
77
+ await this.flush();
78
+ } catch (error) {
79
+ this.onError?.(error);
80
+ }
81
+ }
82
+ };
83
+
10
84
  // src/adapters/sqlite-wasm-persistence.ts
11
85
  var IDB_DATABASE_NAME = "kora-persistence";
12
86
  var IDB_STORE_NAME = "databases";
13
87
  var IDB_VERSION = 1;
14
88
  var DUMP_SUFFIX = "::dump";
89
+ function isIndexedDbQuotaError(error) {
90
+ if (error instanceof DOMException && error.name === "QuotaExceededError") {
91
+ return true;
92
+ }
93
+ if (error instanceof PersistenceError && error.context?.quotaExceeded === true) {
94
+ return true;
95
+ }
96
+ if (error instanceof Error && /quota/i.test(error.message)) {
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ function rejectWithIdbError(reject, message, context, event) {
102
+ const domError = event.target?.error;
103
+ if (domError && isIndexedDbQuotaError(domError)) {
104
+ reject(
105
+ new PersistenceError(message, {
106
+ ...context,
107
+ quotaExceeded: true,
108
+ cause: domError.message
109
+ })
110
+ );
111
+ return;
112
+ }
113
+ reject(new PersistenceError(message, context));
114
+ }
15
115
  function openIdb() {
16
116
  return new Promise((resolve, reject) => {
17
117
  const request = indexedDB.open(IDB_DATABASE_NAME, IDB_VERSION);
@@ -38,7 +138,12 @@ async function saveToIndexedDB(dbName, data) {
38
138
  const store = tx.objectStore(IDB_STORE_NAME);
39
139
  store.put(data, dbName);
40
140
  tx.oncomplete = () => resolve();
41
- tx.onerror = () => reject(new PersistenceError(`Failed to save database "${dbName}" to IndexedDB`, { dbName }));
141
+ tx.onerror = (event) => rejectWithIdbError(
142
+ reject,
143
+ `Failed to save database "${dbName}" to IndexedDB`,
144
+ { dbName },
145
+ event
146
+ );
42
147
  });
43
148
  } finally {
44
149
  idb.close();
@@ -52,7 +157,12 @@ async function saveDumpToIndexedDB(dbName, dump) {
52
157
  const store = tx.objectStore(IDB_STORE_NAME);
53
158
  store.put(dump, `${dbName}${DUMP_SUFFIX}`);
54
159
  tx.oncomplete = () => resolve();
55
- tx.onerror = () => reject(new PersistenceError(`Failed to save dump for database "${dbName}"`, { dbName }));
160
+ tx.onerror = (event) => rejectWithIdbError(
161
+ reject,
162
+ `Failed to save dump for database "${dbName}"`,
163
+ { dbName },
164
+ event
165
+ );
56
166
  });
57
167
  } finally {
58
168
  idb.close();
@@ -108,12 +218,21 @@ async function loadDumpFromIndexedDB(dbName) {
108
218
  var IndexedDbAdapter = class {
109
219
  inner;
110
220
  dbName;
221
+ emitter;
222
+ scheduler;
111
223
  constructor(options = {}) {
112
224
  this.dbName = options.dbName ?? "kora-db";
225
+ this.emitter = options.emitter;
113
226
  this.inner = new SqliteWasmAdapter({
114
227
  bridge: options.bridge,
115
228
  workerUrl: options.workerUrl,
116
- dbName: this.dbName
229
+ dbName: this.dbName,
230
+ workerResponseTimeoutMs: options.workerResponseTimeoutMs
231
+ });
232
+ this.scheduler = new IndexedDbPersistenceScheduler({
233
+ debounceMs: options.persistenceDebounceMs,
234
+ flush: () => this.writeSnapshot(),
235
+ onError: (error) => this.handlePersistenceError(error)
117
236
  });
118
237
  }
119
238
  async open(schema) {
@@ -127,32 +246,55 @@ var IndexedDbAdapter = class {
127
246
  }
128
247
  }
129
248
  async close() {
130
- await this.persistSnapshot();
249
+ await this.scheduler.flushNow();
250
+ this.scheduler.dispose();
131
251
  await this.inner.close();
132
252
  }
133
253
  async execute(sql, params) {
134
254
  await this.inner.execute(sql, params);
135
- await this.persistSnapshot();
255
+ this.scheduler.schedule();
136
256
  }
137
257
  async query(sql, params) {
138
258
  return this.inner.query(sql, params);
139
259
  }
140
260
  async transaction(fn) {
141
261
  await this.inner.transaction(fn);
142
- await this.persistSnapshot();
262
+ this.scheduler.schedule();
143
263
  }
144
264
  async migrate(from, to, migration) {
145
265
  await this.inner.migrate(from, to, migration);
146
- await this.persistSnapshot();
266
+ this.scheduler.schedule();
147
267
  }
148
- async persistSnapshot() {
149
- try {
150
- const data = await this.inner.exportDatabase();
151
- await saveToIndexedDB(this.dbName, data);
152
- const dump = await this.exportDump();
153
- await saveDumpToIndexedDB(this.dbName, dump);
154
- } catch {
268
+ /**
269
+ * Force an immediate snapshot write to IndexedDB (skips debounce).
270
+ * Useful before tab unload or in tests.
271
+ */
272
+ async flushPersistence() {
273
+ await this.scheduler.flushNow();
274
+ }
275
+ async writeSnapshot() {
276
+ const data = await this.inner.exportDatabase();
277
+ await saveToIndexedDB(this.dbName, data);
278
+ const dump = await this.exportDump();
279
+ await saveDumpToIndexedDB(this.dbName, dump);
280
+ }
281
+ handlePersistenceError(error) {
282
+ const message = error instanceof Error ? error.message : "IndexedDB persistence failed";
283
+ const code = error instanceof PersistenceError ? error.code : "PERSISTENCE_FAILED";
284
+ const quotaExceeded = isIndexedDbQuotaError(error);
285
+ if (quotaExceeded) {
286
+ this.emitter?.emit({
287
+ type: "store:quota-exceeded",
288
+ dbName: this.dbName,
289
+ message
290
+ });
155
291
  }
292
+ this.emitter?.emit({
293
+ type: "store:persistence-error",
294
+ dbName: this.dbName,
295
+ message,
296
+ code: quotaExceeded ? "QUOTA_EXCEEDED" : code
297
+ });
156
298
  }
157
299
  async restoreFromDumpFallback() {
158
300
  const dump = await loadDumpFromIndexedDB(this.dbName);
@@ -197,7 +339,10 @@ var IndexedDbAdapter = class {
197
339
  };
198
340
  function ensureSafeIdentifier(identifier) {
199
341
  if (!/^[a-zA-Z0-9_]+$/.test(identifier)) {
200
- throw new AdapterError(`Unsafe SQL identifier: ${identifier}`);
342
+ throw new PersistenceError(`Unsafe SQL identifier: ${identifier}`, {
343
+ code: "UNSAFE_IDENTIFIER",
344
+ identifier
345
+ });
201
346
  }
202
347
  return identifier;
203
348
  }