browser-sqlite 1.0.0-rc.3 → 1.0.0-rc.4

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 (42) hide show
  1. package/NOTICE +56 -0
  2. package/README.md +435 -63
  3. package/dist/LICENSE +21 -0
  4. package/dist/NOTICE +56 -0
  5. package/dist/api.d.ts +376 -0
  6. package/dist/bulk.d.ts +42 -0
  7. package/dist/capabilities.d.ts +23 -0
  8. package/dist/client.d.ts +198 -0
  9. package/dist/credits.d.ts +31 -0
  10. package/dist/{esm/src/debug.d.ts → debug.d.ts} +21 -10
  11. package/dist/delete.d.ts +42 -0
  12. package/dist/epochs.d.ts +55 -0
  13. package/dist/errors.d.ts +37 -0
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +5 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/locks.d.ts +54 -0
  18. package/dist/logger.d.ts +24 -0
  19. package/dist/pool.d.ts +98 -0
  20. package/dist/queries.d.ts +36 -0
  21. package/dist/scheduler.d.ts +131 -0
  22. package/dist/supervisor.d.ts +17 -0
  23. package/dist/transaction.d.ts +38 -0
  24. package/dist/types.d.ts +348 -0
  25. package/dist/utils.d.ts +116 -0
  26. package/dist/worker/cloneable.d.ts +25 -0
  27. package/dist/worker/statement-cache.d.ts +22 -0
  28. package/dist/worker/wa-sqlite-async.wasm +0 -0
  29. package/dist/worker/wa-sqlite-jspi.wasm +0 -0
  30. package/dist/worker/wa-sqlite.wasm +0 -0
  31. package/dist/worker/worker.js +11 -0
  32. package/dist/worker/worker.js.map +1 -0
  33. package/package.json +36 -20
  34. package/dist/esm/index.js +0 -424
  35. package/dist/esm/rslib.config.d.ts +0 -2
  36. package/dist/esm/rstest.config.d.ts +0 -2
  37. package/dist/esm/src/client.d.ts +0 -332
  38. package/dist/esm/src/index.d.ts +0 -1
  39. package/dist/esm/src/orchestrator.d.ts +0 -87
  40. package/dist/esm/src/types.d.ts +0 -83
  41. package/dist/esm/src/utils.d.ts +0 -6
  42. /package/dist/{esm/src → worker}/worker.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/types.ts","../src/capabilities.ts","../src/errors.ts","../src/locks.ts","../src/utils.ts","../src/bulk.ts","../src/epochs.ts","../src/pool.ts","../src/queries.ts","../src/transaction.ts","../src/client.ts","../src/scheduler.ts","../src/logger.ts","../src/debug.ts","../src/supervisor.ts","../src/delete.ts"],"sourcesContent":["export type SQLiteWorkerMessageData<_T = unknown> = {\n callId: number;\n terminate?: boolean;\n} & (\n | SQLWorkerResultData[keyof SQLWorkerResultData]\n | { type: 'error'; message: string }\n);\n\nexport type SQLWorkerResultData<T = unknown> = {\n open: { success: boolean };\n sql: { type: 'partial'; result: T[] } | { type: 'one'; sizes: number[] };\n abort: { type: 'done' };\n};\n\nexport const SharedArrayTypes = {\n INT: 0,\n STRING: 1,\n OBJECT: 2,\n};\n\ntype SQLOptions = {\n chunkSize?: number;\n /** Chunks the worker may send before waiting for a credit. Spec §3.2. */\n credits?: number;\n};\n\n/**\n * Where a worker fetches its `.wasm` from, when the consumer overrode it.\n *\n * Discriminated rather than a single string because the two forms differ in\n * what they leave to the Emscripten glue. `base` is a directory: the glue\n * supplies the file name (`locateFile('wa-sqlite-async.wasm')`), so nothing\n * here names the three builds' files — and nothing has to be renamed when\n * wa-sqlite renames one. `file` is the whole URL, typically content-hashed by\n * a bundler, so the glue's file name is discarded.\n *\n * Always absolute: `resolveWasmLocation` (`src/utils.ts`) resolves against the\n * page before the `open` message is posted, so the worker applies it without\n * knowing what it was relative to.\n */\nexport type WasmLocation = { base: string } | { file: string };\n\nexport type ClientMessageData =\n | {\n type: 'open';\n file: string;\n vfs: SQLiteVFS;\n build?: SQLiteBuild;\n pragmas?: Record<string, string>;\n /** Statements retained per worker; see `src/client.ts`. Internal. */\n statementCacheSize?: number;\n wasm?: WasmLocation;\n }\n | {\n type: 'query';\n callId: number;\n sql: string;\n params: unknown[];\n options?: SQLOptions;\n }\n | { type: 'close'; callId: number }\n | { type: 'credit'; callId: number; n: number }\n | { type: 'stop'; callId: number }\n | {\n type: 'delete';\n callId: number;\n file: string;\n vfs: SQLiteVFS;\n build?: SQLiteBuild;\n wasm?: WasmLocation;\n };\n\nexport type WorkerMessageData =\n | { type: 'ready'; callId: number }\n | { type: 'chunk'; callId: number; data: unknown[] }\n | {\n type: 'done';\n callId: number;\n affected: number;\n /**\n * Statements compiled while serving this query — zero on a cache hit.\n * Rides the same message as `affected` rather than opening a channel:\n * the effect this instruments is a count, not a duration (`mem:lessons`,\n * \"for a sub-millisecond effect, count the round trips\").\n */\n prepared: number;\n }\n | {\n type: 'error';\n callId: number;\n message: string;\n cause?: unknown;\n /** SQLite's numeric result code, when the failure came from SQLite. */\n sqliteCode?: number;\n }\n | { type: 'closed'; callId: number }\n | { type: 'deleted'; callId: number }\n | {\n type: 'open-error';\n callId: number;\n message: string;\n cause?: unknown;\n /** SQLite's numeric result code, when the failure came from SQLite. */\n sqliteCode?: number;\n };\n\n/** Which wa-sqlite WebAssembly build a worker loads. */\nexport type SQLiteBuild = 'sync' | 'async' | 'jspi';\n\n/**\n * What each build needs from the engine beyond plain WebAssembly.\n *\n * `satisfies Record<SQLiteBuild, …>` and not `SQLiteBuild = keyof typeof …`:\n * the check must run in this direction. Adding a build to the union then fails\n * to compile until its requirements are declared, where `keyof` would let a\n * forgotten entry mean silently that the build does not exist. `VFS_CAPABILITIES`\n * derives `SQLiteVFS` from its keys because it *is* the VFS registry; the build\n * registry is `WA_SQLITE_BUILDS` in the worker, and this table describes one\n * attribute of builds rather than the builds themselves.\n */\nexport const BUILD_REQUIREMENTS = {\n sync: [],\n async: [],\n jspi: ['jspi'],\n} as const satisfies Record<SQLiteBuild, readonly PlatformFeature[]>;\n\n/**\n * A platform feature a VFS may need. Which browser versions ship each one is\n * documentation data, not runtime data, so it lives in the README generator\n * (`scripts/render-vfs-matrix.ts`) with its sources — not here, where it would\n * ship to every consumer for nothing.\n */\nexport type PlatformFeature =\n | 'opfs'\n | 'readwrite-unsafe'\n | 'jspi'\n | 'writable-stream';\n\n/** Where a VFS keeps the database. */\nexport type VFSStorage = 'opfs' | 'indexeddb' | 'memory';\n\n/**\n * How a VFS arranges a database in its storage — which is not the same\n * question as `storage`, and cannot be derived from it: `AccessHandlePoolVFS`\n * is `storage: 'opfs'` yet keeps opaque, randomly named slot files whose\n * association with a SQLite path lives in a header inside each file.\n *\n * `deleteDatabase` reads this to decide whether the database is also an OPFS\n * entry it can remove by name after `jDelete` — the pass that covers the two\n * VFS whose `jDelete` does not delete. A wrong value here is a deletion that\n * reports success over an intact file.\n */\nexport type VFSLayout = 'opfs-path' | 'opfs-pool' | 'idb-store' | 'memory';\n\n/** How much of the database a VFS keeps resident in RAM. */\nexport type VFSMemoryModel = 'page-cache' | 'whole-database';\n\n/** What a VFS can and cannot do. One entry per VFS, and no second table. */\nexport type VFSCapability = {\n /** Builds this VFS can run on, most preferred first. */\n readonly builds: readonly [SQLiteBuild, ...SQLiteBuild[]];\n /** Largest pool this VFS supports; `null` when unbounded. */\n readonly maxPoolSize: number | null;\n /** Why the cap exists. Required whenever `maxPoolSize` is not null. */\n readonly poolLimitReason: string | null;\n /** Whether several connections may share one database. */\n readonly multiConnection: boolean;\n /** Whether data outlives `close()`. */\n readonly persistent: boolean;\n /**\n * `page-cache`: only SQLite's page cache is resident, bounded by\n * `PRAGMA cache_size`. `whole-database`: the entire database is resident,\n * and `poolSize` multiplies it.\n */\n readonly memoryModel: VFSMemoryModel;\n /** Where the database actually lives. */\n readonly storage: VFSStorage;\n /** How the database is arranged within that storage. */\n readonly layout: VFSLayout;\n /**\n * Platform features without which this VFS cannot work at all.\n *\n * `readwrite-unsafe` is the one that bites: WebIDL ignores the unknown\n * dictionary member on engines that do not implement it, so the handle\n * silently opens exclusive and the second connection hangs rather than\n * failing. Declaring it is what lets the conformance suite probe for it and\n * skip, instead of leaving it to surface as a 60-second timeout.\n */\n readonly requires: readonly PlatformFeature[];\n /**\n * Platform features this VFS uses when present and works without, at a cost.\n *\n * `OPFSAdaptiveVFS` is the case this field exists for. Without\n * `readwrite-unsafe` it rotates a single exclusive access handle between\n * connections instead of holding one each. That works — 102 of 104 browser\n * tests pass on Firefox — but it serializes the whole pool for the duration\n * of a long uninterruptible statement.\n *\n * Without this distinction, a support table derived from browser specs would\n * mark that VFS broken everywhere outside Chromium, when it merely degrades.\n */\n readonly degradesWithout: readonly PlatformFeature[];\n};\n\n/**\n * The single source of truth for VFS selection. `SQLiteVFS` is derived from its\n * keys, `worker/worker.ts` must supply a loader for every key, the guards in\n * `client.ts` read it, the conformance suite gates its scenarios on it, and the\n * README table is generated from it. Nothing may hold a second copy.\n *\n * Build order is a decision per VFS, not a rule: `sync` is both the fastest and\n * the most portable build, so it leads wherever supported; `OPFSAdaptiveVFS`\n * cannot use it and leads with `async` because `jspi` is Chromium-only.\n *\n * Every declared build combination is verified by running it against the pinned\n * wa-sqlite v1.1.2, never copied from upstream's table.\n */\nexport const VFS_CAPABILITIES = {\n OPFSAdaptiveVFS: {\n builds: ['async', 'jspi'],\n maxPoolSize: null,\n poolLimitReason: null,\n multiConnection: true,\n persistent: true,\n memoryModel: 'page-cache',\n storage: 'opfs',\n layout: 'opfs-path',\n requires: ['opfs'],\n degradesWithout: ['readwrite-unsafe'],\n },\n OPFSWriteAheadVFS: {\n builds: ['sync', 'async', 'jspi'],\n maxPoolSize: null,\n poolLimitReason: null,\n multiConnection: true,\n persistent: true,\n memoryModel: 'page-cache',\n storage: 'opfs',\n layout: 'opfs-path',\n // Measured on Firefox 2026-08-27, HAS_UNSAFE_HANDLES false: all three\n // build pairs and all six invariants pass, concurrent writes included, at\n // poolSize 1, 2 and 4. `requires` used to name readwrite-unsafe, which made\n // the conformance suite skip the very pairs that would have falsified it.\n // Safari is still unmeasured for this VFS — see `mem:follow-ups`.\n requires: ['opfs'],\n degradesWithout: ['readwrite-unsafe'],\n },\n OPFSCoopSyncVFS: {\n builds: ['sync', 'async', 'jspi'],\n maxPoolSize: null,\n poolLimitReason: null,\n multiConnection: true,\n persistent: true,\n memoryModel: 'page-cache',\n storage: 'opfs',\n layout: 'opfs-path',\n requires: ['opfs'],\n degradesWithout: [],\n },\n AccessHandlePoolVFS: {\n builds: ['sync', 'async', 'jspi'],\n maxPoolSize: 1,\n poolLimitReason: 'it cannot share access handles between connections',\n multiConnection: false,\n persistent: true,\n memoryModel: 'page-cache',\n storage: 'opfs',\n layout: 'opfs-pool',\n requires: ['opfs'],\n degradesWithout: [],\n },\n IDBBatchAtomicVFS: {\n builds: ['async', 'jspi'],\n maxPoolSize: null,\n poolLimitReason: null,\n multiConnection: true,\n persistent: true,\n memoryModel: 'page-cache',\n storage: 'indexeddb',\n layout: 'idb-store',\n requires: [],\n degradesWithout: [],\n },\n IDBMirrorVFS: {\n builds: ['async', 'jspi'],\n // Measured 2026-08-25, not inferred: `CREATE TABLE` → `INSERT` → `SELECT`\n // at poolSize 2, 300 rounds under a loaded suite, failed 5 times — with\n // `no such table` (a connection not seeing a committed statement) and\n // `database is locked`. Nothing at all in 60 rounds unloaded, which is why\n // four sightings over two days never reproduced on demand. See MIRROR-1 in\n // mem:follow-ups for the method.\n //\n // It mirrors the whole database in memory PER WORKER and propagates\n // commits over BroadcastChannel, asynchronously — so a pool holds copies\n // that diverge, the same shape that had OPFSPermutedVFS removed from this\n // library. The commit barrier cannot rescue it: its prelude refreshes page\n // 1 through a real read transaction, and there is nothing fresher to read\n // on a connection whose mirror has not received the broadcast yet.\n maxPoolSize: 1,\n poolLimitReason:\n 'its pages are mirrored per worker and commits propagate asynchronously, so a larger pool reads stale data or fails outright',\n multiConnection: false,\n persistent: true,\n // Upstream: \"keeps all files in memory, persisting database files to\n // IndexedDB\", and the whole database must fit in available memory.\n memoryModel: 'whole-database',\n storage: 'indexeddb',\n layout: 'idb-store',\n requires: [],\n degradesWithout: [],\n },\n OPFSAnyContextVFS: {\n builds: ['async', 'jspi'],\n maxPoolSize: null,\n poolLimitReason: null,\n multiConnection: true,\n persistent: true,\n memoryModel: 'page-cache',\n storage: 'opfs',\n layout: 'opfs-path',\n requires: ['opfs', 'writable-stream'],\n degradesWithout: [],\n },\n MemoryVFS: {\n builds: ['sync', 'async', 'jspi'],\n maxPoolSize: 1,\n poolLimitReason:\n 'its pages live in the worker that opened them, so a larger pool would open independent databases that diverge silently',\n multiConnection: false,\n persistent: false,\n memoryModel: 'whole-database',\n storage: 'memory',\n layout: 'memory',\n requires: [],\n degradesWithout: [],\n },\n MemoryAsyncVFS: {\n builds: ['async', 'jspi'],\n maxPoolSize: 1,\n poolLimitReason:\n 'its pages live in the worker that opened them, so a larger pool would open independent databases that diverge silently',\n multiConnection: false,\n persistent: false,\n memoryModel: 'whole-database',\n storage: 'memory',\n layout: 'memory',\n requires: [],\n degradesWithout: [],\n },\n} as const satisfies Record<string, VFSCapability>;\n\nexport type SQLiteVFS = keyof typeof VFS_CAPABILITIES;\n\n/** The build used when the caller does not name one. */\nexport const defaultBuildFor = (vfs: SQLiteVFS): SQLiteBuild =>\n VFS_CAPABILITIES[vfs].builds[0];\n\n/**\n * The VFS this project recommends when a caller has no reason to choose\n * another. It is NOT a default — `vfs` is required, precisely so that the name\n * lives in the consumer's own source and cannot move underneath their data.\n *\n * It lives here, beside the table, because the README generator marks this row\n * `(recommended)` and would otherwise hold a second copy. It is deliberately\n * not exported: a consumer writing `vfs: RECOMMENDED_VFS` would be exposed to\n * the same displacement the day the recommendation changes.\n */\nexport const RECOMMENDED_VFS: SQLiteVFS = 'OPFSAdaptiveVFS';\n","import {\n BUILD_REQUIREMENTS,\n type PlatformFeature,\n type SQLiteBuild,\n type SQLiteVFS,\n VFS_CAPABILITIES,\n} from './types';\n\n/**\n * Synchronous platform probes, keyed by FEATURE rather than by VFS or by build.\n * That is what lets a VFS requirement and a build requirement travel one path.\n *\n * `WebAssembly.Suspending` is cast rather than declared globally: it is not in\n * lib.dom, and a global augmentation would leak the assertion into every file.\n */\nconst PROBES: Partial<Record<PlatformFeature, () => boolean>> = {\n opfs: () =>\n typeof navigator !== 'undefined' &&\n typeof navigator.storage?.getDirectory === 'function' &&\n typeof FileSystemFileHandle !== 'undefined',\n jspi: () =>\n typeof (WebAssembly as { Suspending?: unknown }).Suspending === 'function',\n 'writable-stream': () =>\n typeof FileSystemFileHandle !== 'undefined' &&\n typeof FileSystemFileHandle.prototype.createWritable === 'function',\n};\n\n/**\n * Features with no synchronous probe. Declared, never merely omitted.\n *\n * WebIDL ignores an unknown dictionary member, so asking whether\n * `readwrite-unsafe` is supported answers yes and is wrong. Detecting it means\n * opening two access handles on one file inside a dedicated worker — which the\n * benchmark page does, asynchronously. A feature in neither table is a mistake,\n * and `tests/unit/capabilities.test.ts` says so.\n */\nconst UNPROBEABLE = new Set<PlatformFeature>(['readwrite-unsafe']);\n\n/** Human-readable names for the error messages. */\nconst FEATURE_LABEL: Record<PlatformFeature, string> = {\n opfs: 'OPFS',\n jspi: 'JSPI',\n 'writable-stream': 'FileSystemWritableFileStream',\n 'readwrite-unsafe': 'readwrite-unsafe access handles',\n};\n\n/**\n * Every feature this module can decide: probed, or explicitly exempt. A\n * feature declared in a capability table and absent here is a mistake, and\n * tests/unit/capabilities.test.ts is what says so.\n */\nexport const KNOWN_FEATURES: ReadonlySet<PlatformFeature> = new Set([\n ...(Object.keys(PROBES) as PlatformFeature[]),\n ...UNPROBEABLE,\n]);\n\n/** What this engine can do, probed once by the caller. */\nexport const detectFeatures = (): ReadonlySet<PlatformFeature> => {\n const found = new Set<PlatformFeature>();\n for (const [feature, probe] of Object.entries(PROBES)) {\n if (probe()) found.add(feature as PlatformFeature);\n }\n return found;\n};\n\n/**\n * The first feature this pair needs and this engine lacks, or null.\n *\n * Pure, and takes `available` rather than probing, because the branches worth\n * testing are the negative ones and they are unreachable in a real browser:\n * JSPI cannot be taken away from Chromium.\n */\nexport const missingFeature = (\n vfs: SQLiteVFS,\n build: SQLiteBuild,\n available: ReadonlySet<PlatformFeature>,\n): PlatformFeature | null => {\n const required: readonly PlatformFeature[] = [\n ...VFS_CAPABILITIES[vfs].requires,\n ...BUILD_REQUIREMENTS[build],\n ];\n for (const feature of required) {\n if (UNPROBEABLE.has(feature)) continue;\n if (!available.has(feature)) return feature;\n }\n return null;\n};\n\n/**\n * The message for a missing feature, derived from the capability tables so it\n * cannot drift from them. Names an alternative build when the build is at\n * fault, and VFS that do not need the feature when the VFS is.\n */\nexport const describeMissing = (\n vfs: SQLiteVFS,\n build: SQLiteBuild,\n feature: PlatformFeature,\n): string => {\n const label = FEATURE_LABEL[feature];\n\n if (\n (BUILD_REQUIREMENTS[build] as readonly PlatformFeature[]).includes(feature)\n ) {\n const others = VFS_CAPABILITIES[vfs].builds.filter((b) => b !== build);\n const suffix = others.length\n ? ` ${vfs} also runs on: ${others.join(', ')}.`\n : '';\n return `This browser does not support ${label}, which the '${build}' build requires.${suffix}`;\n }\n\n const alternatives = (Object.keys(VFS_CAPABILITIES) as SQLiteVFS[]).filter(\n (name) =>\n !(VFS_CAPABILITIES[name].requires as readonly PlatformFeature[]).includes(\n feature,\n ),\n );\n const suffix = alternatives.length\n ? ` Without it, these store elsewhere: ${alternatives.join(', ')}.`\n : '';\n return `This browser does not support ${label}, which ${vfs} requires.${suffix}`;\n};\n","/**\n * Every failure this library raises on its own behalf. A caller discriminates\n * on `code`, or on `name` — they carry the same value, so `err.name` reads the\n * way `'AbortError'` does on the DOMException an aborted signal throws.\n */\nexport type SQLiteErrorCode =\n | 'NOT_A_READ_QUERY'\n | 'CLIENT_CLOSED'\n | 'WORKER_CRASHED'\n | 'TIMEOUT'\n | 'PROTOCOL_ERROR'\n | 'INVALID_IDENTIFIER'\n | 'INVALID_OPTION'\n | 'INVALID_PRAGMA'\n | 'BULK_WRITE_FAILED'\n | 'BUSY'\n | 'READ_ONLY_TRANSACTION';\n\nexport class SQLiteError extends Error {\n readonly code: SQLiteErrorCode;\n /**\n * SQLite's own numeric result code, present only when the failure came from\n * SQLite rather than from this library. `BUSY` covers both SQLITE_BUSY (5)\n * and SQLITE_LOCKED (6); this is how a caller tells them apart.\n */\n readonly sqliteCode?: number;\n\n constructor(\n code: SQLiteErrorCode,\n message: string,\n options?: { cause?: unknown; sqliteCode?: number },\n ) {\n super(message, options);\n this.code = code;\n this.name = code;\n if (options?.sqliteCode !== undefined) this.sqliteCode = options.sqliteCode;\n }\n}\n\n/**\n * A batch failed. Raised by `bulkWrite().close()` and by `output().close()`.\n *\n * The counters exist because the old behaviour was silent: batches were chained\n * on one shared promise, so after a rejection every later `.then` was skipped —\n * while their rows had already been spliced out of the buffer (B5). A caller now\n * learns how much of its data reached the database.\n */\nexport class SQLiteBulkWriteError extends SQLiteError {\n readonly rowsWritten: number;\n readonly rowsNotWritten: number;\n\n constructor(\n message: string,\n counts: { rowsWritten: number; rowsNotWritten: number },\n options?: { cause?: unknown },\n ) {\n super('BULK_WRITE_FAILED', message, options);\n this.rowsWritten = counts.rowsWritten;\n this.rowsNotWritten = counts.rowsNotWritten;\n }\n}\n","/**\n * A thin wrapper over `navigator.locks`, used by `output()` to make its staging\n * tables collectable across tabs (D3).\n *\n * The staging lock is NOT mutual exclusion — nothing contends for its name. It\n * is a liveness marker: a lock held for as long as a staging table exists is\n * what lets another tab's sweep tell an in-flight table from an orphan. A tab\n * that is killed has its locks released by the browser, so its orphans become\n * collectable immediately, with no timestamp and no grace period.\n */\n\n/** The slice of the Web Locks API this module uses. */\ntype LockManager = {\n request: (\n name: string,\n optionsOrCallback: any,\n callback?: (lock: unknown) => Promise<unknown>,\n ) => Promise<unknown>;\n query: () => Promise<{ held?: { name?: string }[] }>;\n};\n\nexport type Locks = {\n /** False when the Web Locks API is missing; every method then no-ops. */\n readonly available: boolean;\n /** Acquires `name` and resolves with the function that releases it. */\n hold: (name: string) => Promise<() => void>;\n /** Runs `fn` while holding `name` exclusively. */\n withLock: <T>(name: string, fn: () => Promise<T>) => Promise<T>;\n /**\n * Runs `fn` while holding `name`, or skips it entirely when the lock is held\n * elsewhere. Never waits — which is the point: the staging sweep is\n * opportunistic, and awaiting this lock inside an open transaction would\n * hold SQLite's write lock while waiting on a holder that may itself be\n * waiting for that write lock.\n *\n * Resolves `true` if `fn` ran, `false` if it was skipped.\n */\n tryWithLock: (name: string, fn: () => Promise<unknown>) => Promise<boolean>;\n /** Names currently held anywhere in this origin — every tab included. */\n heldNames: () => Promise<string[]>;\n};\n\nconst STAGING_PREFIX = '__bsq_staging_';\n\nexport const stagingTableName = (uuid: string) =>\n `${STAGING_PREFIX}${uuid.replace(/-/g, '_')}`;\n\nexport const isStagingTable = (table: string) =>\n table.startsWith(STAGING_PREFIX);\n\nexport const stagingLockName = (file: string, table: string) =>\n `bsq:staging:${file}:${table}`;\n\nexport const sweepLockName = (file: string) => `bsq:sweep:${file}`;\n\n/** Serializes database opening across the pool — replaces the SAB init mutex. */\nexport const initLockName = (file: string) => `bsq:init:${file}`;\n\n/**\n * Which staging tables no live `output()` is using — pure, so it is driven by\n * Node tests rather than by two browser tabs.\n */\nexport const staleStagingTables = (\n tables: string[],\n heldNames: string[],\n file: string,\n): string[] => {\n const held = new Set(heldNames);\n return tables.filter((table) => !held.has(stagingLockName(file, table)));\n};\n\n/** The no-op Locks value for environments where the Web Locks API is absent. */\nexport const noOpLocks: Locks = {\n available: false,\n hold: async () => () => {},\n withLock: async (_name, fn) => fn(),\n tryWithLock: async (_name, fn) => {\n await fn();\n return true;\n },\n heldNames: async () => [],\n};\n\nexport const createLocks = (\n manager: LockManager | undefined = globalThis.navigator?.locks as\n | LockManager\n | undefined,\n): Locks => {\n if (!manager) return noOpLocks;\n\n return {\n available: true,\n hold: (name) =>\n new Promise<() => void>((resolveReleaser, rejectOuter) => {\n let release!: () => void;\n const held = new Promise<void>((resolveHeld) => {\n release = resolveHeld;\n });\n manager\n .request(name, () => {\n resolveReleaser(release);\n return held;\n })\n .catch(rejectOuter);\n }),\n withLock: <T>(name: string, fn: () => Promise<T>) =>\n manager.request(name, { mode: 'exclusive' }, () => fn()) as Promise<T>,\n tryWithLock: async (name, fn) => {\n let ran = false;\n await manager.request(\n name,\n { mode: 'exclusive', ifAvailable: true },\n async (lock) => {\n // `ifAvailable` hands the callback null instead of waiting.\n if (!lock) return;\n ran = true;\n await fn();\n },\n );\n return ran;\n },\n heldNames: async () => {\n const snapshot = await manager.query();\n return (snapshot.held ?? [])\n .map((lock) => lock.name)\n .filter((name): name is string => typeof name === 'string');\n },\n };\n};\n","import { SQLiteError } from './errors';\nimport type { SQLiteBuild, WasmLocation } from './types';\n\nexport const sqlParams = () => {\n // `unknown`, not `any`: these are SQL bind values and they are never\n // inspected here — only counted, de-duplicated by identity, and handed on.\n // The public query surface has always said `unknown[]`; this was the one\n // place that quietly said less.\n const sqlParamsMap = new Map<unknown, number>();\n const sqlParams: unknown[] = [];\n\n const addParam = (v: unknown) => {\n let paramIndex = sqlParamsMap.get(v);\n if (!paramIndex) {\n paramIndex = sqlParams.length + 1;\n sqlParamsMap.set(v, paramIndex);\n sqlParams.push(v);\n }\n return `?${paramIndex.toString().padStart(3, '0')}`;\n };\n const addParamArray = (values: unknown[]) => {\n return values.map((v) => addParam(v)).join(',');\n };\n return {\n addParam,\n addParamArray,\n params: sqlParams,\n };\n};\n\n/**\n * Every statement SQLite treats as a write, or that must be serialized through\n * the single writer worker. Matched anywhere in the string, not just at the\n * start: the worker executes `;`-separated statements, so a write hiding after\n * a semicolon must still route to the writer.\n */\nconst WRITE_KEYWORDS =\n /\\b(INSERT|REPLACE|UPDATE|DELETE|CREATE|DROP|ALTER|VACUUM|ANALYZE|REINDEX|SAVEPOINT|RELEASE|BEGIN|COMMIT|ROLLBACK|ATTACH|DETACH|PRAGMA)\\b/i;\n\n/**\n * Routing predicate: is this statement provably a read?\n *\n * Two conditions, both required. The statement must OPEN with a read keyword,\n * and it must contain no write keyword anywhere. The first condition alone is\n * not enough — the worker executes `;`-separated statements, so `SELECT 1;\n * DROP TABLE t` opens as a read and is not one. The second alone is not enough\n * either — it would admit any unrecognised statement as a read.\n *\n * The previous blocklist missed VACUUM, ALTER, ANALYZE, REINDEX, SAVEPOINT and\n * a manual BEGIN, so those ran on the read pool: a VACUUM could execute on an\n * arbitrary worker while the writer held an open transaction, bypassing\n * exclusivity one layer above the pool.\n *\n * Misclassification now fails toward the writer — correct, merely slower. A\n * read whose text merely mentions a write keyword (`SELECT 'INSERT'`, or\n * `EXPLAIN INSERT ...`, which never executes) is serialized needlessly. That\n * is the accepted price of never routing a write to the read pool.\n */\n/**\n * A statement that is nothing but a single PRAGMA lookup: no assignment, no\n * argument, nothing after it. Anchoring at `$` is what makes this safe for\n * free — `PRAGMA journal_mode; DROP TABLE t` does not match, and neither does\n * `PRAGMA journal_mode=WAL`, whose `=` breaks the match.\n */\nconst READ_PRAGMA = /^\\s*PRAGMA\\s+(\\w+\\.)?\\w+\\s*;?\\s*$/i;\n\nexport const isReadQuery = (sql: string) =>\n READ_PRAGMA.test(sql) ||\n (/^\\s*(SELECT|EXPLAIN|VALUES|WITH)\\b/i.test(sql) &&\n !WRITE_KEYWORDS.test(sql));\n\nexport const isWriteQuery = (sql: string) => !isReadQuery(sql);\n\n/**\n * Combines two abort signals into one that fires with the reason of whichever\n * source aborted first, plus the `release()` that unsubscribes it.\n *\n * NOT `AbortSignal.any()`. That is Chrome 116 / Firefox 124 / Safari 17.4, far\n * above this library's floor (Chrome 92 / Firefox 95 / Safari 15.4), and\n * adopting it would raise every row of the generated README matrix for every\n * consumer.\n *\n * The common case allocates nothing: with one side absent, or one side already\n * aborted, the surviving signal is returned as itself — no listener, no\n * teardown owed, and the caller sees the original `reason` rather than a copy.\n */\nexport const mergeSignals = (\n a: AbortSignal | undefined,\n b: AbortSignal | undefined,\n): { signal: AbortSignal | undefined; release: () => void } => {\n const noop = () => {};\n if (!a || a === b) return { signal: b, release: noop };\n if (!b) return { signal: a, release: noop };\n if (a.aborted) return { signal: a, release: noop };\n if (b.aborted) return { signal: b, release: noop };\n\n const merged = new AbortController();\n const relay = (source: AbortSignal) => () => merged.abort(source.reason);\n const onA = relay(a);\n const onB = relay(b);\n a.addEventListener('abort', onA, { once: true });\n b.addEventListener('abort', onB, { once: true });\n return {\n signal: merged.signal,\n release: () => {\n a.removeEventListener('abort', onA);\n b.removeEventListener('abort', onB);\n },\n };\n};\n\n/**\n * Routing guard for the read-shaped methods (`read`, `chunk`, `stream`, `first`).\n * Throws before a lease is taken, so a rejected statement costs no pool capacity.\n *\n * A bare read pragma (`PRAGMA journal_mode`) is accepted; a pragma that assigns\n * (`PRAGMA journal_mode=WAL`), takes an argument, or is followed by anything\n * else must go through `write()`.\n */\nexport const assertReadable = (sql: string, method: string): void => {\n if (isReadQuery(sql)) return;\n const keyword = sql.trim().split(/\\s+/)[0]?.toUpperCase() ?? '';\n throw new SQLiteError(\n 'NOT_A_READ_QUERY',\n `${method}() only accepts statements that are provably reads; \"${keyword}\" must go through write(). ` +\n `Note that a PRAGMA that assigns a value or takes an argument is a write.`,\n );\n};\n\n/**\n * Quotes an SQL identifier so it can never be read as anything but a name.\n *\n * The library interpolates table, column and index names into generated SQL —\n * `bulkWrite`, `output` and their indexes. wa-sqlite's `statements()` executes\n * `;`-separated statements, so an unquoted name is a stacked-query injection\n * (B4). Quoting is what makes `t\"; DROP TABLE users; --` one identifier.\n *\n * Note that quoting preserves case in `sqlite_master`; SQLite still resolves\n * names case-insensitively.\n */\nexport const quoteIdent = (name: string): string => {\n if (!name)\n throw new SQLiteError('INVALID_IDENTIFIER', 'Identifier cannot be empty');\n if (name.includes('\\0'))\n throw new SQLiteError(\n 'INVALID_IDENTIFIER',\n `Identifier contains a NUL character: ${JSON.stringify(name)}`,\n );\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n};\n\n/** `INTEGER`, `TEXT`, `VARCHAR(255)`, `DECIMAL(10, 2)` — nothing else. */\nconst COLUMN_TYPE = /^[A-Za-z][A-Za-z0-9 ]*(\\([0-9, ]+\\))?$/;\n\n/**\n * A column type is not an identifier and cannot be quoted — it is an SQL\n * fragment the caller writes. It is validated by shape instead: this is the\n * narrowed, not closed, channel documented in the spec (§1.2).\n */\nexport const assertColumnType = (type: string, column: string): string => {\n const trimmed = type.trim();\n if (!COLUMN_TYPE.test(trimmed))\n throw new SQLiteError(\n 'INVALID_IDENTIFIER',\n `Column \"${column}\" declares an unsupported type ${JSON.stringify(type)}. ` +\n `A type must be a word, optionally followed by numeric arguments, e.g. \"INTEGER\" or \"VARCHAR(255)\".`,\n );\n return trimmed;\n};\n\n/**\n * A GENERATED ALWAYS AS expression is caller-authored SQL. It must at least be\n * parenthesised and free of statement separators, so it cannot escape its slot.\n */\nexport const assertGeneratedExpression = (\n expr: string,\n column: string,\n): string => {\n const trimmed = expr.trim();\n if (\n !trimmed.startsWith('(') ||\n !trimmed.endsWith(')') ||\n trimmed.includes(';')\n )\n throw new SQLiteError(\n 'INVALID_IDENTIFIER',\n `Column \"${column}\" declares an invalid generated expression ${JSON.stringify(expr)}. ` +\n `It must be parenthesised and contain no \";\", e.g. \"(base * 2)\".`,\n );\n return trimmed;\n};\n\nconst PRAGMA_NAME = /^[A-Za-z_]\\w*$/;\nconst PRAGMA_INTEGER = /^-?\\d+$/;\nconst PRAGMA_LITERAL = /^'([^']|'')*'$/;\n\n/**\n * Renders the client's `pragmas` option into executable statements, rejecting\n * anything that is not provably a name and a scalar value (B4).\n *\n * Validation is syntactic rather than a closed list of the ~60 SQLite pragmas:\n * a fixed list makes every legitimate pragma outside it unreachable and drifts\n * with SQLite versions, for no additional protection — no \";\", no parenthesis\n * and no comment marker survives these three shapes either.\n *\n * Called twice: by the client at construction, so a bad configuration fails at\n * `createSQLiteClient()` rather than inside an unrelated query, and by the\n * worker at open, which is the only place the statements actually run.\n */\nexport const renderPragmas = (pragmas: Record<string, string>): string[] =>\n Object.entries(pragmas).map(([key, value]) => {\n if (!PRAGMA_NAME.test(key))\n throw new SQLiteError(\n 'INVALID_PRAGMA',\n `Invalid pragma name ${JSON.stringify(key)}: a pragma name must match ${PRAGMA_NAME}.`,\n );\n const raw = String(value).trim();\n if (PRAGMA_INTEGER.test(raw) || PRAGMA_NAME.test(raw))\n return `PRAGMA ${key}=${raw}`;\n // PRAGMA_LITERAL already guarantees raw is a well-formed SQLite string\n // literal (outer quotes present, internal single quotes doubled per '').\n // Using raw directly is correct and simpler than re-escaping the content.\n if (PRAGMA_LITERAL.test(raw)) return `PRAGMA ${key}=${raw}`;\n throw new SQLiteError(\n 'INVALID_PRAGMA',\n `Invalid value ${JSON.stringify(value)} for pragma \"${key}\": expected an integer, a bare word such as WAL, or a quoted literal.`,\n );\n });\n\n/**\n * The single definition of database identity — one string used everywhere:\n * the worker open call, the VFS, the epoch registry and every lock name.\n *\n * The form is **relative** (no leading `/`). `URL.pathname` is absolute by\n * construction, so stripping the slash is necessary: SQLite core checks\n * `nPathname + 8 > mxPathname` (64, `node_modules/wa-sqlite/src/VFS.js:10`)\n * before `xOpen`, and a leading `/` costs a character the budget cannot spare —\n * measured at task 1: it broke all 96 browser tests on 56-char names. The\n * strip gives that character back, so a 56-char name that the caller wrote\n * still fits after normalization. The VFS re-parse (`new URL(zName, 'file://')`\n * for four of five; `AccessHandlePoolVFS` via `'file://localhost/'`) produces\n * identical `pathname` whether the open call receives `'data'` or `'/data'`,\n * so the opened OPFS file is the same regardless.\n *\n * Idempotent: the VFS re-parse of an already-normalized name is a no-op.\n */\nexport const normalizeDatabaseFile = (file: string): string =>\n new URL(file, 'file://').pathname.replace(/^\\//, '');\n\n/**\n * Turns the `wasmUrl` client option into the absolute location posted in the\n * `open` message, or `undefined` when the option was not given.\n *\n * `undefined` is the load-bearing case: the worker sets Emscripten's\n * `locateFile` only when it receives a location, and `findWasmBinary` takes its\n * `new URL('wa-sqlite.wasm', import.meta.url)` branch whenever `locateFile` is\n * absent. So an omitted option leaves resolution byte-for-byte as it was\n * before this option existed — which is the entire contract of the escape\n * hatch.\n *\n * Resolution happens **here**, on the client, against the page: what the\n * consumer writes means what it means from the page they wrote it on, not from\n * the worker's own directory one level down. The callback is therefore called\n * once, at client construction, before any worker exists — its result is\n * reused by every worker in the pool and by every restart.\n *\n * A string is a **directory** and gets its missing trailing slash back before\n * resolution: URL resolution treats a last segment without a slash as a\n * document and replaces it, so `'/static/wasm'` would otherwise silently mean\n * `/static/`. A callback names a **file**, so nothing is appended to it.\n *\n * @throws `SQLiteError('INVALID_OPTION')` when the value cannot be parsed as a\n * URL — synchronously, at construction, rather than as an opaque open failure\n * from a worker that could not fetch its module.\n */\nexport const resolveWasmLocation = (\n wasmUrl: string | ((build: SQLiteBuild) => string) | undefined,\n build: SQLiteBuild,\n baseHref: string,\n): WasmLocation | undefined => {\n if (wasmUrl === undefined) return undefined;\n\n const isCallback = typeof wasmUrl === 'function';\n const raw = isCallback ? wasmUrl(build) : wasmUrl;\n const value = isCallback || raw.endsWith('/') ? raw : `${raw}/`;\n\n let href: string;\n try {\n href = new URL(value, baseHref).href;\n } catch {\n throw new SQLiteError(\n 'INVALID_OPTION',\n `wasmUrl could not be parsed as a URL: ${JSON.stringify(raw)}. Pass a directory (relative, absolute, or a full URL), or a callback returning the full URL of one .wasm file.`,\n );\n }\n\n return isCallback ? { file: href } : { base: href };\n};\n","import type {\n Schema,\n SQLiteBulkWriteOptions,\n SQLiteOutputOptions,\n SQLiteOutputRow,\n SQLiteTransactionOptions,\n} from './api';\nimport { SQLiteBulkWriteError } from './errors';\nimport {\n type Locks,\n stagingLockName,\n stagingTableName,\n staleStagingTables,\n sweepLockName,\n} from './locks';\nimport type { Logger } from './logger';\nimport {\n assertColumnType,\n assertGeneratedExpression,\n quoteIdent,\n} from './utils';\n\n// Structural, and deliberately narrower than SQLiteQueryAPI: bulk needs only\n// these three calls, and requiring the full surface would make every unit test\n// build a complete stub to exercise a single INSERT.\n/**\n * The options these three actually pass is a signal and nothing else, so that\n * is what they ask for. `any` here accepted a misspelt option in silence, which\n * is the one thing a narrow type was never meant to buy.\n */\ntype BulkCallOptions = { signal?: AbortSignal | undefined };\n\nexport type WriteFn = (\n sql: string,\n params?: unknown[],\n options?: BulkCallOptions,\n) => Promise<{ result: unknown[]; affected: number }>;\n\nexport type ReadFn = (\n sql: string,\n params?: unknown[],\n options?: BulkCallOptions,\n) => Promise<unknown[]>;\n\nexport type TransactionFn = <T>(\n callback: (db: {\n write: (\n sql: string,\n params?: unknown[],\n options?: BulkCallOptions,\n ) => Promise<{ result: unknown[]; affected: number }>;\n }) => Promise<T>,\n options?: SQLiteTransactionOptions,\n) => Promise<T>;\n\n/**\n * How long the best-effort staging DROP may wait for a worker before the\n * caller is let go. Not an option: a caller has nothing useful to tune here,\n * and the consequence of expiry is a table the sweep already collects.\n */\nconst DROP_STAGING_TIMEOUT = 5_000;\n\n/**\n * Returned by every `enqueue()` that does not have to wait. Shared rather than\n * created per call: the hot path allocates nothing.\n */\nconst ADMITTED = Promise.resolve();\n\n/** A promise and the handle that resolves it. */\nconst makeRoom = (): { promise: Promise<void>; resolve: () => void } => {\n let resolve!: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n return { promise, resolve };\n};\n\nexport const createBulk = (shared: {\n file: string;\n locks: Locks;\n logger: Logger;\n maxVariables?: number;\n}) => {\n const { file, locks, maxVariables = 32766, logger } = shared;\n\n // Net 2 of the three-net cleanup: orphans left by a closed tab or a crashed\n // session.\n //\n // It runs at the FIRST output() of this client, never at open(). The writer is\n // only designated lazily, on the first write, so a sweep at open would race\n // the n workers. That is the argument against making it eager to make the\n // first output() faster — an attractive idea that the two-stage split does\n // NOT rule out on its own.\n //\n // The memo lives HERE rather than in forTarget on purpose: a transaction\n // builds its own target, so a per-target memo would sweep on every\n // tx.output() instead of once per client.\n let swept: Promise<void> | undefined;\n\n return (target: {\n read: ReadFn;\n write: WriteFn;\n transaction: TransactionFn;\n }) => {\n const { read, write, transaction } = target;\n\n // bulkWrite, sweepOnce, indexStatements and output move in here VERBATIM.\n // Not one character of their bodies changes: they already read `read`,\n // `write`, `transaction`, `file`, `locks`, `logger` and `maxVariables` as\n // free variables, and all seven are still in scope. This task is a\n // relocation; any behavioural edit smuggled into it is a defect.\n\n /**\n * Creates a bulk write utility for efficiently inserting many rows.\n * Automatically batches inserts to stay within SQLite variable limits.\n *\n * @param table - Table name to insert into\n * @param keys - Column names for the insert\n * @returns Object with enqueue() to add rows and close() to flush remaining\n */\n const bulkWrite = <KEYS extends string>(\n table: string,\n keys: KEYS[],\n options?: SQLiteBulkWriteOptions,\n /** Internal: awaited before the first batch. `output()` passes its staging DDL. */\n before?: Promise<unknown>,\n ) => {\n const signal = options?.signal;\n const maxBufferSize = Math.floor(maxVariables / keys.length);\n // Two batches' worth by default: the batch is the unit that gets queued,\n // so anything smaller than one is meaningless and two is the smallest\n // window that lets a batch settle while another is being filled. Derived\n // rather than fixed because the same row count means a different number\n // of INSERTs on a wide table than on a narrow one. It bounds ROWS — what\n // they weigh is the caller's business, and `queueSize` is theirs to set.\n // Raised to 1 rather than trusted: a flush always queues at least one\n // row, so anything lower can never be satisfied and would park the\n // producer for ever. This is the one place an explicit value is not taken\n // as given — the spec's \"no clamping\" was about a value too HIGH, whose\n // worst case is the behaviour that predates this option.\n const queueSize = Math.max(1, options?.queueSize ?? 2 * maxBufferSize);\n\n const buffer: { [K in KEYS]: any }[] = [];\n\n let writePromise = Promise.resolve<number>(0);\n let failure: unknown;\n let closed = false;\n let rowsWritten = 0;\n let rowsNotWritten = 0;\n /** Rows handed to a batch that has not settled yet. */\n let queuedRows = 0;\n /** Shared by every enqueue() parked while the queue is full. */\n let room: { promise: Promise<void>; resolve: () => void } | undefined;\n\n const releaseRoom = () => {\n room?.resolve();\n room = undefined;\n };\n\n // The abort must release a producer parked on enqueue(): the batch it\n // waits for may never settle — the pool can stay empty on a VFS that\n // rotates one exclusive handle — and the release is what lets its next\n // enqueue() throw signal.reason. Removed by close(), so a signal the\n // caller keeps does not collect one listener per writer.\n signal?.addEventListener('abort', releaseRoom, { once: true });\n\n const fail = (): SQLiteBulkWriteError =>\n new SQLiteBulkWriteError(\n `bulkWrite into \"${table}\" failed after ${rowsWritten} row(s); ${rowsNotWritten} row(s) were not written.`,\n { rowsWritten, rowsNotWritten },\n { cause: failure },\n );\n\n const flush = () => {\n const toInsert = [...buffer];\n buffer.length = 0;\n queuedRows += toInsert.length;\n // The chain never rejects: a rejection here is what used to skip every\n // later `.then()` and drop already-spliced rows without a word (B5).\n const runBatch = async (currentAffected: number) => {\n if (failure) {\n rowsNotWritten += toInsert.length;\n return currentAffected;\n }\n // Skips a batch the abort beat to the start, so no round trip is\n // paid for rows that will not be written.\n if (signal?.aborted) {\n rowsNotWritten += toInsert.length;\n return currentAffected;\n }\n try {\n if (before) await before;\n // The signal goes DOWN to the write. An earlier version withheld\n // it, reasoning that an aborted batch would be caught below and\n // recorded as `failure`, making close() reject with\n // SQLiteBulkWriteError instead of the caller's reason. The premise\n // was right and the conclusion wrong: the catch is ours, and it\n // tells the two apart.\n //\n // Withholding it cost a hang. A batch already in flight had no way\n // to be rejected, so a write that never settles — OPFSCoopSyncVFS\n // on an engine without `readwrite-unsafe`, waiting on a handle\n // hand-over that never comes — left this chain pending for ever,\n // and close() with it. Observed on macOS Safari 27.0.\n const { affected } = await write(\n `INSERT INTO ${quoteIdent(table)} (${keys.map(quoteIdent).join(',')}) VALUES ${toInsert.map(() => `(${keys.map(() => '?')})`)}`,\n toInsert.flatMap((data) => keys.map((k) => data[k])),\n { signal },\n );\n rowsWritten += toInsert.length;\n return currentAffected + affected;\n } catch (error) {\n // An abort is not a failure. This branch is what keeps close()\n // rejecting with `signal.reason` rather than SQLiteBulkWriteError.\n if (signal?.aborted) {\n rowsNotWritten += toInsert.length;\n return currentAffected;\n }\n failure = error;\n // A multi-row INSERT is statement-atomic: nothing of this batch landed.\n rowsNotWritten += toInsert.length;\n return currentAffected;\n }\n };\n writePromise = writePromise.then(async (currentAffected) => {\n try {\n return await runBatch(currentAffected);\n } finally {\n // Every exit passes here — success, latched failure, and the batch\n // an abort skipped. One missed decrement and enqueue() never\n // resolves again.\n queuedRows -= toInsert.length;\n if (queuedRows < queueSize) releaseRoom();\n }\n });\n };\n\n const failClosed = (): SQLiteBulkWriteError =>\n new SQLiteBulkWriteError(`Bulk writer for \"${table}\" is closed.`, {\n rowsWritten,\n rowsNotWritten,\n });\n\n return {\n enqueue: (data: { [K in KEYS]: any }) => {\n if (closed) throw failClosed();\n // Before the failure guard: an aborted writer is not a failed one,\n // and the caller who aborted wants their own reason back, not a\n // report about rows they stopped caring about.\n signal?.throwIfAborted();\n if (failure) throw fail();\n buffer.push(data);\n if (buffer.length >= maxBufferSize) flush();\n if (queuedRows < queueSize) return ADMITTED;\n // One deferred for every caller while the queue is full: enqueue() is\n // not concurrent-safe today and this does not make it so.\n room ??= makeRoom();\n return room.promise;\n },\n close: async () => {\n if (closed) throw failClosed();\n try {\n if (buffer.length) flush();\n const affected = await writePromise;\n // Ordered ahead of the failure check for the same reason: a batch\n // skipped by the abort is not a batch that failed.\n signal?.throwIfAborted();\n if (failure) throw fail();\n closed = true;\n return affected;\n } finally {\n signal?.removeEventListener('abort', releaseRoom);\n }\n },\n };\n };\n\n const sweepOnce = () => {\n // MANDATORY guard: without the Web Locks API there is no way to tell an\n // in-flight staging table from an orphan, and `heldNames()` returns []. A\n // sweep in that state would drop another tab's live staging table — worse\n // than not sweeping. The sweep is opportunistic; skipping it is correct.\n if (!locks.available) {\n if (swept === undefined) {\n swept = Promise.resolve();\n logger.warn(\n 'navigator.locks is unavailable; skipping the staging sweep',\n );\n }\n return swept;\n }\n\n // tryWithLock, not withLock: awaiting this lock inside an open transaction\n // would hold SQLite's write lock while waiting on a holder that may itself\n // be waiting for that write lock — reachable with two clients in one tab.\n //\n // A refused attempt is memoized deliberately. If the lock was held, another\n // client was sweeping; retrying would put a lock request in front of every\n // output() for nothing.\n swept ??= locks\n .tryWithLock(sweepLockName(file), async () => {\n const rows = await read(\n `SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '__bsq_staging_%'`,\n );\n const tables = rows\n .map((row) => (row as { name?: unknown }).name)\n .filter(\n (name: unknown): name is string => typeof name === 'string',\n );\n if (!tables.length) return;\n const stale = staleStagingTables(\n tables,\n await locks.heldNames(),\n file,\n );\n for (const orphan of stale) {\n await write(`DROP TABLE IF EXISTS ${quoteIdent(orphan)}`);\n }\n })\n .then(() => undefined)\n .catch(() => {\n // A failed sweep must never fail the output() that triggered it.\n });\n return swept;\n };\n\n /** CREATE INDEX statements for the final table, built after the rename. */\n const indexStatements = <SCHEMA extends Schema>(\n table: string,\n options?: SQLiteOutputOptions<SCHEMA>,\n ): string[] => {\n const statements: string[] = [];\n for (const index of options?.indexes ?? []) {\n const columns = Array.isArray(index)\n ? index\n : typeof index === 'object'\n ? 'column' in index\n ? [index.column]\n : index.columns\n : [index];\n const unique =\n !Array.isArray(index) && typeof index === 'object' && !!index.unique;\n if (!columns?.length) continue;\n const names = columns.map(String);\n statements.push(\n `CREATE${unique ? ' UNIQUE' : ''} INDEX IF NOT EXISTS ${quoteIdent(`${table}_${names.join('_')}_${unique ? 'U' : 'IDX'}`)} ON ${quoteIdent(table)}(${names.map(quoteIdent).join(',')})`,\n );\n }\n return statements;\n };\n\n /**\n * Builds a table from scratch and swaps it in atomically — MongoDB's $out.\n *\n * Rows are loaded into __bsq_staging_<uuid> (a normal table in main, never\n * TEMP: a TEMP table lives in the temp database and cannot be renamed across\n * databases, and is invisible to the other pool workers). The final swap is\n * one short transaction: DROP the target, RENAME the staging table onto it,\n * then build the indexes with their final names — SQLite has no\n * ALTER INDEX ... RENAME, so indexes built before the swap would keep the\n * staging name forever (D3).\n *\n * Until close() succeeds the previous table stays intact and fully\n * populated. That is the guarantee output() did not have (B5): it used to\n * DROP and CREATE eagerly, so a failure anywhere in the load left the caller\n * with no table at all.\n */\n const output = <SCHEMA extends Schema>(\n table: string,\n schema: SCHEMA,\n options?: SQLiteOutputOptions<SCHEMA>,\n ) => {\n const staging = stagingTableName(crypto.randomUUID());\n\n const normalizedSchema = Object.entries(schema).map(([k, v]) => {\n const type = assertColumnType(typeof v === 'string' ? v : v.type, k);\n const unique = typeof v === 'object' && !!v.unique;\n const notnull = typeof v === 'object' && !!v.required;\n const generated =\n typeof v === 'object' && v.generated\n ? assertGeneratedExpression(v.generated, k)\n : undefined;\n return { name: k, type, unique, notnull, generated };\n });\n\n // Held for as long as the staging table exists: this is what tells another\n // tab's sweep that the table is in flight and must not be collected.\n const lockHeld = locks.hold(stagingLockName(file, staging));\n\n const createStaging = sweepOnce()\n .then(() =>\n write(`\n\t\t\tCREATE TABLE ${quoteIdent(staging)}(\n\t\t\t\t${normalizedSchema\n .map(({ name, type, unique, notnull, generated }) => {\n return `${quoteIdent(name)} ${type} ${unique ? 'UNIQUE' : ''} ${notnull ? 'NOT NULL' : ''} ${generated ? `GENERATED ALWAYS AS ${generated}` : ''}`;\n })\n .join(',')}\n\t\t\t)`),\n )\n .then(() => undefined);\n\n const { enqueue, close } = bulkWrite(\n staging,\n Object.keys(schema).filter(\n (col) => typeof schema[col] !== 'object' || !schema[col].generated,\n ),\n { signal: options?.signal, queueSize: options?.queueSize },\n createStaging,\n );\n\n const releaseLock = async () => {\n (await lockHeld)();\n };\n\n const dropStaging = () =>\n Promise.race([\n write(`DROP TABLE IF EXISTS ${quoteIdent(staging)}`),\n // Bounded, because this runs on the path whose whole point is to\n // stop quickly. The DROP is a write, so it needs a worker — and\n // after an abort the pool may still be finishing the batch the abort\n // skipped, or be stuck for the reason the caller aborted over.\n // Unbounded, a best-effort cleanup would hold close() open forever.\n //\n // Giving up here is safe by construction: the fallback is an orphan\n // staging table, and releasing the staging lock — which happens\n // AFTER this attempt, deliberately — is what tells another sweep it\n // may collect it.\n new Promise((resolve) => setTimeout(resolve, DROP_STAGING_TIMEOUT)),\n ]).catch(() => {\n // Net 2 (the sweep) collects what this could not.\n });\n\n return {\n enqueue: (data: SQLiteOutputRow<SCHEMA>) => enqueue(data as any),\n\n close: async () => {\n let affected: number;\n try {\n // Ensure the staging table exists even when no rows were enqueued —\n // bulkWrite.close() only awaits createStaging via flush(), and flush()\n // is skipped when the buffer is empty.\n await createStaging;\n affected = await close();\n } catch (error) {\n await dropStaging();\n await releaseLock();\n throw error;\n }\n\n try {\n await transaction(async (tx) => {\n await tx.write(`DROP TABLE IF EXISTS ${quoteIdent(table)}`);\n await tx.write(\n `ALTER TABLE ${quoteIdent(staging)} RENAME TO ${quoteIdent(table)}`,\n );\n for (const statement of indexStatements(table, options)) {\n await tx.write(statement);\n }\n });\n } catch (error) {\n await dropStaging();\n throw error;\n } finally {\n await releaseLock();\n }\n\n return affected;\n },\n };\n };\n\n return { bulkWrite, output };\n };\n};\n","/**\n * The commit epoch: a monotonic integer per database, counting commits\n * performed in this realm. Its absolute value means nothing — only the\n * comparison with a worker's `seen` does.\n *\n * The registry lives in the realm-wide symbol registry rather than in a module\n * variable on purpose. A module singleton is unique only when the bundler\n * loads one copy of the module; `Symbol.for` is unique per realm whatever the\n * bundler did. That is what makes \"two clients in one tab see each other\" true\n * by construction.\n *\n * The `v1` suffix separates incompatible shapes. Bump it ONLY if the shape\n * changes — bumping it per release recreates the fragmentation it prevents.\n */\n\n/**\n * The statement the barrier runs and discards.\n *\n * Measured 2026-08-20 in the forced configuration: 6/6 correct. `SELECT 1`\n * touches no page and is 6/6 stale; `PRAGMA data_version` and\n * `PRAGMA schema_version` are 8/8 stale; so is waiting. Only a statement that\n * opens a real read transaction on the file refreshes the connection's cached\n * page 1 — and it must be a SEPARATE statement, because the one that triggers\n * the refresh still returns the stale result.\n */\nexport const BARRIER_SQL = 'SELECT count(*) FROM sqlite_master';\n\nconst REGISTRY_KEY = Symbol.for('browser-sqlite.epochs.v1');\n\ntype Cell = { value: number };\ntype Registry = Map<string, Cell>;\n\nconst registry = (): Registry => {\n const host = globalThis as unknown as Record<symbol, Registry | undefined>;\n const existing = host[REGISTRY_KEY];\n if (existing) return existing;\n const created: Registry = new Map();\n host[REGISTRY_KEY] = created;\n return created;\n};\n\nexport type Epochs = {\n /** The number of commits observed in this realm for this database. */\n current: () => number;\n /** Records one commit and returns the new epoch. */\n bump: () => number;\n};\n\n/**\n * Handles onto the counter for `file`, which MUST already be normalized by\n * `normalizeDatabaseFile`. Entries are never removed: deleting one would\n * restart the counter at 0, and a worker still alive with `seen = 5` would\n * then read `5 > 0`, believe itself current forever, and serve stale data.\n */\nexport const epochsFor = (file: string): Epochs => {\n const map = registry();\n const existing = map.get(file);\n const cell: Cell = existing ?? { value: 0 };\n if (!existing) map.set(file, cell);\n return {\n current: () => cell.value,\n bump: () => {\n cell.value += 1;\n return cell.value;\n },\n };\n};\n\n/**\n * Where a worker's `seen` lands after the write it just served.\n *\n * `target` is the epoch captured when its lease was granted; `next` is the\n * epoch its own commit produced. Advancing requires both conditions:\n *\n * - `seen === target`: the worker was actually observing from `target` when its\n * lease was granted. If the worker was already behind (`seen < target`), it\n * must not be marked current regardless of what it just committed.\n * - `next === target + 1`: the commit is the immediate successor of `target`.\n * If another client committed during our lease, `next` skipped; our\n * connection never observed that commit and must stay marked behind.\n *\n * Marking a connection current when it is not is the only class of bug this\n * design must make impossible.\n */\nexport const advanceSeen = (\n seen: number,\n target: number,\n next: number,\n): number => (seen === target && next === target + 1 ? next : seen);\n","import { DEFAULT_CREDIT_WINDOW } from './credits';\nimport { SQLiteError } from './errors';\nimport type { Logger } from './logger';\nimport type {\n SQLiteBuild,\n SQLiteVFS,\n WasmLocation,\n WorkerMessageData,\n} from './types';\n\n/**\n * Query execution options forwarded to a pool worker.\n */\nexport type PoolWorkerQueryOptions = {\n chunkSize?: number | undefined;\n credits?: number | undefined;\n /**\n * When true, the query's completion does not call `deps.onServed`. Set for\n * the commit-propagation barrier: it is a synthetic probe, not user work, and\n * must not reset the supervisor's restart counter.\n * `createQueryDebugState` is intentionally NOT suppressed: barrier statements\n * still appear in the debug request tree, and a browser test counts them there\n * to prove the barrier stays conditional.\n */\n noServed?: boolean;\n};\n\n/**\n * A Worker extended with pool-specific properties.\n *\n * Note: no `available` field — availability lives in the Scheduler, not on\n * the worker itself. This makes it impossible to republish a borrowed worker\n * from outside the scheduler (the root cause of B1).\n */\nexport type PoolWorker = Worker & {\n index: number;\n /** Lifecycle label for the debug surface. Replaces the SAB status byte. */\n status: string;\n /**\n * The commit epoch this connection has absorbed. Starts at -1: a worker\n * opens the file — and reads page 1 — BEFORE it enters the pool, and a\n * commit can land in between. At poolSize 2 that is the nominal startup\n * ordering, not a rare race, so a new worker is always treated as behind and\n * pays exactly one barrier statement in its lifetime.\n */\n seen: number;\n /** The epoch captured when the current lease was granted. */\n epochTarget: number;\n query: <T extends Record<string, unknown> = Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n options?: PoolWorkerQueryOptions,\n ) => AsyncGenerator<T[] | number>;\n /**\n * Ask the worker to stop. Also settles a `next()` already in flight, which\n * is what lets the consumer's queued `return()` reach the generator's\n * finally instead of waiting behind a chunk that may be minutes away.\n */\n interrupt: () => void;\n /** Resolves when no query is in flight on this worker. */\n quiesce: () => Promise<void>;\n /** Posts `close`, awaits the `closed` reply, then the caller must terminate. */\n close: () => Promise<void>;\n};\n\nconst STOP = Symbol('stop');\n\n/** SQLITE_BUSY and SQLITE_LOCKED — the two ways a lock conflict reports. */\nconst BUSY_CODES = new Set([5, 6]);\n\n/**\n * Returns a SQLiteError('BUSY', …) when data carries a lock-conflict result\n * code (5 or 6), else undefined. Shared by both the query-error and\n * open-error paths so the BUSY_CODES decision lives in exactly one place.\n */\nexport const busyFromCode = (data: {\n message: string;\n cause?: unknown;\n sqliteCode?: number;\n}): SQLiteError | undefined =>\n data.sqliteCode !== undefined && BUSY_CODES.has(data.sqliteCode)\n ? new SQLiteError('BUSY', data.message, {\n cause: data.cause,\n sqliteCode: data.sqliteCode,\n })\n : undefined;\n\n/**\n * Mints a typed error only for lock conflicts. Every other SQLite failure\n * keeps today's shape — a plain Error carrying SQLite's message — so no\n * existing consumer's error handling changes.\n */\nconst workerError = (data: {\n message: string;\n cause?: unknown;\n sqliteCode?: number;\n}) => busyFromCode(data) ?? new Error(data.message, { cause: data.cause });\n\n/**\n * The single `new Worker(new URL(…))` expression in this package.\n *\n * It must stay one literal, in one place: bundlers find the worker by static\n * analysis of exactly this shape, and a second copy would have them emit a\n * second, untransformed worker bundle. `pool.ts:191` records what that cost\n * when the expression was written a second time for an error message.\n */\nexport const spawnWorker = (name: string): Worker =>\n new Worker(\n /* webpackChunkName: \"browser-sqlite\" */ new URL(\n './worker/worker.js',\n import.meta.url,\n ),\n { name, type: 'module' },\n );\n\n/**\n * Creates a new pool worker and registers it in the pool array.\n * Sets up message routing via callId for query responses.\n *\n * Moved verbatim from `createWorker` in client.ts, with three changes:\n * 1. Closure variables become explicit `deps` parameters.\n * 2. Both `available` assignments are deleted (availability lives in the Scheduler).\n * 3. `worker.available = false/true` in the `query` generator are deleted.\n */\nexport const createPoolWorker = (deps: {\n index: number;\n pool: (PoolWorker | undefined)[];\n clientPrefix: string;\n file: string;\n vfs: SQLiteVFS;\n build: SQLiteBuild;\n /** Already resolved and absolute; relayed to the worker, never read here. */\n wasm?: WasmLocation | undefined;\n pragmas?: Record<string, string> | undefined;\n statementCacheSize?: number | undefined;\n onDeath?: (index: number, error: SQLiteError) => void;\n onServed?: (index: number) => void;\n drainTimeout: number;\n createWorkerDebugState?: ((index: number, name: string) => any) | undefined;\n createQueryDebugState?:\n | ((index: number, sql: string, params?: unknown[]) => any)\n | undefined;\n logger: Logger;\n}): Promise<PoolWorker> => {\n const {\n index,\n pool,\n clientPrefix,\n file,\n vfs,\n build,\n wasm,\n pragmas,\n statementCacheSize,\n } = deps;\n const { createWorkerDebugState, createQueryDebugState, logger } = deps;\n\n const deferredInit = Promise.withResolvers<PoolWorker>();\n\n const workerName = `${clientPrefix} / Worker ${index + 1}`;\n const worker = Object.assign(spawnWorker(workerName) as PoolWorker, {\n index,\n status: 'NEW',\n seen: -1,\n epochTarget: 0,\n });\n pool[index] = worker;\n logger.info(`worker ${index + 1} created`);\n\n const state = createWorkerDebugState?.(index, workerName);\n\n let currentCallId = 0;\n\n // Deferred promise for streaming query results one chunk at a time\n let deferredChunk: PromiseWithResolvers<unknown[] | number> | undefined;\n // Set by the query generator when options.noServed is true; cleared in\n // case 'done' after (possibly) suppressing onServed, and in the generator's\n // finally so a query that fails before 'done' does not leave it set.\n let suppressServed = false;\n\n // Deferred promise resolved when the worker replies 'closed'.\n let deferredClose: PromiseWithResolvers<void> | undefined;\n\n // Resolved while a query is in flight; `quiesce()` is how a lease learns the\n // worker is genuinely idle again.\n let idle: PromiseWithResolvers<void> | undefined;\n let stopRequested: PromiseWithResolvers<typeof STOP> | undefined;\n\n let dead = false;\n let ready = false;\n const deathDeferred = Promise.withResolvers<never>();\n // Nothing awaits this until a query runs; without a sink an early death is an\n // unhandled rejection.\n deathDeferred.promise.catch(() => {});\n\n // Per-query channel for a message that never arrived (onmessageerror). The\n // worker is alive, so the request rejects but the transport stays intact and\n // the generator's finally still stops and drains it.\n let lost: PromiseWithResolvers<never> | undefined;\n\n const die = (error: SQLiteError) => {\n if (dead) return;\n dead = true;\n worker.status = 'DEAD';\n deathDeferred.reject(error);\n deferredInit.reject(error); // no-op once resolved\n deps.onDeath?.(index, error);\n };\n\n worker.onerror = (event) => {\n const errorEvent = event as ErrorEvent;\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(errorEvent.message ?? '')\n : '';\n // Chrome leaves ErrorEvent.filename empty for worker script-load failures,\n // so this is usually absent — measured 2026-08-27, and it is why the\n // fallback below is not simply the worker's own URL.\n //\n // Deliberately NOT `new URL('./worker/worker.js', import.meta.url)`: that\n // expression is an asset reference every bundler follows, and Vite emits a\n // second, untransformed copy of the worker for it — 777 KB whose own\n // `new URL('wa-sqlite.wasm', …)` references dangle, and which nothing ever\n // loads. It existed only so this message could name a URL.\n //\n // A bare `import.meta.url` is not an asset reference, so naming where the\n // client itself was loaded from costs nothing, and it points at the\n // directory the worker should have been emitted beside — which is the\n // thing a consumer actually needs to check.\n const failedUrl = errorEvent.filename;\n logger.error(`worker ${index + 1} crashed: ${detail}`);\n die(\n new SQLiteError(\n 'WORKER_CRASHED',\n ready\n ? `Worker ${index + 1} failed: ${detail || 'uncaught error'}`\n : `browser-sqlite could not load its worker${\n failedUrl\n ? ` from ${failedUrl}`\n : `; the client itself was loaded from ${import.meta.url}, and the worker must be emitted beside it`\n }. ` +\n `If the worker URL 404s, your bundler did not emit the worker beside your build output — ` +\n `see the \"Bundler Configuration\" section of the browser-sqlite README. ${detail}`,\n { cause: event },\n ),\n );\n };\n\n worker.addEventListener('messageerror', () => {\n logger.error(`worker ${index + 1} sent an undeserializable message`);\n lost?.reject(\n new SQLiteError(\n 'PROTOCOL_ERROR',\n `Worker ${index + 1} sent a message that could not be deserialized; the request cannot be completed.`,\n ),\n );\n });\n\n // Message handler routes responses by callId\n worker.onmessage = ({ data }: MessageEvent<WorkerMessageData>) => {\n const { callId, type } = data;\n switch (type) {\n case 'ready': {\n if (callId === 0) {\n ready = true;\n worker.status = 'READY';\n if (state) state.initializationTime = Date.now();\n logger.info(`worker ${index + 1} ready`);\n deferredInit.resolve(worker);\n }\n break;\n }\n case 'open-error': {\n if (callId === 0) {\n logger.error(`worker ${index + 1} failed to open: ${data.message}`);\n die(\n busyFromCode(data) ??\n new SQLiteError('WORKER_CRASHED', data.message, {\n cause: data.cause,\n }),\n );\n }\n break;\n }\n case 'closed': {\n if (callId === 0) {\n logger.info(`worker ${index + 1} closed`);\n worker.status = 'CLOSED';\n deferredClose?.resolve();\n }\n break;\n }\n case 'chunk': {\n if (deferredChunk && callId === currentCallId) {\n if (state?.currentRequest?.currentQuery) {\n state.currentRequest.currentQuery.firstRowTime ??= Date.now();\n }\n deferredChunk.resolve(data.data);\n deferredChunk = Promise.withResolvers<unknown[] | number>();\n }\n break;\n }\n case 'done': {\n if (deferredChunk && callId === currentCallId) {\n const affected = data.affected;\n if (state?.currentRequest?.currentQuery) {\n state.currentRequest.currentQuery.affectedRows = affected;\n state.currentRequest.currentQuery.prepared = data.prepared;\n state.currentRequest.affectedRows += affected;\n state.currentRequest.currentQuery.endTime = Date.now();\n }\n deferredChunk.resolve(affected);\n deferredChunk = undefined;\n if (!suppressServed) deps.onServed?.(index);\n suppressServed = false;\n }\n break;\n }\n case 'error': {\n if (deferredChunk && callId === currentCallId) {\n const error = workerError(data);\n if (state?.currentRequest?.currentQuery) {\n state.currentRequest.currentQuery.error = error;\n state.currentRequest.currentQuery.endTime = Date.now();\n }\n deferredChunk.reject(error);\n // Do NOT null deferredChunk here. If the generator is suspended at\n // `yield` when the error arrives, nulling it would cause the while\n // loop to exit normally (silent truncation). Leaving the rejected\n // promise in place ensures the generator throws on its next\n // `await Promise.race([deferredChunk.promise, ...])` call, which\n // propagates the error to the consumer. The generator's `finally`\n // clears deferredChunk unconditionally.\n // Attach a no-op handler to suppress unhandled-rejection warnings:\n // the consumer may be suspended (e.g. in sleep()) when the error\n // arrives, and `await Promise.race` only attaches its handler on\n // the next generator resume, which may be a macrotask away.\n deferredChunk.promise.catch(() => {});\n }\n break;\n }\n case 'deleted': {\n // A connection worker never deletes; this message belongs to the\n // delete-worker path handled in src/delete.ts and cannot arrive here.\n break;\n }\n default: {\n const _unexpected: never = data;\n throw new Error(\n `Unhandled worker message: ${JSON.stringify(_unexpected)}`,\n );\n }\n }\n };\n\n /**\n * Generator function that executes a query and streams results.\n * Manages the deferredChunk protocol and abort signals.\n */\n const query = async function* <\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n sql: string,\n params?: unknown[],\n options?: PoolWorkerQueryOptions,\n ): AsyncGenerator<T[] | number> {\n try {\n if (deferredChunk) {\n console.error(`Previous query not finished on worker ${index + 1}`);\n throw new Error('Worker is already processing a query');\n }\n\n if (state?.currentRequest) {\n const queryState = createQueryDebugState?.(index, sql, params);\n state.currentRequest.currentQuery = queryState;\n }\n\n // Extract query options\n const {\n chunkSize = 500,\n credits = DEFAULT_CREDIT_WINDOW,\n noServed = false,\n } = options ?? {};\n suppressServed = noServed;\n\n // Prepare for streaming chunks\n deferredChunk = Promise.withResolvers<unknown[] | number>();\n lost = Promise.withResolvers<never>();\n lost.promise.catch(() => {});\n idle = Promise.withResolvers<void>();\n stopRequested = Promise.withResolvers<typeof STOP>();\n\n // Send query to worker with options\n worker.postMessage({\n type: 'query',\n callId: ++currentCallId,\n sql,\n params,\n options: { chunkSize, credits },\n });\n worker.status = 'RUNNING';\n\n // Stream chunks until query completes\n while (deferredChunk) {\n const chunk = await Promise.race([\n deferredChunk.promise,\n stopRequested.promise,\n lost.promise,\n deathDeferred.promise,\n ]);\n if (chunk === STOP) break;\n yield chunk as T[] | number;\n // Spec §3.3: the credit is issued once the CONSUMER has taken the\n // chunk. Crediting on arrival would let the worker run at full speed\n // and pile the chunks up in the message queue, which is the guarantee\n // this whole mechanism exists to make true.\n if (typeof chunk !== 'number') {\n worker.postMessage({ type: 'credit', callId: currentCallId, n: 1 });\n }\n }\n } finally {\n // If the consumer left early (break / return / throw) the worker is still\n // stepping rows. Tell it to stop, then wait for the reply it always sends,\n // so the worker is genuinely idle before the lease goes back to the pool.\n // Without this wait, the second half of B1 stands: a released worker still\n // inside sqlite.step().\n if (deferredChunk && !dead) {\n worker.status = 'ABORTING';\n // Spec §5.1: the worker may be parked waiting for a credit that this\n // unwinding client will never send. The flag above cannot reach it\n // there — only a message can.\n worker.postMessage({ type: 'stop', callId: currentCallId });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expiry = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new SQLiteError(\n 'WORKER_CRASHED',\n `Worker ${index + 1} did not answer the stop request within ${deps.drainTimeout} ms; presumed dead.`,\n ),\n ),\n deps.drainTimeout,\n );\n });\n try {\n while (deferredChunk) {\n await Promise.race([deferredChunk.promise, expiry]);\n }\n } catch (error) {\n // A timeout is our own verdict and must be acted on. Any other error\n // is the worker reporting a failure while winding down; the caller is\n // already unwinding and surfacing it here would mask their reason.\n if (error instanceof SQLiteError && error.code === 'WORKER_CRASHED') {\n die(error);\n }\n } finally {\n clearTimeout(timer);\n }\n }\n deferredChunk = undefined;\n lost = undefined;\n stopRequested = undefined;\n // Reset in case the query failed before 'done' arrived — prevents\n // leaking noServed=true into the next query on this worker.\n suppressServed = false;\n worker.status = dead ? 'DEAD' : 'READY';\n idle?.resolve();\n idle = undefined;\n }\n };\n\n // Attach query method to worker\n Object.assign(worker, {\n query,\n /**\n * Ask the worker to stop. Also settles a `next()` already in flight, which\n * is what lets the consumer's queued `return()` reach the generator's\n * finally instead of waiting behind a chunk that may be minutes away.\n */\n interrupt: () => {\n stopRequested?.resolve(STOP);\n },\n quiesce: () => idle?.promise ?? Promise.resolve(),\n close: async () => {\n if (!deferredClose) {\n deferredClose = Promise.withResolvers<void>();\n worker.postMessage({ type: 'close', callId: 0 });\n }\n await deferredClose.promise;\n },\n });\n\n // Initialize worker with database file and configuration\n worker.postMessage({\n callId: 0,\n type: 'open',\n file,\n vfs,\n build,\n wasm,\n pragmas,\n statementCacheSize,\n });\n\n return deferredInit.promise;\n};\n","import type { OptionsWithSignal, SQLiteChunkOptions } from './api';\nimport type { PoolWorker } from './pool';\n\n/**\n * Wires an AbortSignal into a promise that rejects the instant the signal\n * fires, and returns a teardown that removes the listener. The rejection sink\n * (`aborted?.catch`) suppresses the unhandled-rejection when the query ends\n * normally and nobody is racing the promise any more.\n *\n * This is the only place in the module that reads an AbortSignal; both\n * `chunk()` and `writeWorker()` delegate here.\n */\nexport const makeAbortRace = (\n signal: AbortSignal | undefined,\n): { aborted: Promise<never> | undefined; teardown: () => void } => {\n if (!signal) return { aborted: undefined, teardown: () => {} };\n let onAbort: (() => void) | undefined;\n const aborted = new Promise<never>((_, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n // Nothing consumes this rejection when the query ends normally.\n aborted.catch(() => {});\n return {\n aborted,\n teardown: () => {\n if (onAbort) signal.removeEventListener('abort', onAbort);\n },\n };\n};\n\n/**\n * The single query primitive. Every other read path is a thin derivation, and\n * abort is implemented here exactly once.\n */\nexport const chunk = async function* <\n T extends Record<string, unknown> = Record<string, unknown>,\n>(\n worker: PoolWorker,\n sql: string,\n params?: unknown[],\n options?: SQLiteChunkOptions & { credits?: number },\n): AsyncGenerator<T[]> {\n const { signal, chunkSize, credits } = options ?? {};\n\n // B9: addEventListener never fires for a signal that is already aborted.\n if (signal?.aborted) throw signal.reason;\n\n const { aborted, teardown } = makeAbortRace(signal);\n const iterator = worker.query<T>(sql, params, { chunkSize, credits });\n try {\n while (true) {\n // Racing the pending chunk, not testing a flag after it: an ORDER BY\n // sorts entirely inside the first step(), so waiting for a chunk before\n // noticing the abort makes AbortSignal.timeout(n) return minutes late.\n const next = aborted\n ? await Promise.race([iterator.next(), aborted])\n : await iterator.next();\n if (next.done) break;\n // FLK-1: chunks already queued are not delivered once the signal fired.\n if (typeof next.value !== 'number') yield next.value;\n }\n } finally {\n teardown();\n // Start the stop-and-drain, never await it. The caller must not wait for a\n // sort that may still have minutes to run; the lease returns through\n // quiesce() instead. interrupt() first, so the queued return() is not\n // parked behind a next() that will not settle.\n worker.interrupt();\n void iterator.return(undefined).catch(() => {});\n }\n};\n\nexport const streamRows = async function* <\n T extends Record<string, unknown> = Record<string, unknown>,\n>(\n worker: PoolWorker,\n sql: string,\n params?: unknown[],\n options?: SQLiteChunkOptions,\n): AsyncGenerator<T> {\n for await (const rows of chunk<T>(worker, sql, params, options)) {\n for (const row of rows) yield row;\n }\n};\n\nexport const readWorker = async <\n T extends Record<string, unknown> = Record<string, unknown>,\n>(\n worker: PoolWorker,\n sql: string,\n params?: unknown[],\n options?: SQLiteChunkOptions,\n): Promise<T[]> => {\n const result: T[] = [];\n for await (const rows of chunk<T>(worker, sql, params, options)) {\n result.push(...rows);\n }\n return result;\n};\n\n/**\n * First row, then stop. This BREAKS rather than aborting: a break triggers the\n * generator's return path, which runs chunk()'s finally and the transport's\n * stop-and-drain — the same worker-stop routine, reached without an exception.\n * That is why there is no internal AbortController here and no need to tell an\n * internal abort from the caller's.\n */\nexport const firstWorker = async <\n T extends Record<string, unknown> = Record<string, unknown>,\n>(\n worker: PoolWorker,\n sql: string,\n params?: unknown[],\n options?: OptionsWithSignal,\n): Promise<T | undefined> => {\n for await (const rows of chunk<T>(worker, sql, params, {\n ...options,\n chunkSize: 1,\n // Spec §4.1: with the default window of 2 the worker would produce a\n // second row before parking. One credit is the exact one-row bound the\n // JSDoc has always promised.\n credits: 1,\n })) {\n return rows[0];\n }\n return undefined;\n};\n\nexport const writeWorker = async <\n T extends Record<string, unknown> = Record<string, unknown>,\n>(\n worker: PoolWorker,\n sql: string,\n params?: unknown[],\n options?: OptionsWithSignal,\n): Promise<{ result: T[]; affected: number }> => {\n const { signal } = options ?? {};\n\n // B9: addEventListener never fires for a signal that is already aborted.\n if (signal?.aborted) throw signal.reason;\n\n const { aborted, teardown } = makeAbortRace(signal);\n const iterator = worker.query<T>(sql, params, {});\n const result: T[] = [];\n let affected = 0;\n try {\n while (true) {\n // Racing the pending chunk, not testing a flag after it: an ORDER BY\n // sorts entirely inside the first step(), so waiting for a chunk before\n // noticing the abort makes AbortSignal.timeout(n) return minutes late.\n const next = aborted\n ? await Promise.race([iterator.next(), aborted])\n : await iterator.next();\n if (next.done) break;\n // write() is the only caller that needs the affected count, which is why\n // the T[] | number union stays private to this module.\n if (typeof next.value === 'number') affected = next.value;\n else result.push(...next.value);\n }\n } finally {\n teardown();\n // Start the stop-and-drain, never await it. Same pattern as chunk().\n worker.interrupt();\n void iterator.return(undefined).catch(() => {});\n }\n return { result, affected };\n};\n","import type {\n OptionsWithSignal,\n SQLiteChunkOptions,\n SQLiteQueryAPI,\n SQLiteTransactionDB,\n SQLiteTransactionOptions,\n} from './api';\nimport type { ReadFn, TransactionFn, WriteFn } from './bulk';\nimport { SQLiteError } from './errors';\nimport type { PoolWorker } from './pool';\nimport {\n chunk as chunkWorker,\n firstWorker,\n makeAbortRace,\n readWorker,\n streamRows,\n writeWorker,\n} from './queries';\nimport type { Scheduler } from './scheduler';\nimport { isWriteQuery, mergeSignals } from './utils';\n\n// Drains a statement that returns no rows (BEGIN, COMMIT, ROLLBACK) without\n// the chunkSize-1 + break overhead of firstWorker.\nconst exec = async (worker: PoolWorker, sql: string): Promise<void> => {\n await readWorker(worker, sql);\n};\n\n/**\n * Returns the `transaction()` method for a SQLiteDB instance.\n *\n * The returned function acquires exactly one lease for the full lifetime of\n * the transaction. All SQLiteTransactionDB methods call worker-bound derivations\n * directly — never the public API — so no secondary lease acquisition can\n * occur during the callback.\n */\nexport const createTransaction =\n (deps: {\n scheduler: Scheduler<PoolWorker>;\n afterWrite: (worker: PoolWorker) => void;\n /**\n * Called when a connection may still hold an open transaction. The worker\n * is lost rather than repaired: a \"dirty worker\" state is one more\n * state the barrier would have to reason about, while a respawned\n * connection is transaction-free by construction.\n */\n onPoisoned: (index: number, error: SQLiteError) => void;\n /**\n * The client's bulk factory. Called per transaction with the transaction's\n * own read/write and a pass-through `transaction`, so output()'s swap runs\n * on the caller's transaction instead of opening a BEGIN SQLite does not\n * allow.\n */\n bulkFor: (target: {\n read: ReadFn;\n write: WriteFn;\n transaction: TransactionFn;\n }) => {\n bulkWrite: SQLiteQueryAPI['bulkWrite'];\n output: SQLiteQueryAPI['output'];\n };\n }) =>\n async <T = void>(\n callback: (db: SQLiteTransactionDB) => Promise<T>,\n options?: SQLiteTransactionOptions,\n ): Promise<T> => {\n const { readOnly = false, autoCommit = true, signal } = options ?? {};\n // The signal aborts the wait too: without it a transaction could not be\n // abandoned while the pool has nothing to lend, which is a state a VFS\n // rotating one exclusive OPFS handle can stay in indefinitely.\n const lease = await deps.scheduler.acquire(\n readOnly ? 'read' : 'write',\n signal,\n );\n const worker = lease.worker;\n\n const checksql = (sql: string): string => {\n if (readOnly && isWriteQuery(sql))\n throw new SQLiteError(\n 'READ_ONLY_TRANSACTION',\n 'Cannot write in a read-only transaction.',\n );\n return sql;\n };\n\n let done = false;\n // Set only once BEGIN has come back. A ROLLBACK sent to a connection that\n // opened no transaction fails, and that failure would lose a healthy\n // worker through onPoisoned.\n let begun = false;\n\n /**\n * The options a statement runs with: the transaction's signal, merged with\n * the caller's own when they gave one, so either may abort the statement\n * and the reason is always the source's. `release` is owed once the\n * statement has settled — the merge is the only thing here that subscribes\n * to a signal the caller may keep alive far longer than this transaction.\n */\n const withSignal = <O extends { signal?: AbortSignal | undefined }>(\n given: O | undefined,\n ): { options: O; release: () => void } => {\n const { signal: merged, release } = mergeSignals(signal, given?.signal);\n return { options: { ...given, signal: merged } as O, release };\n };\n\n /** Runs `release` when the consumer stops reading, however it stops. */\n const releasing = async function* <R>(\n source: AsyncGenerator<R>,\n release: () => void,\n ): AsyncGenerator<R> {\n try {\n yield* source;\n } finally {\n release();\n }\n };\n\n // Guarded at the call, not at the first flush. bulkWrite buffers, so the\n // failure would otherwise surface once the buffer overflows — and for\n // output() later still, trapped inside the createStaging promise.\n const refuse = (method: string) => (): never => {\n throw new SQLiteError(\n 'READ_ONLY_TRANSACTION',\n `${method}() writes, and this transaction is read-only.`,\n );\n };\n\n const bulk = readOnly\n ? {\n bulkWrite: refuse('bulkWrite') as SQLiteQueryAPI['bulkWrite'],\n output: refuse('output') as SQLiteQueryAPI['output'],\n }\n : deps.bulkFor({\n read: (sql, params, given) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return readWorker(worker, query, params, options).finally(release);\n },\n write: (sql, params, given) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return writeWorker(worker, query, params, options).finally(release);\n },\n // The caller's transaction is already open. No BEGIN, no COMMIT.\n // db is referenced before its const declaration, deliberately: this arrow\n // only runs when output().close() fires, by which point db is assigned.\n // Moving `bulk` below `const db` breaks the literal that consumes it.\n transaction: (fn) => fn(db),\n });\n\n const db: SQLiteTransactionDB = {\n read: <T extends Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n given?: SQLiteChunkOptions,\n ) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return readWorker<T>(worker, query, params, options).finally(release);\n },\n\n write: <T extends Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n given?: OptionsWithSignal,\n ) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return writeWorker<T>(worker, query, params, options).finally(release);\n },\n\n chunk: <T extends Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n given?: SQLiteChunkOptions,\n ) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return releasing(\n chunkWorker<T>(worker, query, params, options),\n release,\n );\n },\n\n stream: <T extends Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n given?: SQLiteChunkOptions,\n ) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return releasing(\n streamRows<T>(worker, query, params, options),\n release,\n );\n },\n\n first: <T extends Record<string, unknown>>(\n sql: string,\n params?: unknown[],\n given?: OptionsWithSignal,\n ) => {\n const query = checksql(sql);\n const { options, release } = withSignal(given);\n return firstWorker<T>(worker, query, params, options).finally(release);\n },\n\n bulkWrite: bulk.bulkWrite,\n output: bulk.output,\n\n commit: async () => {\n // The only place a COMMIT is refused, and it covers both callers: the\n // explicit tx.commit() and the auto-commit below. Without it a callback\n // that swallowed its statement's rejection could still commit, and the\n // transaction's own rejection would arrive after the data landed.\n signal?.throwIfAborted();\n await exec(worker, 'COMMIT');\n done = true;\n },\n\n rollback: async () => {\n await exec(worker, 'ROLLBACK');\n done = true;\n },\n };\n\n const { aborted, teardown } = makeAbortRace(signal);\n\n try {\n signal?.throwIfAborted();\n // BEGIN carries no signal, and neither do COMMIT and ROLLBACK. Their\n // completion is what decides whether a rollback is owed: a BEGIN that ran\n // on the worker but rejected on the client would return a connection to\n // the pool holding an open transaction, which is the state onPoisoned\n // exists to prevent. The cost is a window — while BEGIN is in flight the\n // transaction cannot be abandoned, and on a VFS rotating one exclusive\n // handle that wait can be long. The abort lands the moment BEGIN settles.\n await exec(worker, 'BEGIN');\n begun = true;\n // That window, closed: the signal may have fired while BEGIN was in\n // flight, and the transaction is open now. The callback never runs.\n signal?.throwIfAborted();\n\n const running = callback(db);\n // Racing the callback, not only its statements: an abort landing while\n // the callback sits in user code — an await on anything that is not a\n // statement — would otherwise be invisible until it returns, which may be\n // never. The callback is not interrupted, it is abandoned; it cannot\n // reach the worker afterwards because every statement it issues inherits\n // the aborted signal and rejects before the round trip, and the lease\n // returns to the pool only after quiesce().\n running.catch(() => {\n // Nothing consumes this rejection when the abort wins the race.\n });\n const result = aborted\n ? await Promise.race([running, aborted])\n : await running;\n\n if (!done) {\n if (autoCommit) {\n await db.commit();\n } else {\n await db.rollback();\n }\n }\n return result;\n } catch (e) {\n // Only roll back if the transaction is still open. `done` is set after the\n // statement succeeds, so a COMMIT that failed leaves it false and the\n // transaction still active — that case must still roll back.\n if (begun && !done) {\n try {\n await db.rollback();\n } catch {\n // A failed rollback must not replace the caller's error, which is the\n // one that explains what actually went wrong. But the connection may\n // now hold an open transaction, and a read inside one reads that\n // transaction's snapshot — the barrier would refresh nothing and\n // report success. Evict instead of hoping.\n deps.onPoisoned(\n worker.index,\n new SQLiteError(\n 'WORKER_CRASHED',\n `Worker ${worker.index + 1} may hold an open transaction after a failed rollback.`,\n { cause: e },\n ),\n );\n }\n }\n throw e;\n } finally {\n teardown();\n // Same reasoning as write(): before the void, because release is\n // asynchronous. A read-only transaction commits nothing and must not\n // bump.\n if (!readOnly) deps.afterWrite(worker);\n // The lease returns when the worker confirms it is idle, not when the\n // caller leaves: a worker still inside step() must not be re-lent, and\n // the caller must not wait for it.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n }\n };\n","import type { OptionsWithSignal, SQLiteChunkOptions, SQLiteDB } from './api';\nimport { createBulk } from './bulk';\nimport {\n describeMissing,\n detectFeatures,\n missingFeature,\n} from './capabilities';\nimport { createClientDebug } from './debug';\nimport { advanceSeen, BARRIER_SQL, epochsFor } from './epochs';\nimport { SQLiteError } from './errors';\nimport { createLocks } from './locks';\nimport { createLogger } from './logger';\nimport { createPoolWorker, type PoolWorker } from './pool';\nimport {\n chunk as chunkWorker,\n firstWorker,\n makeAbortRace,\n readWorker,\n streamRows,\n writeWorker,\n} from './queries';\nimport {\n createScheduler,\n type InternalSQLiteClientOptions,\n type WriterPolicy,\n} from './scheduler';\nimport { createSupervisor } from './supervisor';\nimport { createTransaction } from './transaction';\nimport {\n defaultBuildFor,\n RECOMMENDED_VFS,\n type SQLiteBuild,\n type SQLiteVFS,\n VFS_CAPABILITIES,\n} from './types';\nimport {\n assertReadable,\n normalizeDatabaseFile,\n renderPragmas,\n resolveWasmLocation,\n} from './utils';\n\n/**\n * SQLite client for browser environments using a pool of Web Workers.\n *\n * Features:\n * - Worker pool management for concurrent SQLite operations\n * - Read/write query differentiation with exclusive write access\n * - Streaming results support for large datasets\n * - Transaction support with rollback capability\n */\n\nconst DEFAULT_POOL_SIZE = 2;\n\n/**\n * Statements retained per worker. Not a consumer option (spec §3.2): the\n * value is declared here rather than in the worker so that exposing it later\n * is one options line, not a move.\n */\nconst DEFAULT_STATEMENT_CACHE_SIZE = 32;\n\n/**\n * Configuration options for creating a SQLite client.\n */\nexport type CreateSQLiteClientOptions = {\n /**\n * Database file name within the OPFS origin private file system.\n * Each unique name maps to a distinct SQLite database file.\n * @defaultValue `\"SQLite\"` prefix + auto-incremented client index\n */\n name?: string;\n\n /**\n * Number of Web Workers spawned in the pool at initialization.\n * A larger pool allows more concurrent read operations but increases\n * memory consumption and OPFS file handle usage.\n * Must be `1` when using `AccessHandlePoolVFS` — any larger value throws at construction time.\n * @defaultValue `2`\n */\n poolSize?: number;\n\n /**\n * Which VFS stores the database. Required: a VFS decides *where* the bytes\n * live, and a database written through one VFS is not visible through\n * another. See the README's VFS Selection guide.\n */\n vfs: SQLiteVFS;\n /**\n * Which wa-sqlite WebAssembly build to load. Defaults to the first entry of\n * `VFS_CAPABILITIES[vfs]` — `sync` where the VFS supports it, since it is both the\n * fastest and the most portable, otherwise `async`. `jspi` needs engine\n * support; see the README's Builds section for versions.\n *\n * @throws at construction when the build is not one the chosen VFS supports.\n */\n build?: SQLiteBuild;\n\n /**\n * Where the workers fetch their `.wasm` from. **An escape hatch, not a\n * setting**: omit it and resolution is exactly what it was before this\n * option existed — the file is taken from beside `worker.js`, which is where\n * the package ships it and where every bundler emits it.\n *\n * Reach for it only when the `.wasm` have been separated from `worker.js`:\n * assets moved by hand with no bundler, or a build whose emitted URL is\n * wrong at runtime.\n *\n * A **string is a directory**, resolved against the page — relative\n * (`'wasm/'`), absolute (`'/static/wasm'`) or a full URL. A missing trailing\n * slash is added. The file name comes from wa-sqlite itself, so one base\n * serves whichever `build` is loaded.\n *\n * A **callback names one file** and receives the resolved `build`, for a\n * bundler-emitted asset whose name carries a content hash:\n * ```ts\n * import wasmUrl from 'browser-sqlite/dist/worker/wa-sqlite.wasm?url';\n * createSQLiteClient('app.db', { vfs, wasmUrl: () => wasmUrl });\n * ```\n * It is called once, at construction, and its answer is reused by every\n * worker and every restart.\n *\n * Serving the `.wasm` from another origin has two requirements beyond this\n * option, both enforced by the browser: the response needs CORS\n * (`Access-Control-Allow-Origin`), since the glue fetches it, and it must\n * carry `Content-Type: application/wasm` for streaming compilation.\n *\n * @throws at construction when the value cannot be parsed as a URL.\n */\n wasmUrl?: string | ((build: SQLiteBuild) => string);\n\n /**\n * SQLite PRAGMAs applied to each worker's database connection on open.\n * Keys are PRAGMA names, values are their string representations.\n * Example: `{ journal_mode: 'WAL', synchronous: 'NORMAL' }`.\n * If omitted, no PRAGMAs are applied beyond SQLite defaults.\n */\n pragmas?: Record<string, string>;\n\n /**\n * How many times a worker slot may be restarted after it has died.\n * A slot that never reached readiness is never restarted — an initial\n * failure is deterministic, and restarting only delays the diagnostic.\n * The counter resets once the replacement has actually served a request.\n * @defaultValue `1`\n */\n maxWorkerRestarts?: number;\n\n /**\n * Milliseconds a worker has to post `ready` after its `open` message is sent.\n * On expiry the slot is failed immediately — the most common cause is a\n * database held under an exclusive lock by another tab or client.\n * @defaultValue `30_000`\n */\n openTimeout?: number;\n\n /**\n * Milliseconds the drain loop (in the query generator's `finally`) may run\n * before the worker is presumed dead and the crash path is invoked.\n * @defaultValue `60_000`\n */\n drainTimeout?: number;\n\n /**\n * Turns on the introspection subsystem exposed as `db.debug`, and the\n * lifecycle log. A string is used as the log prefix; `true` falls back to the\n * client prefix (`\"<name> <index>\"`), which already names the workers.\n *\n * @defaultValue undefined — no collection, no output, `db.debug` undefined.\n */\n debug?: string | boolean;\n\n /**\n * Called whenever a worker slot is permanently lost. Receives the slot index,\n * the number of workers still alive after the loss, the requested pool size,\n * and the error that killed the slot.\n *\n * Guaranteed to be called **before** the client is failed when the last slot\n * is lost. Wrapped in try/catch — a throwing callback is reported through\n * `logger.always.warn` and does not break the pool.\n *\n * @defaultValue undefined\n */\n onWorkerLost?: (event: WorkerLostEvent) => void;\n};\n\n/**\n * What `onWorkerLost` receives. Named and exported rather than inlined in the\n * option: a consumer whose handler is a standalone function needs to be able\n * to type its parameter.\n */\nexport type WorkerLostEvent = {\n /** Zero-based index of the lost slot. */\n index: number;\n /** Number of workers still alive after this loss. */\n live: number;\n /** The requested pool size (`poolSize` option). */\n size: number;\n /** The error that killed the worker. */\n cause: SQLiteError;\n};\n\nlet clientCount = 0;\n\n/**\n * Creates a SQLite client backed by a pool of Web Workers, each running\n * a wa-sqlite instance in a dedicated thread.\n *\n * @remarks\n * **Browser requirements:** This client uses OPFS through Web Workers; no\n * special HTTP headers are required and cross-origin isolation is not needed.\n * The default `build` needs no browser opt-in; only `build: 'jspi'` does, and\n * JSPI is Chromium-only — an unrelated constraint, not a header requirement.\n *\n * **Worker pool side effect:** Calling this function immediately spawns\n * `poolSize` Web Worker threads and begins asynchronous database\n * initialization. Workers become queryable once they emit a `ready` message.\n *\n * @param file - SQLite database file name within the OPFS origin.\n * Each distinct name corresponds to a separate database file.\n * @param clientOptions - Pool and VFS configuration. Required: `vfs` has no\n * default, because a VFS decides where the database is written.\n * See {@link CreateSQLiteClientOptions} for field defaults.\n * @returns A {@link SQLiteDB} object providing `read`, `write`, `chunk`,\n * `stream`, `first`, `transaction`, `bulkWrite`, `output`, and `close` methods.\n *\n * @throws {SQLiteError} With code `INVALID_OPTION` when `build` is not one of\n * the builds the chosen `vfs` supports. The message names the supported\n * builds; the pairing is declared once, in `VFS_CAPABILITIES`.\n * @throws {SQLiteError} With code `INVALID_OPTION` when `poolSize` exceeds the\n * `maxPoolSize` the chosen `vfs` declares. The message names the cap and the\n * reason for it; both come from `VFS_CAPABILITIES`.\n *\n * @example\n * ```typescript\n * import { createSQLiteClient } from 'browser-sqlite';\n *\n * const db = createSQLiteClient('myapp.sqlite', {\n * poolSize: 3,\n * vfs: 'OPFSAdaptiveVFS',\n * pragmas: { journal_mode: 'WAL', synchronous: 'NORMAL' },\n * });\n *\n * const users = await db.read<{ id: number; name: string }>(\n * 'SELECT id, name FROM users WHERE active = ?',\n * [1],\n * );\n * ```\n */\nexport const createSQLiteClient = (\n file: string,\n clientOptions: CreateSQLiteClientOptions,\n) => {\n // One definition of database identity for the workers, the VFS, the epoch\n // registry, every lock name and the returned `db.debug.file`.\n const dbFile = normalizeDatabaseFile(file);\n\n // FIRST, before anything reads the options. `clientOptions` is required in\n // the type, but a JavaScript caller can still omit it entirely — and then\n // every access below would throw a bare TypeError naming nothing. The `?.`\n // here is the only one left in this function, and it is load-bearing: it is\n // what turns a missing argument into the error that says what to pass.\n //\n // Required, and thrown for rather than defaulted: a moving default would\n // leave a consumer reading an empty database while their bytes sat in a VFS\n // nothing queries.\n if (!clientOptions?.vfs) {\n throw new SQLiteError(\n 'INVALID_OPTION',\n `vfs is required. ${RECOMMENDED_VFS} is the recommended universal choice and was the previous default — pass it to keep reading a database created before this version. Compare VFS in the README's VFS Selection guide, and measure your own targets at https://lalexdotcom.github.io/browser-sqlite/`,\n );\n }\n\n const clientIndex = ++clientCount;\n\n const clientPrefix = `${clientOptions.name ?? 'SQLite'} ${clientIndex}`;\n\n const poolSize = clientOptions.poolSize ?? DEFAULT_POOL_SIZE;\n const pool: (PoolWorker | undefined)[] = [];\n\n const vfs = clientOptions.vfs;\n const build = clientOptions.build ?? defaultBuildFor(vfs);\n\n const capability = VFS_CAPABILITIES[vfs];\n\n // Synchronous: an unsupported combination must fail here and name itself,\n // not surface later as an opaque open-error from a worker that could not\n // instantiate its module.\n if (!(capability.builds as readonly SQLiteBuild[]).includes(build)) {\n throw new SQLiteError(\n 'INVALID_OPTION',\n `${vfs} cannot run on the '${build}' build. Supported: ${capability.builds.join(', ')}.`,\n );\n }\n\n // Resolved once, here, and reused by every worker in the pool and by every\n // restart — a callback must not be re-entered per slot. Undefined when the\n // option was not given, which is what leaves the worker's resolution alone.\n const wasm = resolveWasmLocation(clientOptions.wasmUrl, build, location.href);\n\n if (capability.maxPoolSize !== null && poolSize > capability.maxPoolSize) {\n throw new SQLiteError(\n 'INVALID_OPTION',\n `${vfs} does not support pool sizes greater than ${capability.maxPoolSize}: ${capability.poolLimitReason}. Set poolSize: ${capability.maxPoolSize}.`,\n );\n }\n\n // The engine, not the declaration. Without this the mismatch surfaces later\n // as an opaque open-error from a worker that could not instantiate wasm.\n const absent = missingFeature(vfs, build, detectFeatures());\n if (absent) {\n throw new SQLiteError(\n 'INVALID_OPTION',\n describeMissing(vfs, build, absent),\n );\n }\n\n // Fail at construction, not inside the first unrelated query.\n if (clientOptions.pragmas) renderPragmas(clientOptions.pragmas);\n\n // TEST-ONLY, UNSUPPORTED. Read once here, validated, and converted to a\n // typed internal value so no `any` travels further. Absent from the public\n // options type on purpose — see InternalSQLiteClientOptions in scheduler.ts.\n const testWriterPolicy = (clientOptions as InternalSQLiteClientOptions)\n .__unsafeTestWriterPolicy;\n const writerPolicy: WriterPolicy | undefined =\n typeof testWriterPolicy === 'function' ? testWriterPolicy : undefined;\n\n // ---------------------------------------------------------------------------\n // Startup state: the deferred first-settle verdict\n //\n // While inStartup is true the gate is still closed and handleDeath skips\n // supervisor entirely. The verdict (fast-fail or single retry round) fires\n // from the scheduler's onFirstSettle callback. inStartup is cleared in\n // onGateOpen (which fires after the retry round, or immediately when all\n // slots opened without failure).\n // ---------------------------------------------------------------------------\n let inStartup = true;\n let startupFirstError: SQLiteError | undefined;\n // Every startup death is recorded here keyed by slot index, and removed in\n // spawn's .then() when the slot becomes ready (retry round success). What\n // remains when the gate opens are the slots that permanently failed.\n const startupLosses = new Map<number, SQLiteError>();\n\n /**\n * Creates a new pool worker and adds it to the pool.\n * Sets up message routing via callId for query responses.\n */\n const scheduler = createScheduler<PoolWorker>(\n (() => {\n // onFirstSettle and onGateOpen are callbacks that fire asynchronously\n // (after all const declarations in this scope have been initialised), so\n // references to spawn / failClient / supervisor / emitWorkerLost are\n // safe even though those names appear later in the source.\n const onFirstSettle = (result: {\n openedCount: number;\n failedIndices: number[];\n }) => {\n if (result.openedCount === 0) {\n // Total startup failure: every slot failed to open. No retry —\n // when nothing opened the config is wrong and a retry only delays\n // the error.\n //\n // Emit loss for every failed slot before failing the client — the\n // contract requires the callback to fire before the client is failed.\n // The supervisor is not consulted here: no R1 restart or liveness\n // logic applies when nothing opened; the failure is total and\n // permanent.\n //\n // startupFirstError is always set when openedCount === 0 because\n // every settled-failed slot goes through handleDeath, which sets it.\n // The fallback is unreachable but satisfies the linter.\n inStartup = false;\n for (const [index, error] of startupLosses) {\n emitWorkerLost(index, error);\n }\n startupLosses.clear();\n failClient(\n startupFirstError ??\n new SQLiteError(\n 'WORKER_CRASHED',\n 'All workers failed to open the database.',\n ),\n );\n return;\n }\n // One retry round, hardcoded. The count is not an option yet because:\n // the startup contention that motivates this path (exclusive OPFS\n // handle rotating between workers on Firefox) is a transient, bounded\n // race, not a persistent fault. One round is enough to resolve it.\n // Exposing a knob before we have evidence the default is wrong would\n // make the option permanent to remove.\n for (const index of result.failedIndices) {\n scheduler.rearmSlot(index);\n spawn(index);\n }\n // If failedIndices is empty the gate opens immediately (no re-arming).\n // If not, it stays closed until retry slots settle.\n };\n\n const onGateOpen = () => {\n inStartup = false;\n // Report all startup deaths to the supervisor first so liveCount() is\n // correct for post-startup R1 decisions, and collect verdicts.\n let failClientError: SQLiteError | undefined;\n for (const [index, error] of startupLosses) {\n // 'lost', not 'died': the retry round is capped at one, so these\n // slots are not coming back and the consumer is about to be told so.\n // 'died' would return 'restart' for a slot that had opened, leave it\n // revivable, and spend a restart that never happens — the supervisor\n // would then disagree with the `onWorkerLost` we emit below.\n const verdict = supervisor.report(index, 'lost');\n // Honour a 'fail-client' verdict from the supervisor.\n if (verdict === 'fail-client') failClientError ??= error;\n }\n // Emit all losses BEFORE possibly failing the client — the contract\n // requires the callback to fire before the client is failed.\n for (const [index, error] of startupLosses) {\n emitWorkerLost(index, error);\n }\n startupLosses.clear();\n // Also fail the client when the pool is empty even if no verdict was\n // 'fail-client'. This handles the case where supervisor returns\n // 'restart' for an everReady slot (e.g., slot 0 opened in round 1 and\n // died during the retry round) while slot 1's verdict depends on\n // iteration order — the pool check is order-independent.\n if (\n failClientError !== undefined ||\n pool.filter(Boolean).length === 0\n ) {\n failClient(\n failClientError ??\n startupFirstError ??\n new SQLiteError(\n 'WORKER_CRASHED',\n 'All workers failed to open the database.',\n ),\n );\n }\n };\n\n return writerPolicy\n ? {\n canDesignateWriter: writerPolicy,\n poolSize,\n onFirstSettle,\n onGateOpen,\n }\n : { poolSize, onFirstSettle, onGateOpen };\n })(),\n );\n\n const debugOption = clientOptions.debug;\n\n const debugPrefix =\n typeof debugOption === 'string' ? debugOption : clientPrefix;\n\n const logger = createLogger(debugPrefix, !!debugOption);\n\n const clientDebug = debugOption\n ? createClientDebug(\n dbFile,\n pool,\n {\n vfs,\n pragmas: clientOptions.pragmas ?? {},\n name: clientOptions.name ?? 'SQLite',\n },\n () => scheduler.stats(),\n )\n : undefined;\n\n const debug = clientDebug?.state;\n\n const epochs = epochsFor(dbFile);\n\n /**\n * The barrier. Runs on a leased worker, so nothing can interleave a\n * statement between it and the query the lease was taken for — the lease\n * supplies the atomicity of the pair for free.\n *\n * `target` is captured BEFORE the statement: if another client commits while\n * it is in flight, this connection did not observe that commit and must not\n * be credited with it.\n */\n const applyBarrier = async (worker: PoolWorker) => {\n const target = epochs.current();\n worker.epochTarget = target;\n if (worker.seen >= target) return;\n // Drained, not just dispatched: it is the opening AND closing of the read\n // transaction that refreshes page 1. noServed: true prevents the barrier\n // from resetting the supervisor's restart counter — it is a synthetic probe,\n // not user work.\n const barrierIter = worker.query(BARRIER_SQL, undefined, {\n noServed: true,\n });\n while (!(await barrierIter.next()).done) {\n /* discard rows */\n }\n // Only on success — a failed barrier leaves the worker marked behind so\n // the next attempt re-posts it.\n worker.seen = target;\n };\n\n /** Records a commit. Called after the write, before its promise resolves. */\n const afterWrite = (worker: PoolWorker) => {\n worker.seen = advanceSeen(worker.seen, worker.epochTarget, epochs.bump());\n };\n\n /**\n * Debug-stamps the acquisition with request timing. Extracted from\n * acquireInstrumented so the barrier wrapper can cover both paths uniformly.\n */\n const acquireWithDebug = async (\n kind: 'read' | 'write',\n signal?: AbortSignal,\n ) => {\n // Called only when clientDebug is set — cast to NonNullable to avoid the\n // forbidden non-null assertion operator while preserving the correct type.\n const request = (\n clientDebug as NonNullable<typeof clientDebug>\n ).createRequestDebugState();\n const lease = await scheduler.acquire(kind, signal);\n request.assign(lease.worker.index);\n\n return {\n worker: lease.worker,\n release: () => {\n request.state.releaseTime = Date.now();\n lease.release();\n },\n };\n };\n\n /**\n * The single owner of the request level of the debug tree.\n *\n * There are six acquisition sites; instrumenting each is six chances to\n * miss one. This wrapper stamps `acquireTime` (through `assign`) and\n * `releaseTime`, and is a pass-through when debug is off. Nothing outside it\n * calls `scheduler.acquire`. The barrier runs on the acquired lease before\n * the caller sees it — the lease atomically covers the barrier statement and\n * the real query together.\n */\n const acquireInstrumented = async (\n kind: 'read' | 'write',\n signal?: AbortSignal,\n ) => {\n const lease = clientDebug\n ? await acquireWithDebug(kind, signal)\n : await scheduler.acquire(kind, signal);\n try {\n // Raced, not merely passed a signal. `applyBarrier` drains a real query\n // on the worker, and `PoolWorkerQueryOptions` carries no signal — so on\n // a worker that never answers, that loop is unbounded and every method\n // goes through it. Firefox 154 stopped here, on OPFSCoopSyncVFS, after\n // the two earlier abort paths were closed.\n //\n // This is the second and last phase of a call that was not already\n // abortable: `scheduler.acquire` now honours the signal while queued,\n // and the query phase has honoured it since wave 1. Guarding here rather\n // than at each public method is what makes that complete — an await\n // added to this function later is covered without being remembered.\n //\n // The race abandons the WAIT, not the WORK: the barrier statement runs\n // on. The catch below releases through `quiesce()`, which returns the\n // worker only once it is actually idle, so nothing is re-lent mid-flight.\n const { aborted, teardown } = makeAbortRace(signal);\n try {\n const barrier = applyBarrier(lease.worker);\n await (aborted ? Promise.race([barrier, aborted]) : barrier);\n } finally {\n teardown();\n }\n } catch (error) {\n // The caller never received the lease, so its try/finally cannot return\n // the worker. Release on the same path a normal caller would.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n throw error;\n }\n return lease;\n };\n\n /**\n * Executes a read query and returns all results.\n * Automatically acquires and releases a worker from the pool.\n *\n * @remarks\n * **Read-your-own-writes is guaranteed within the tab.** Any read issued after a\n * write resolves — from that client or from any other client in the same tab on\n * the same database — observes it, regardless of pool size. It is not guaranteed\n * across tabs.\n */\n const read = async <\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n sql: string,\n params?: unknown[],\n options?: OptionsWithSignal,\n ) => {\n assertReadable(sql, 'read');\n const lease = await acquireInstrumented('read', options?.signal);\n try {\n return await readWorker<T>(lease.worker, sql, params, options);\n } finally {\n // The lease returns when the worker confirms it is idle, not when the\n // caller leaves: a worker still inside step() must not be re-lent, and\n // the caller must not wait for it.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n }\n };\n\n /**\n * Executes a query and yields result rows in chunks.\n * The single abort-aware primitive — all other read paths derive from this.\n *\n * @remarks\n * **Worker freshness.** See the `read()` remarks — read-your-own-writes is\n * guaranteed within the tab, not across tabs.\n */\n const chunk = async function* <\n T extends Record<string, unknown> = Record<string, unknown>,\n >(sql: string, params?: unknown[], options?: SQLiteChunkOptions) {\n assertReadable(sql, 'chunk');\n const lease = await acquireInstrumented('read', options?.signal);\n try {\n yield* chunkWorker<T>(lease.worker, sql, params, options);\n } finally {\n // The lease returns when the worker confirms it is idle, not when the\n // caller leaves: a worker still inside step() must not be re-lent, and\n // the caller must not wait for it.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n }\n };\n\n /**\n * Executes a query and streams individual rows (flattened from chunks).\n *\n * @remarks\n * **Worker freshness.** See the `read()` remarks — read-your-own-writes is\n * guaranteed within the tab, not across tabs.\n */\n const stream = async function* <\n T extends Record<string, unknown> = Record<string, unknown>,\n >(sql: string, params?: unknown[], options?: SQLiteChunkOptions) {\n assertReadable(sql, 'stream');\n const lease = await acquireInstrumented('read', options?.signal);\n try {\n yield* streamRows<T>(lease.worker, sql, params, options);\n } finally {\n // The lease returns when the worker confirms it is idle, not when the\n // caller leaves: a worker still inside step() must not be re-lent, and\n // the caller must not wait for it.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n }\n };\n\n /**\n * Executes a write query and returns results with affected row count.\n * Automatically acquires and releases a worker from the pool.\n */\n const write = async <\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n sql: string,\n params?: unknown[],\n options?: OptionsWithSignal,\n ) => {\n const lease = await acquireInstrumented('write', options?.signal);\n try {\n return await writeWorker<T>(lease.worker, sql, params, options);\n } finally {\n // Before the void: release is asynchronous, so write() resolves first. A\n // read chained on this promise would otherwise acquire before the\n // increment, observe the old epoch, and skip the barrier — the exact bug\n // being fixed. In `finally`, so a failed write bumps too: that costs a\n // barrier statement, never a wrong read.\n afterWrite(lease.worker);\n // The lease returns when the worker confirms it is idle, not when the\n // caller leaves: a worker still inside step() must not be re-lent, and\n // the caller must not wait for it.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n }\n };\n\n /**\n * Executes a query and returns only the first row.\n * Breaks after the first chunk — no internal AbortController needed.\n *\n * @remarks\n * **Worker freshness.** See the `read()` remarks — read-your-own-writes is\n * guaranteed within the tab, not across tabs.\n */\n const first = async <\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n sql: string,\n params?: unknown[],\n options?: OptionsWithSignal,\n ) => {\n assertReadable(sql, 'first');\n const lease = await acquireInstrumented('read', options?.signal);\n try {\n return await firstWorker<T>(lease.worker, sql, params, options);\n } finally {\n // The lease returns when the worker confirms it is idle, not when the\n // caller leaves: a worker still inside step() must not be re-lent, and\n // the caller must not wait for it.\n void lease.worker.quiesce().then(\n () => lease.release(),\n () => lease.release(),\n );\n }\n };\n\n const bulkFor = createBulk({ file: dbFile, locks: createLocks(), logger });\n\n const transaction = createTransaction({\n scheduler: { ...scheduler, acquire: acquireInstrumented },\n afterWrite,\n // Wrapped, not passed by reference: handleDeath is declared further down\n // and would be in its temporal dead zone here.\n onPoisoned: (index, error) => handleDeath(index, error),\n bulkFor,\n });\n\n const { bulkWrite, output } = bulkFor({ read, write, transaction });\n\n /** Bounds any settlement that depends on a worker answering. */\n const bounded = async (promise: Promise<unknown>, ms: number) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n promise,\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, ms);\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n };\n\n let closing: Promise<void> | undefined;\n\n /**\n * Drains in-flight work, rejects queued work, closes each database\n * connection, then terminates all workers. Bounded by `drainTimeout`.\n * A second call returns the same promise object — runs exactly once.\n */\n const close = (): Promise<void> => {\n if (closing) return closing;\n closing = (async () => {\n logger.info('client closing');\n // Shutting the front door first: queued waiters reject at once and no new\n // work can be acquired while the in-flight work drains.\n const draining = scheduler.shutdown(\n new SQLiteError('CLIENT_CLOSED', 'The SQLite client has been closed.'),\n );\n // A transaction's lease is held by user code, so this wait is bounded like\n // the rest: a callback that never returns must not make close() hang.\n await bounded(draining, drainTimeout);\n await Promise.all(\n pool.map(async (worker) => {\n if (!worker) return;\n await bounded(worker.close(), drainTimeout);\n worker.terminate();\n }),\n );\n pool.length = 0;\n })();\n return closing;\n };\n\n const openTimeout = clientOptions.openTimeout ?? 30_000;\n const drainTimeout = clientOptions.drainTimeout ?? 60_000;\n\n const supervisor = createSupervisor({\n size: poolSize,\n maxWorkerRestarts: clientOptions.maxWorkerRestarts,\n });\n\n let fatal: SQLiteError | undefined;\n\n const failClient = (error: SQLiteError) => {\n fatal ??= error;\n void scheduler.shutdown(fatal);\n for (const dying of pool) dying?.terminate();\n };\n\n const spawn = (index: number) => {\n // The slot holds a worker again from here — not from `ready`. Without this,\n // a restarted slot stays marked dead and the replacement's own death is\n // taken for a duplicate signal about the worker it replaced: no decision\n // comes back, nothing restarts, nothing fails, and the pool is empty and\n // silent for the rest of the client's life.\n supervisor.report(index, 'spawned');\n const timer = setTimeout(() => {\n handleDeath(\n index,\n new SQLiteError(\n 'TIMEOUT',\n `Worker ${index + 1} did not become ready within ${openTimeout} ms. ` +\n `The database may be held under an exclusive lock by another tab or another client.`,\n ),\n );\n }, openTimeout);\n\n void createPoolWorker({\n index,\n pool,\n clientPrefix,\n file: dbFile,\n vfs,\n build,\n wasm,\n pragmas: clientOptions.pragmas,\n statementCacheSize: DEFAULT_STATEMENT_CACHE_SIZE,\n onDeath: handleDeath,\n onServed: (served) => {\n supervisor.report(served, 'served');\n },\n drainTimeout,\n createWorkerDebugState: clientDebug?.createWorkerDebugState,\n createQueryDebugState: clientDebug?.createQueryDebugState,\n logger,\n })\n .then((worker) => {\n supervisor.report(index, 'ready');\n // If this slot was recorded in startupLosses (it failed in a prior\n // round and is now recovering in the retry), remove the record so it\n // is not reported as permanently lost in onGateOpen.\n startupLosses.delete(index);\n scheduler.add(worker);\n })\n .catch(() => {\n // The rejection is the death already reported through onDeath.\n })\n .finally(() => clearTimeout(timer));\n };\n\n /**\n * Permanently loses a worker slot: logs the loss via the always-on channel\n * and calls the onWorkerLost callback (if provided). Must be called BEFORE\n * failClient so the callback sees the event before the client is shut down.\n */\n const emitWorkerLost = (index: number, error: SQLiteError) => {\n // pool[index] is already undefined here (cleared by handleDeath or startup).\n const live = pool.filter(Boolean).length;\n logger.always.warn(\n `worker ${index + 1} lost; pool is now ${live} of ${poolSize}`,\n );\n const cb = clientOptions.onWorkerLost;\n if (cb) {\n try {\n cb({ index, live, size: poolSize, cause: error });\n } catch (cbError) {\n logger.always.warn(\n `onWorkerLost callback threw: ${cbError instanceof Error ? cbError.message : String(cbError)}`,\n );\n }\n }\n };\n\n const handleDeath = (index: number, error: SQLiteError) => {\n // Snapshot inStartup BEFORE scheduler.remove() may trigger onFirstSettle or\n // onGateOpen (both of which can change inStartup synchronously).\n const wasInStartup = inStartup;\n\n if (wasInStartup) {\n // During startup (gate still closed), defer the verdict to onFirstSettle.\n // A slot that fails before all others have settled must not be restarted\n // or marked lost immediately: we cannot yet distinguish \"only this slot\n // is broken\" from \"contention during startup resolved itself for others\".\n //\n // Set startupFirstError BEFORE scheduler.remove() so that onFirstSettle\n // (which fires synchronously inside remove()) reads the correct error.\n startupFirstError ??= error;\n // Record every startup death — not only retry-slot deaths (defect 1).\n // The entry is removed in spawn's .then() when the slot becomes ready,\n // so only permanently lost slots remain by the time onGateOpen fires.\n startupLosses.set(index, error);\n }\n\n // Terminate and clear BEFORE scheduler.remove() so that emitWorkerLost\n // (called from onGateOpen / post-startup path, both inside or after remove)\n // computes the correct live count from pool.\n pool[index]?.terminate();\n pool[index] = undefined;\n\n scheduler.remove(index); // may synchronously trigger onFirstSettle/onGateOpen\n\n if (wasInStartup) return; // startup: handled entirely by the scheduler callbacks\n\n // Post-startup: apply R1 exactly as before — supervisor decides.\n const decision = supervisor.report(index, 'died');\n if (decision === 'restart') {\n logger.warn(`restarting worker ${index + 1}`);\n void spawn(index);\n } else if (decision === 'lost') {\n // Slot permanently lost, but the pool still has workers.\n emitWorkerLost(index, error);\n } else if (decision === 'fail-client') {\n // Last worker gone — emit loss (with live=0) before failing the client.\n emitWorkerLost(index, error);\n failClient(error);\n }\n };\n\n // Initialize the worker pool with the requested number of workers\n for (let index = 0; index < poolSize; index += 1) spawn(index);\n\n // Return the public API\n const api = {\n chunk,\n read,\n write,\n stream,\n first,\n transaction,\n bulkWrite,\n output,\n close,\n\n debug,\n };\n return api;\n};\n","/**\n * Pure worker scheduling: availability, wait queues, writer designation.\n *\n * This module is deliberately free of `Worker` and DOM imports so\n * it can be exercised by fast Node tests. B1 survived for months because the\n * scheduler was only reachable through slow browser tests.\n */\n\nimport type { CreateSQLiteClientOptions } from './client';\n\n/**\n * A borrowed worker. `release()` is the only way back into the pool and is\n * idempotent — a second call is a no-op, not an error.\n */\nexport type Lease<W> = {\n readonly worker: W;\n release: () => void;\n};\n\n/**\n * Decides whether a worker index may hold the write designation. The default\n * accepts every index, so production behaviour is exactly what it was.\n */\nexport type WriterPolicy = (index: number) => boolean;\n\n/**\n * TEST-ONLY, UNSUPPORTED, removable without notice.\n *\n * The barrier's browser test needs the failing configuration — writer not on\n * the worker that serves the read — to be deterministic; at startup chance it\n * occurs ~3 runs in 10. This type is declared here, and NOT in `client.ts`,\n * because `src/index.ts` re-exports only `./client` and `./errors`: keeping it\n * out of that path keeps it out of the published `.d.ts` and out of every\n * consumer's autocompletion. `CreateSQLiteClientOptions` is pulled in with\n * `import type`, which is erased at build time and creates no runtime cycle.\n *\n * A predicate that refuses every index leaves writes queued forever — use it\n * with `poolSize >= 2`.\n */\nexport type InternalSQLiteClientOptions = CreateSQLiteClientOptions & {\n __unsafeTestWriterPolicy?: WriterPolicy;\n};\n\nexport type Scheduler<W> = {\n add: (worker: W) => void;\n /**\n * Leases a worker, queueing when none is free.\n *\n * `signal` aborts the WAIT, and only the wait: it rejects with\n * `signal.reason` while the request is still queued, and is ignored once a\n * lease has been granted — from that point the caller owns the worker and\n * owes a `release()`. Without it an abort could not land at all while the\n * pool had nothing to lend, which is the state a VFS rotating one exclusive\n * OPFS handle can stay in indefinitely.\n */\n acquire: (kind: 'read' | 'write', signal?: AbortSignal) => Promise<Lease<W>>;\n /**\n * Takes a worker out of the pool for good. A lease already outstanding on\n * that index becomes inert: its `release()` neither hands the worker back nor\n * counts towards `shutdown()`'s wait.\n */\n remove: (index: number) => void;\n /**\n * Closes the front door. Queued waiters reject with `reason`, later\n * acquisitions reject the same way, and the returned promise settles when the\n * last outstanding lease has come back.\n */\n shutdown: (reason: Error) => Promise<void>;\n /**\n * Read-only counters for the debug subsystem. The scheduler stays pure: it\n * exposes numbers and knows nothing about debug (spec §3.2).\n */\n stats: () => {\n read: number;\n write: number;\n available: number;\n leased: number;\n /**\n * Callers suspended on the readiness gate. They are in NEITHER wait queue —\n * the gate is awaited before `takeAvailable` is ever reached — so `read`\n * and `write` cannot see them, and without this the debug surface reports\n * an idle pool for the whole startup window.\n *\n * Waiting for the pool to *exist* is a different wait from waiting for a\n * free worker, which is why this is its own counter and not folded in.\n */\n gated: number;\n };\n /**\n * Removes a slot from the settled-set so that its next `add()` or `remove()`\n * call counts again toward opening the readiness gate. Only effective while\n * the gate is still closed; a no-op once the gate has opened.\n *\n * Used by the startup retry round: the client re-arms the failed slots so\n * the gate stays closed until the retry slots have settled.\n */\n rearmSlot: (index: number) => void;\n};\n\n/**\n * Creates a scheduler over workers identified by a numeric `index`.\n *\n * @param opts.onIdle - Called when a released worker returns to the available\n * set with nothing queued behind it. The scheduler itself knows nothing about\n * worker state.\n */\nexport const createScheduler = <W extends { index: number }>(\n opts: {\n onIdle?: (worker: W) => void;\n canDesignateWriter?: WriterPolicy;\n /**\n * Total number of worker slots the pool will spawn. Once every slot has\n * settled (via `add` when it becomes ready, or via `remove` when it dies or\n * fails to open), a one-shot gate is lifted and `acquire()` may proceed.\n * Omit or pass 0 for an immediately-open gate (tests and single-shot use).\n */\n poolSize?: number;\n /**\n * Called exactly once when every slot in [0, poolSize) has settled for the\n * first time. Fires before the gate opens so the callback can call\n * `rearmSlot()` to extend the wait for a retry round.\n *\n * `openedCount` — slots that settled via `add()` (became ready).\n * `failedIndices` — slots that settled via `remove()` (died / timed out).\n */\n onFirstSettle?: (result: {\n openedCount: number;\n failedIndices: number[];\n }) => void;\n /**\n * Called when the readiness gate resolves (opens). Not called when the gate\n * is rejected via `shutdown()`. Use this to clear any startup-pending flag\n * after the retry round (if any) has fully settled.\n */\n onGateOpen?: () => void;\n } = {},\n): Scheduler<W> => {\n const workers: (W | undefined)[] = [];\n\n // Availability lives HERE and nowhere else. No worker carries an `available`\n // flag, so no other module can republish a borrowed worker — which is exactly\n // how B1 happened.\n //\n // A second guarantee rests on this set, and nothing about it is visible from\n // here. A leased worker leaves `available` until `release()` puts it back, so\n // exactly one query is ever in flight per worker. `worker/statement-cache.ts`\n // is built on that and takes no lock of any kind: its statements outlive the\n // query that compiled them, and are reset and cleared on the way out. Lend a\n // worker to a second concurrent caller and that reset lands on a statement\n // another query is part-way through — rewound cursor, cleared bindings, wrong\n // rows — while losing a worker can finalise a handle that other query still\n // holds, which is a use-after-free on a `sqlite3_stmt` pointer. Before the\n // cache, breaking this was merely confusing.\n const available = new Set<number>();\n\n const dead = new Set<number>();\n const leased = new Set<number>();\n // Per-index generation counter. Bumped by remove() so that a release() from\n // a lease created before the remove can detect it is stale and do nothing.\n const generations = new Map<number, number>();\n const gen = (index: number) => generations.get(index) ?? 0;\n\n let shutdownReason: Error | undefined;\n let shutdownDeferred: PromiseWithResolvers<void> | undefined;\n\n // One-shot readiness gate: lifts once every slot in [0, poolSize) has\n // settled — either via add() (ready) or remove() (died / failed to open).\n // poolSize 0 or absent → gate is open from the start.\n //\n // Genuinely one-shot: once gateOpen is true it stays true. A worker that\n // restarts (remove → add) after the gate has lifted must not re-block callers\n // already in flight.\n const settledSlots = new Set<number>();\n let gateOpen = (opts.poolSize ?? 0) === 0;\n const gateDeferred = Promise.withResolvers<void>();\n if (gateOpen) gateDeferred.resolve();\n // Suppress unhandled-rejection when shutdown() fires before any acquire()\n // has attached a handler. Each awaiting acquire() still sees the rejection.\n void gateDeferred.promise.catch(() => {});\n\n // Tracks slots that settled via add() (became ready) in the first round,\n // used to compute openedCount/failedIndices for onFirstSettle.\n const firstSettleOpened = new Set<number>();\n let firstSettleFired = false;\n\n // Callers currently suspended on the gate. See `stats().gated`.\n let gatedWaiters = 0;\n\n const settleGateSlot = (index: number, kind: 'opened' | 'failed') => {\n if (gateOpen || settledSlots.has(index)) return;\n settledSlots.add(index);\n if (kind === 'opened') firstSettleOpened.add(index);\n if (settledSlots.size < (opts.poolSize ?? 0)) return;\n\n // All slots have now settled (first round or retry round).\n if (opts.onFirstSettle && !firstSettleFired) {\n firstSettleFired = true;\n const failedIndices = [...settledSlots].filter(\n (i) => !firstSettleOpened.has(i),\n );\n opts.onFirstSettle({\n openedCount: firstSettleOpened.size,\n failedIndices,\n });\n // After the callback the client may have:\n // (a) called rearmSlot() for retry slots → settledSlots.size < poolSize,\n // gate stays closed; or\n // (b) called shutdown() (opened===0 fast-fail) → shutdownReason is set.\n // In both cases skip the resolve/open below.\n if (settledSlots.size < (opts.poolSize ?? 0) || shutdownReason) return;\n }\n\n gateOpen = true;\n gateDeferred.resolve();\n opts.onGateOpen?.();\n };\n\n const readerQueue: Array<{\n resolve: (worker: W) => void;\n reject: (error: Error) => void;\n }> = [];\n const writerQueue: Array<{\n resolve: (worker: W) => void;\n reject: (error: Error) => void;\n }> = [];\n\n // Index of the worker designated for writes, or -1 when none is designated.\n //\n // The designation exists to serialize writes onto one connection, and it\n // lasts no longer than that: handOver releases it as soon as no write is\n // queued behind it, so `designated` and `leased` coincide. It was sticky for\n // the life of the worker until wave 4's barrier shipped — a write landing on\n // a worker that had not absorbed the previous commit failed at `prepare` with\n // `no such table`. `applyBarrier` covers `kind: 'write'`, so a newly\n // designated writer catches up before it prepares anything.\n //\n // Measured 2026-08-21: with a long read holding worker 0, five writes took\n // 30 ms spread over worker 1 against 934-1052 ms pinned behind the read.\n let currentWriterIndex = -1;\n\n /**\n * The worker that most recently held the write designation, kept after that\n * designation is released. `currentWriterIndex` answers \"who may write now\";\n * this answers \"who has already seen the last commit\", which outlives it.\n */\n let lastWriterIndex = -1;\n\n const canDesignate = opts.canDesignateWriter ?? (() => true);\n\n /**\n * Serves the writer queue from `worker` when it may hold the designation.\n * Extracted because `handOver` and `add` carried this branch twice, and a\n * predicate that lives in only one of the two copies is a silent hole.\n */\n const serveWriterFirst = (worker: W): boolean => {\n if (!writerQueue.length) return false;\n if (currentWriterIndex !== worker.index && currentWriterIndex !== -1)\n return false;\n // An already-designated writer is not re-judged; only a NEW designation is.\n if (currentWriterIndex === -1 && !canDesignate(worker.index)) return false;\n // Claim the designation before serving: without this, a later write\n // acquisition could designate a second writer while this one still runs.\n currentWriterIndex = worker.index;\n lastWriterIndex = worker.index;\n writerQueue.shift()?.resolve(worker);\n return true;\n };\n\n const checkShutdown = () => {\n if (shutdownDeferred && leased.size === 0) shutdownDeferred.resolve();\n };\n\n const handOver = (worker: W) => {\n if (serveWriterFirst(worker)) return;\n\n // Release the designation. Reaching this line proves no write is queued:\n // serveWriterFirst's only negative exit that leaves the designation on this\n // worker is an empty writerQueue. It sits ABOVE the reader branch because\n // that branch returns — the release has to happen on every exit, not only\n // the idle one.\n //\n // Measured, so nobody \"fixes\" it: moving this above serveWriterFirst is\n // behaviourally equivalent in production, since the call reclaims the\n // designation on the same worker at once. The two differ only under a\n // canDesignateWriter that refuses this index — tests only.\n if (currentWriterIndex === worker.index) currentWriterIndex = -1;\n\n if (readerQueue.length) {\n // Reads never alter the designation — rule 1.\n readerQueue.shift()?.resolve(worker);\n return;\n }\n\n available.add(worker.index);\n opts.onIdle?.(worker);\n };\n\n const makeLease = (worker: W): Lease<W> => {\n leased.add(worker.index);\n const myGen = gen(worker.index);\n let released = false;\n return {\n worker,\n release: () => {\n if (released) return;\n released = true;\n if (gen(worker.index) !== myGen) {\n // Stale lease: remove() was called after this lease was created,\n // bumping the generation. Handing the worker back would corrupt the\n // pool (it could be held by a new lease on the revived slot).\n checkShutdown();\n return;\n }\n leased.delete(worker.index);\n handOver(worker);\n checkShutdown();\n },\n };\n };\n\n const takeAvailable = (write: boolean): W | undefined => {\n if (write && currentWriterIndex > -1) {\n if (!available.has(currentWriterIndex)) return undefined;\n available.delete(currentWriterIndex);\n return workers[currentWriterIndex];\n }\n\n // Prefer the worker that wrote last, for a read and for a new designation\n // alike. A read served there skips the barrier, that worker having already\n // seen the commit; a write served there keeps a run of writes on one\n // connection instead of walking the pool between batches.\n //\n // A PREFERENCE, never a pin: it picks only among workers that are already\n // available, so it can never make anything wait. That is what keeps it\n // clear of the measurement above — which was about writes queued BEHIND a\n // busy designated writer, not about which free worker to choose.\n //\n // `workers[-1]` is undefined, so the unset case needs no separate guard.\n const preferred = workers[lastWriterIndex];\n if (\n preferred !== undefined &&\n available.has(lastWriterIndex) &&\n (!write || canDesignate(lastWriterIndex))\n ) {\n available.delete(lastWriterIndex);\n if (write) currentWriterIndex = lastWriterIndex;\n return preferred;\n }\n\n // Lowest-index-first for both reads and new writes (reads never touch\n // the designation; write designation is set below when a new one starts).\n const found = workers.find(\n (worker) =>\n worker !== undefined &&\n available.has(worker.index) &&\n (!write || canDesignate(worker.index)),\n );\n if (!found) return undefined;\n\n available.delete(found.index);\n if (write) {\n currentWriterIndex = found.index;\n lastWriterIndex = found.index;\n }\n return found;\n };\n\n return {\n add: (worker) => {\n // Settle this slot in the gate (first call per index only; restarts are\n // ignored because gateOpen is already true by then).\n settleGateSlot(worker.index, 'opened');\n\n dead.delete(worker.index);\n workers[worker.index] = worker;\n // Serve any requests that arrived before this worker was ready, preserving\n // the same writer-first priority as handOver. Does NOT call onIdle — the\n // worker is newly joining the pool, not returning from a lease.\n if (serveWriterFirst(worker)) return;\n if (readerQueue.length) {\n // Reads never alter the designation — rule 1.\n readerQueue.shift()?.resolve(worker);\n return;\n }\n available.add(worker.index);\n },\n\n remove: (index) => {\n // Settle this slot in the gate — a dead slot counts. First call per\n // index only; a restart after the gate is open is a no-op here.\n settleGateSlot(index, 'failed');\n\n dead.add(index);\n available.delete(index);\n leased.delete(index);\n workers[index] = undefined;\n // Bump the generation so any outstanding lease on this index knows it is\n // stale when its release() eventually fires.\n generations.set(index, gen(index) + 1);\n if (currentWriterIndex === index) currentWriterIndex = -1;\n // A respawned slot is a different connection with a fresh epoch, so the\n // freshness hint this index carried is void.\n if (lastWriterIndex === index) lastWriterIndex = -1;\n checkShutdown();\n },\n\n shutdown: (reason) => {\n // Reject the gate so any caller blocked on it gets the shutdown error.\n if (!gateOpen) {\n gateOpen = true;\n gateDeferred.reject(reason);\n }\n shutdownReason ??= reason;\n shutdownDeferred ??= Promise.withResolvers<void>();\n for (const waiter of readerQueue.splice(0)) waiter.reject(reason);\n for (const waiter of writerQueue.splice(0)) waiter.reject(reason);\n checkShutdown();\n return shutdownDeferred.promise;\n },\n\n stats: () => ({\n read: readerQueue.length,\n write: writerQueue.length,\n available: available.size,\n leased: leased.size,\n gated: gatedWaiters,\n }),\n\n rearmSlot: (index) => {\n if (!gateOpen) settledSlots.delete(index);\n },\n\n acquire: async (kind, signal) => {\n if (shutdownReason) throw shutdownReason;\n // Before the queue, not after: a caller who has already given up must not\n // take a place in line and be served a worker nobody will release.\n signal?.throwIfAborted();\n\n // Readiness gate: block until every slot has settled. The gate is\n // one-shot — once open it never closes, so this branch is never re-entered\n // by callers already in flight after a worker restarts.\n if (!gateOpen) {\n // In a `finally`, so an abort or a shutdown rejection decrements too:\n // a leaked count would make the pool look permanently congested.\n gatedWaiters += 1;\n try {\n // The tie is settled by microtask order, and it settles in favour of\n // the gate: `resolve()` queues its reaction before a synchronous\n // `abort()` queues `abortP`'s, so a caller aborted in the very tick\n // the last slot settles still gets its lease. That is the queue\n // path's behaviour too — `onAbort` there returns early once the\n // waiter has been shifted — so the two agree rather than diverge.\n if (signal) {\n const { promise: abortP, reject: abortReject } =\n Promise.withResolvers<void>();\n const onGateAbort = () => abortReject(signal.reason);\n signal.addEventListener('abort', onGateAbort, { once: true });\n try {\n await Promise.race([gateDeferred.promise, abortP]);\n } finally {\n signal.removeEventListener('abort', onGateAbort);\n }\n } else {\n await gateDeferred.promise;\n }\n } finally {\n gatedWaiters -= 1;\n }\n }\n\n // Re-check after the gate: shutdown() may have fired while we waited\n // (remove() settles the gate synchronously before failClient can run, so\n // the gate resolves a microtask before shutdown() sets shutdownReason).\n if (shutdownReason) throw shutdownReason;\n\n const write = kind === 'write';\n\n const immediate = takeAvailable(write);\n if (immediate) return makeLease(immediate);\n\n const { promise, resolve, reject } = Promise.withResolvers<W>();\n const queue = write ? writerQueue : readerQueue;\n const waiter = { resolve, reject };\n queue.push(waiter);\n\n if (!signal) return makeLease(await promise);\n\n const onAbort = () => {\n const at = queue.indexOf(waiter);\n // The guard is the whole correctness of this branch, in both\n // directions. A waiter still in the queue is REMOVED, never merely\n // rejected in place: the drains take the head with `shift()`, so a\n // dead entry left behind would be handed a worker that nobody then\n // releases. And a waiter already shifted is left alone: its lease is\n // real, the caller owes a release for it, and rejecting here would\n // strand that worker for the life of the client. The in-query abort\n // race in `queries.ts` covers what happens after the lease.\n //\n // Read-then-mutate needs no lock: this is one synchronous block with\n // no await and no yield, and the drains (`handOver`, `serveWriterFirst`)\n // are synchronous too, so nothing can shift this waiter out between the\n // lookup and the removal. `splice` before `reject` for the same reason\n // read the other way — `reject` only schedules a microtask, but the\n // queue is left consistent before anything else can observe it.\n const queued = at !== -1;\n if (!queued) return;\n queue.splice(at, 1);\n reject(signal.reason);\n };\n signal.addEventListener('abort', onAbort, { once: true });\n try {\n return makeLease(await promise);\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n },\n };\n};\n","/**\n * The prefixed logger the `debug` option turns on.\n *\n * Lifecycle events only — worker created, ready, open-error, crash, restart,\n * worker loss, close, skipped sweep. A line per query would be illegible under\n * real load and would put user values on the console; query throughput belongs\n * in `db.debug`, not here.\n */\nexport type Logger = {\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n /**\n * Always writes through the sink regardless of the `enabled` flag.\n * Use for events that must be visible even when debug logging is off —\n * permanent pool shrinkage being the primary case.\n */\n always: {\n warn: (message: string) => void;\n };\n};\n\ntype Sink = Pick<Console, 'debug' | 'warn' | 'error'>;\n\nexport const createLogger = (\n prefix: string,\n enabled: boolean,\n sink: Sink = console,\n): Logger => {\n const line = (message: string) => `[${prefix}] ${message}`;\n // always.warn bypasses the enabled gate — pool shrinkage must be visible\n // even when debug logging is off, so the sink is the point (it is injectable\n // by tests, unlike a bare console.warn call).\n const always = { warn: (message: string) => sink.warn(line(message)) };\n\n if (!enabled)\n return { info: () => {}, warn: () => {}, error: () => {}, always };\n\n return {\n info: (message) => sink.debug(line(message)),\n warn: (message) => sink.warn(line(message)),\n error: (message) => sink.error(line(message)),\n always,\n };\n};\n","import type { CreateSQLiteClientOptions } from './client';\nimport type { PoolWorker } from './pool';\nimport type { SQLiteVFS } from './types';\n\nexport const debugSQLQuery = (sql: string, params?: unknown[]) => {\n if (!params || params.length === 0) return sql;\n\n let result = '';\n let paramIndex = 0;\n let i = 0;\n\n while (i < sql.length) {\n if (sql[i] === '?') {\n // Check if it's a positional parameter (?001, ?002, etc.)\n if (\n i + 3 < sql.length &&\n /\\d/.test(sql[i + 1]) &&\n /\\d/.test(sql[i + 2]) &&\n /\\d/.test(sql[i + 3])\n ) {\n const position = sql.substring(i + 1, i + 4);\n const numIndex = parseInt(position, 10) - 1;\n\n if (!Number.isNaN(numIndex) && params[numIndex] !== undefined) {\n result += formatValue(params[numIndex]);\n } else {\n result += 'NULL';\n }\n i += 4; // Skip ? and 3 digits\n } else {\n // Simple parameter (?)\n if (paramIndex < params.length) {\n result += formatValue(params[paramIndex++]);\n } else {\n result += 'NULL';\n }\n i++;\n }\n } else if (sql[i] === \"'\" || sql[i] === '\"') {\n // Skip string literals to avoid replacing ? inside them\n const quote = sql[i];\n result += sql[i++];\n while (i < sql.length) {\n result += sql[i];\n if (sql[i] === quote) {\n // Check for escaped quote\n if (i + 1 < sql.length && sql[i + 1] === quote) {\n result += sql[++i];\n } else {\n i++;\n break;\n }\n }\n i++;\n }\n } else {\n result += sql[i++];\n }\n }\n\n return result;\n\n function formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return 'NULL';\n }\n if (typeof value === 'string') {\n return `'${value.replace(/'/g, \"''\")}'`;\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (value instanceof Date) {\n return `'${value.toISOString()}'`;\n }\n // `Buffer` does not exist in a browser. A Node Buffer is a Uint8Array\n // subclass, so this single branch still covers both.\n if (value instanceof Uint8Array) {\n let hex = '';\n for (const byte of value) {\n hex += byte.toString(16).padStart(2, '0');\n }\n return `X'${hex}'`;\n }\n return `'${JSON.stringify(value).replace(/'/g, \"''\")}'`;\n }\n};\n\ntype QueryDebugState = {\n sql: string;\n params?: unknown[] | undefined;\n startTime: number;\n firstRowTime?: number;\n endTime?: number;\n error?: unknown;\n affectedRows: number;\n prepared: number;\n};\n\ntype RequestDebugState = {\n startTime: number;\n acquireTime?: number;\n releaseTime?: number;\n affectedRows: number;\n queries: QueryDebugState[];\n currentQuery?: QueryDebugState;\n};\n\ntype WorkerDebugState = {\n index: number;\n name: string;\n creationTime: number;\n initializationTime?: number;\n requests: RequestDebugState[];\n currentRequest?: RequestDebugState;\n readonly status: string;\n};\n\nexport type ClientDebugState = {\n readonly file: string;\n readonly vfs: SQLiteVFS;\n readonly pragmas: Record<string, string>;\n readonly name: string;\n readonly queue: {\n readonly read: number;\n readonly write: number;\n /**\n * Callers suspended on the pool's readiness gate, waiting for the pool to\n * exist rather than for a free worker. They sit in neither wait queue, so\n * `read` and `write` are both 0 while they wait — during startup, and\n * during the retry round that follows a failed open.\n */\n readonly gated: number;\n };\n workers: WorkerDebugState[];\n};\n\nconst MAX_QUERY_HISTORY_LENGTH = 50;\nconst MAX_REQUEST_HISTORY_LENGTH = 50;\n\nexport const createClientDebug = (\n file: string,\n pool: (PoolWorker | undefined)[],\n clientOptions: Required<\n Pick<CreateSQLiteClientOptions, 'vfs' | 'pragmas' | 'name'>\n >,\n stats: () => { read: number; write: number; gated: number },\n) => {\n const { vfs, pragmas, name } = clientOptions;\n\n // Read through to the scheduler: the old counters were incremented by hand at\n // every acquire/release site and went stale the moment one was missed.\n const queue = {\n get read() {\n return stats().read;\n },\n get write() {\n return stats().write;\n },\n get gated() {\n return stats().gated;\n },\n };\n\n const clientState: ClientDebugState = {\n file,\n vfs,\n pragmas,\n name,\n queue,\n workers: [],\n };\n\n const createWorkerDebugState = (index: number, name: string) => {\n const state: WorkerDebugState = new Proxy(\n {\n index,\n name,\n requests: [],\n status: pool[index]?.status ?? 'EMPTY',\n creationTime: Date.now(),\n },\n {\n get: (target, prop) => {\n if (prop === 'status') {\n return pool[index]?.status ?? 'EMPTY';\n }\n return target[prop as keyof typeof target];\n },\n },\n );\n clientState.workers[index] = state;\n return state;\n };\n\n const createRequestDebugState = () => {\n const state: RequestDebugState = {\n queries: [],\n startTime: Date.now(),\n affectedRows: 0,\n };\n return {\n state,\n assign: (index: number) => {\n const worker = clientState.workers[index];\n if (worker) {\n state.acquireTime = Date.now();\n // Bounded: this array is pushed to on EVERY request and used to grow\n // with the client's total query count (D5 §1.3, the blocking fix).\n if (worker.requests.length >= MAX_REQUEST_HISTORY_LENGTH)\n worker.requests.shift();\n worker.requests.push(state);\n worker.currentRequest = state;\n }\n },\n };\n };\n\n const createQueryDebugState = (\n workerIndex: number,\n sql: string,\n params?: unknown[],\n ) => {\n const state: QueryDebugState = {\n sql,\n params,\n startTime: Date.now(),\n affectedRows: 0,\n prepared: 0,\n };\n const worker = clientState.workers[workerIndex];\n if (worker?.currentRequest) {\n if (worker.currentRequest.queries.length >= MAX_QUERY_HISTORY_LENGTH) {\n worker.currentRequest.queries.shift();\n }\n worker.currentRequest.queries.push(state);\n worker.currentRequest.currentQuery = state;\n }\n return state;\n };\n\n return {\n state: clientState,\n createWorkerDebugState,\n createRequestDebugState,\n createQueryDebugState,\n } as const;\n};\n","/**\n * Pure restart policy for worker slots.\n *\n * Deliberately free of `Worker` and DOM imports so Node tests can\n * drive it in milliseconds — the same reason `scheduler.ts` is pure. B1 lived\n * for months because the only way to reach the pool's decisions was a browser.\n *\n * The caller reports facts; this module returns a decision and never acts.\n */\nexport type SupervisorDecision = 'restart' | 'lost' | 'fail-client';\n\nexport type Supervisor = {\n report: (\n index: number,\n event: 'spawned' | 'ready' | 'served' | 'died' | 'lost',\n ) => SupervisorDecision | undefined;\n};\n\ntype Slot = {\n everReady: boolean;\n alive: boolean;\n lost: boolean;\n restarts: number;\n};\n\nexport const createSupervisor = (options: {\n size: number;\n maxWorkerRestarts?: number | undefined;\n}): Supervisor => {\n const { size, maxWorkerRestarts = 1 } = options;\n\n const slots: Slot[] = Array.from({ length: size }, () => ({\n everReady: false,\n alive: true,\n lost: false,\n restarts: 0,\n }));\n\n const liveCount = () => slots.filter((slot) => slot.alive).length;\n\n return {\n report: (index, event) => {\n const slot = slots[index];\n if (!slot) return undefined;\n\n if (event === 'spawned') {\n // A slot is alive from the moment a worker is created for it — which is\n // what the constructor's `alive: true` already encodes for the first\n // spawn. Without this event a restarted slot never re-enters that\n // state, so the replacement's death reads as a duplicate signal for the\n // worker that died before it: the guard below returns no decision, the\n // client neither restarts nor fails, and every queued request waits on\n // a pool that will never have a worker again.\n if (slot.lost) return undefined;\n slot.alive = true;\n return undefined;\n }\n\n if (event === 'ready') {\n // A lost slot cannot be revived: the loss was permanent and a\n // late ready would inflate liveCount, masking an empty pool.\n if (slot.lost) return undefined;\n slot.everReady = true;\n slot.alive = true;\n // Deliberately NOT resetting `restarts`: a worker that boots fine and\n // dies on every query would otherwise restart forever, silently.\n return undefined;\n }\n\n if (event === 'served') {\n // A stale done message can arrive after the slot was declared dead; if\n // it reset restarts then, it would silently refill the spent budget.\n if (!slot.alive) return undefined;\n slot.restarts = 0;\n return undefined;\n }\n\n if (event === 'lost') {\n // A death the caller has ALREADY judged terminal, which 'died' cannot\n // express. The startup retry round is capped at one, so a slot the\n // client has announced through `onWorkerLost` must never come back.\n // Reported as 'died' it would take the restart branch instead: `lost`\n // would stay false, leaving the slot revivable by a later\n // 'spawned'/'ready', and a restart would be charged against a budget\n // for a restart that never happens. The supervisor's view and the\n // consumer's would then disagree, with no source of truth to arbitrate.\n //\n // Same duplicate-signal guard as 'died': one report per slot.\n if (!slot.alive) return undefined;\n slot.alive = false;\n slot.lost = true;\n return liveCount() === 0 ? 'fail-client' : 'lost';\n }\n\n // 'died' — a slot already counted as dead reports once per signal\n // (onerror and a drain timeout can both fire), so ignore repeats.\n if (!slot.alive) return undefined;\n slot.alive = false;\n\n // R1: a slot that never worked is a configuration error, not an\n // accident. Restarting it only delays the diagnostic.\n if (slot.everReady && slot.restarts < maxWorkerRestarts) {\n slot.restarts += 1;\n return 'restart';\n }\n\n slot.lost = true;\n return liveCount() === 0 ? 'fail-client' : 'lost';\n },\n };\n};\n","import { SQLiteError } from './errors';\nimport { createLocks, initLockName } from './locks';\nimport { busyFromCode, spawnWorker } from './pool';\nimport {\n defaultBuildFor,\n RECOMMENDED_VFS,\n type SQLiteBuild,\n type SQLiteVFS,\n VFS_CAPABILITIES,\n type WorkerMessageData,\n} from './types';\nimport { normalizeDatabaseFile, resolveWasmLocation } from './utils';\n\nexport type DeleteDatabaseOptions = {\n /**\n * Which VFS holds the database. Required for the same reason it is required\n * on `createSQLiteClient`: a VFS decides where the bytes live, so deleting\n * without naming one deletes in the wrong store — or nowhere, while\n * reporting success.\n */\n vfs: SQLiteVFS;\n /**\n * Which wa-sqlite build to load. It does **not** affect where the database\n * lives; it is here only because a VFS runs solely on the builds it\n * declares, and one of them must be loaded to instantiate the VFS at all.\n * @defaultValue the first build the VFS declares\n */\n build?: SQLiteBuild;\n /**\n * Where the worker fetches its `.wasm`, with the same meaning as on\n * `createSQLiteClient`. A deployment that needs it to open a database needs\n * it to delete one.\n */\n wasmUrl?: string | ((build: SQLiteBuild) => string);\n};\n\n/**\n * Deletes a database and the two siblings SQLite may leave beside it.\n *\n * Deleting a database that is not there is success — SQLite's own `xDelete`\n * behaves the same way, and a caller who wanted it gone has got what they\n * asked for.\n *\n * Nothing a VFS keeps for itself is touched: not the IndexedDB store, which is\n * shared by every database that VFS holds on this origin, and not the\n * `AccessHandlePoolVFS` directory, whose files *are* its reusable capacity.\n * The bytes of the named database are freed in both cases.\n *\n * @throws {SQLiteError} `INVALID_OPTION` when `vfs` is missing or the `build`\n * is not one the VFS supports — synchronously in spirit, as a rejection here.\n * @throws {SQLiteError} `BUSY` when the database is open or being opened, in\n * this tab or another. A connection already holding its handles cannot be\n * revoked from here; see the README's Known Limitations.\n */\nexport const deleteDatabase = async (\n file: string,\n options: DeleteDatabaseOptions,\n): Promise<void> => {\n if (!options?.vfs) {\n throw new SQLiteError(\n 'INVALID_OPTION',\n `vfs is required. Pass the VFS the database was created with — ${RECOMMENDED_VFS} is the recommended universal choice. A database written through one VFS is not visible through another, so deleting through the wrong one deletes nothing.`,\n );\n }\n\n const vfs = options.vfs;\n const build = options.build ?? defaultBuildFor(vfs);\n const capability = VFS_CAPABILITIES[vfs];\n\n if (!(capability.builds as readonly SQLiteBuild[]).includes(build)) {\n throw new SQLiteError(\n 'INVALID_OPTION',\n `${vfs} cannot run on the '${build}' build. Supported: ${capability.builds.join(', ')}.`,\n );\n }\n\n // Nothing was ever persisted, so there is nothing to delete and no worker\n // worth spawning to say so.\n if (capability.layout === 'memory') return;\n\n const dbFile = normalizeDatabaseFile(file);\n const wasm = resolveWasmLocation(options.wasmUrl, build, location.href);\n\n const ran = await createLocks().tryWithLock(initLockName(dbFile), () =>\n runDelete({ file: dbFile, vfs, build, wasm }),\n );\n\n if (!ran) {\n throw new SQLiteError(\n 'BUSY',\n `${dbFile} is being opened or deleted elsewhere. Close every client on it, in every tab, and try again.`,\n );\n }\n};\n\n/**\n * How long a delete may take before the worker is presumed unable to answer.\n * Matches `openTimeout`'s default, because the failure it catches is the same\n * one: a VFS that cannot acquire what it needs — `AccessHandlePoolVFS` whose\n * six slots are held elsewhere reaches neither success nor error. Not a public\n * option: a caller has nothing useful to tune here, and a delete that takes\n * thirty seconds has already failed.\n */\nconst DELETE_TIMEOUT = 30_000;\n\nconst runDelete = (message: {\n file: string;\n vfs: SQLiteVFS;\n build: SQLiteBuild;\n wasm: ReturnType<typeof resolveWasmLocation>;\n}): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n const worker = spawnWorker(`SQLite delete / ${message.file}`);\n\n const timer = setTimeout(() => {\n settle(\n new SQLiteError(\n 'TIMEOUT',\n `deleting ${message.file} timed out after ${DELETE_TIMEOUT} ms. The database is most likely held open by another client or tab.`,\n ),\n );\n }, DELETE_TIMEOUT);\n\n const settle = (error?: SQLiteError) => {\n clearTimeout(timer);\n worker.terminate();\n if (error) reject(error);\n else resolve();\n };\n\n worker.onmessage = (event: MessageEvent<WorkerMessageData>) => {\n const data = event.data;\n if (data.type === 'deleted') return settle();\n if (data.type === 'error') {\n return settle(\n busyFromCode(data) ??\n new SQLiteError('WORKER_CRASHED', data.message, {\n cause: data.cause,\n }),\n );\n }\n };\n\n worker.onerror = (event) => {\n settle(\n new SQLiteError(\n 'WORKER_CRASHED',\n `worker crashed while deleting ${message.file}: ${(event as ErrorEvent).message ?? ''}`,\n ),\n );\n };\n\n worker.postMessage({ type: 'delete', callId: 0, ...message });\n });\n"],"names":["BUILD_REQUIREMENTS","VFS_CAPABILITIES","defaultBuildFor","vfs","RECOMMENDED_VFS","PROBES","navigator","FileSystemFileHandle","WebAssembly","UNPROBEABLE","Set","FEATURE_LABEL","Object","detectFeatures","found","feature","probe","missingFeature","build","available","SQLiteError","Error","code","message","options","undefined","SQLiteBulkWriteError","counts","stagingLockName","file","table","noOpLocks","_name","fn","createLocks","manager","globalThis","name","Promise","resolveReleaser","rejectOuter","release","held","resolveHeld","ran","lock","snapshot","WRITE_KEYWORDS","READ_PRAGMA","isReadQuery","sql","assertReadable","method","keyword","quoteIdent","JSON","COLUMN_TYPE","PRAGMA_NAME","PRAGMA_INTEGER","PRAGMA_LITERAL","normalizeDatabaseFile","URL","resolveWasmLocation","wasmUrl","baseHref","href","isCallback","raw","value","ADMITTED","REGISTRY_KEY","Symbol","STOP","BUSY_CODES","busyFromCode","data","spawnWorker","Worker","makeAbortRace","signal","onAbort","aborted","_","reject","chunk","worker","params","chunkSize","credits","teardown","iterator","next","streamRows","rows","row","readWorker","result","firstWorker","writeWorker","affected","exec","clientCount","createSQLiteClient","clientOptions","startupFirstError","closing","fatal","onFirstSettle","onGateOpen","map","existing","cell","deps","dbFile","clientIndex","clientPrefix","poolSize","pool","capability","wasm","location","absent","describeMissing","label","others","b","suffix","alternatives","key","String","testWriterPolicy","writerPolicy","inStartup","startupLosses","Map","scheduler","createScheduler","opts","shutdownReason","shutdownDeferred","workers","dead","leased","generations","gen","index","settledSlots","gateOpen","gateDeferred","firstSettleOpened","firstSettleFired","gatedWaiters","settleGateSlot","kind","failedIndices","i","readerQueue","writerQueue","currentWriterIndex","lastWriterIndex","canDesignate","serveWriterFirst","checkShutdown","makeLease","myGen","released","handOver","reason","waiter","abortP","abortReject","onGateAbort","write","immediate","takeAvailable","preferred","promise","resolve","queue","at","error","emitWorkerLost","failClient","spawn","failClientError","verdict","supervisor","Boolean","debugOption","logger","createLogger","prefix","enabled","sink","console","line","always","clientDebug","createClientDebug","stats","pragmas","clientState","createWorkerDebugState","state","Proxy","Date","target","prop","createRequestDebugState","createQueryDebugState","workerIndex","debug","epochs","registry","host","created","applyBarrier","barrierIter","afterWrite","seen","acquireWithDebug","request","lease","acquireInstrumented","barrier","read","chunkWorker","stream","first","bulkFor","createBulk","shared","swept","locks","maxVariables","transaction","bulkWrite","keys","before","failure","room","maxBufferSize","Math","queueSize","buffer","writePromise","closed","rowsWritten","rowsNotWritten","queuedRows","releaseRoom","fail","flush","toInsert","runBatch","currentAffected","k","failClosed","r","output","schema","uuid","staging","crypto","normalizedSchema","v","type","assertColumnType","column","trimmed","unique","notnull","generated","assertGeneratedExpression","expr","lockHeld","createStaging","sweepOnce","heldNames","tables","orphan","enqueue","close","col","releaseLock","dropStaging","setTimeout","tx","statement","indexStatements","statements","columns","Array","names","handleDeath","callback","readOnly","autoCommit","checksql","done","begun","withSignal","given","merged","mergeSignals","a","noop","AbortController","relay","source","onA","onB","releasing","refuse","bulk","query","db","running","e","bounded","ms","timer","clearTimeout","openTimeout","drainTimeout","createSupervisor","size","maxWorkerRestarts","slots","liveCount","slot","event","dying","createPoolWorker","deferredChunk","deferredClose","idle","stopRequested","lost","statementCacheSize","deferredInit","workerName","currentCallId","suppressServed","ready","deathDeferred","die","detail","errorEvent","failedUrl","callId","queryState","DEFAULT_CREDIT_WINDOW","noServed","expiry","served","live","cb","cbError","wasInStartup","decision","draining","deleteDatabase","runDelete","settle"],"mappings":"AAwHO,IAAMA,EAAqB,CAChC,KAAM,EAAE,CACR,MAAO,EAAE,CACT,KAAM,CAAC,OAAO,AAChB,EA6FaC,EAAmB,CAC9B,gBAAiB,CACf,OAAQ,CAAC,QAAS,OAAO,CACzB,YAAa,KACb,gBAAiB,KACjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,aACb,QAAS,OACT,OAAQ,YACR,SAAU,CAAC,OAAO,CAClB,gBAAiB,CAAC,mBAAmB,AACvC,EACA,kBAAmB,CACjB,OAAQ,CAAC,OAAQ,QAAS,OAAO,CACjC,YAAa,KACb,gBAAiB,KACjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,aACb,QAAS,OACT,OAAQ,YAMR,SAAU,CAAC,OAAO,CAClB,gBAAiB,CAAC,mBAAmB,AACvC,EACA,gBAAiB,CACf,OAAQ,CAAC,OAAQ,QAAS,OAAO,CACjC,YAAa,KACb,gBAAiB,KACjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,aACb,QAAS,OACT,OAAQ,YACR,SAAU,CAAC,OAAO,CAClB,gBAAiB,EAAE,AACrB,EACA,oBAAqB,CACnB,OAAQ,CAAC,OAAQ,QAAS,OAAO,CACjC,YAAa,EACb,gBAAiB,qDACjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,aACb,QAAS,OACT,OAAQ,YACR,SAAU,CAAC,OAAO,CAClB,gBAAiB,EAAE,AACrB,EACA,kBAAmB,CACjB,OAAQ,CAAC,QAAS,OAAO,CACzB,YAAa,KACb,gBAAiB,KACjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,aACb,QAAS,YACT,OAAQ,YACR,SAAU,EAAE,CACZ,gBAAiB,EAAE,AACrB,EACA,aAAc,CACZ,OAAQ,CAAC,QAAS,OAAO,CAczB,YAAa,EACb,gBACE,8HACF,gBAAiB,GACjB,WAAY,GAGZ,YAAa,iBACb,QAAS,YACT,OAAQ,YACR,SAAU,EAAE,CACZ,gBAAiB,EAAE,AACrB,EACA,kBAAmB,CACjB,OAAQ,CAAC,QAAS,OAAO,CACzB,YAAa,KACb,gBAAiB,KACjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,aACb,QAAS,OACT,OAAQ,YACR,SAAU,CAAC,OAAQ,kBAAkB,CACrC,gBAAiB,EAAE,AACrB,EACA,UAAW,CACT,OAAQ,CAAC,OAAQ,QAAS,OAAO,CACjC,YAAa,EACb,gBACE,yHACF,gBAAiB,GACjB,WAAY,GACZ,YAAa,iBACb,QAAS,SACT,OAAQ,SACR,SAAU,EAAE,CACZ,gBAAiB,EAAE,AACrB,EACA,eAAgB,CACd,OAAQ,CAAC,QAAS,OAAO,CACzB,YAAa,EACb,gBACE,yHACF,gBAAiB,GACjB,WAAY,GACZ,YAAa,iBACb,QAAS,SACT,OAAQ,SACR,SAAU,EAAE,CACZ,gBAAiB,EAAE,AACrB,CACF,EAKaC,EAAkB,AAACC,GAC9BF,CAAgB,CAACE,EAAI,CAAC,MAAM,CAAC,EAAE,CAYpBC,EAA6B,kBChWpCC,EAA0D,CAC9D,KAAM,IACJ,AAAqB,IAArB,OAAOC,WACP,AAA2C,YAA3C,OAAOA,UAAU,OAAO,EAAE,cAC1B,AAAgC,IAAhC,OAAOC,qBACT,KAAM,IACJ,AAAgE,YAAhE,OAAQC,YAAyC,UAAU,CAC7D,kBAAmB,IACjB,AAAgC,IAAhC,OAAOD,sBACP,AAAyD,YAAzD,OAAOA,qBAAqB,SAAS,CAAC,cAAc,AACxD,EAWME,EAAc,IAAIC,IAAqB,CAAC,mBAAmB,EAG3DC,EAAiD,CACrD,KAAM,OACN,KAAM,OACN,kBAAmB,+BACnB,mBAAoB,iCACtB,CAO4D,KACtDC,OAAO,IAAI,CAACP,MACbI,EACH,CAGK,IAAMI,EAAiB,KAC5B,IAAMC,EAAQ,IAAIJ,IAClB,IAAK,GAAM,CAACK,EAASC,EAAM,GAAIJ,OAAO,OAAO,CAACP,GACxCW,KAASF,EAAM,GAAG,CAACC,GAEzB,OAAOD,CACT,EASaG,EAAiB,CAC5Bd,EACAe,EACAC,KAMA,IAAK,IAAMJ,IAJkC,IACxCd,CAAgB,CAACE,EAAI,CAAC,QAAQ,IAC9BH,CAAkB,CAACkB,EAAM,CAC7B,CAEC,IAAIT,EAAY,GAAG,CAACM,IAChB,CAACI,EAAU,GAAG,CAACJ,GAAU,OAAOA,EAEtC,OAAO,IACT,CCpEO,OAAMK,UAAoBC,MACtB,IAAsB,AAMtB,WAAoB,AAE7B,aACEC,CAAqB,CACrBC,CAAe,CACfC,CAAkD,CAClD,CACA,KAAK,CAACD,EAASC,GACf,IAAI,CAAC,IAAI,CAAGF,EACZ,IAAI,CAAC,IAAI,CAAGA,EACRE,GAAS,aAAeC,QAAW,KAAI,CAAC,UAAU,CAAGD,EAAQ,UAAU,AAAD,CAC5E,CACF,CAUO,MAAME,UAA6BN,EAC/B,WAAoB,AACpB,eAAuB,AAEhC,aACEG,CAAe,CACfI,CAAuD,CACvDH,CAA6B,CAC7B,CACA,KAAK,CAAC,oBAAqBD,EAASC,GACpC,IAAI,CAAC,WAAW,CAAGG,EAAO,WAAW,CACrC,IAAI,CAAC,cAAc,CAAGA,EAAO,cAAc,AAC7C,CACF,CCVO,IAAMC,EAAkB,CAACC,EAAcC,IAC5C,CAAC,YAAY,EAAED,EAAK,CAAC,EAAEC,EAAM,CAAC,CAqBnBC,EAAmB,CAC9B,UAAW,GACX,KAAM,SAAY,KAAO,EACzB,SAAU,MAAOC,EAAOC,IAAOA,IAC/B,YAAa,MAAOD,EAAOC,KACzB,MAAMA,IACC,IAET,UAAW,SAAY,EAAE,AAC3B,EAEaC,EAAc,CACzBC,EAAmCC,WAAW,SAAS,EAAE,KAE5C,GAEb,AAAKD,EAEE,CACL,UAAW,GACX,KAAM,AAACE,GACL,IAAIC,QAAoB,CAACC,EAAiBC,KAExC,IADIC,EACEC,EAAO,IAAIJ,QAAc,AAACK,IAC9BF,EAAUE,CACZ,GACAR,EACG,OAAO,CAACE,EAAM,KACbE,EAAgBE,GACTC,IAER,KAAK,CAACF,EACX,GACF,SAAU,CAAIH,EAAcJ,IAC1BE,EAAQ,OAAO,CAACE,EAAM,CAAE,KAAM,WAAY,EAAG,IAAMJ,KACrD,YAAa,MAAOI,EAAMJ,KACxB,IAAIW,EAAM,GAWV,OAVA,MAAMT,EAAQ,OAAO,CACnBE,EACA,CAAE,KAAM,YAAa,YAAa,EAAK,EACvC,MAAOQ,IAEAA,IACLD,EAAM,GACN,MAAMX,IACR,GAEKW,CACT,EACA,UAAW,SAEDE,AAAAA,CADS,OAAMX,EAAQ,KAAK,EAAC,EACpB,IAAI,EAAI,EAAC,EACvB,GAAG,CAAC,AAACU,GAASA,EAAK,IAAI,EACvB,MAAM,CAAC,AAACR,GAAyB,AAAgB,UAAhB,OAAOA,EAE/C,EAvCqBN,ECpDjBgB,EACJ,4IA2BIC,EAAc,qCAEPC,EAAc,AAACC,GAC1BF,EAAY,IAAI,CAACE,IAChB,sCAAsC,IAAI,CAACA,IAC1C,CAACH,EAAe,IAAI,CAACG,GAkDZC,EAAiB,CAACD,EAAaE,KAC1C,GAAIH,EAAYC,GAAM,OACtB,IAAMG,EAAUH,EAAI,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,eAAiB,EAC7D,OAAM,IAAI9B,EACR,mBACA,CAAC,EAAEgC,EAAO,qDAAqD,EAAEC,EAAQ,mGAA2B,CAAC,CAGzG,EAaaC,EAAa,AAACjB,IACzB,GAAI,CAACA,EACH,MAAM,IAAIjB,EAAY,qBAAsB,8BAC9C,GAAIiB,EAAK,QAAQ,CAAC,MAChB,MAAM,IAAIjB,EACR,qBACA,CAAC,qCAAqC,EAAEmC,KAAK,SAAS,CAAClB,GAAM,CAAC,EAElE,MAAO,CAAC,CAAC,EAAEA,EAAK,OAAO,CAAC,KAAM,MAAM,CAAC,CAAC,AACxC,EAGMmB,EAAc,yCAwCdC,EAAc,iBACdC,EAAiB,UACjBC,EAAiB,iBAoDVC,EAAwB,AAAC/B,GACpC,IAAIgC,IAAIhC,EAAM,WAAW,QAAQ,CAAC,OAAO,CAAC,MAAO,IA4BtCiC,EAAsB,CACjCC,EACA7C,EACA8C,SAQIC,EANJ,GAAIF,AAAYtC,SAAZsC,EAAuB,OAE3B,IAAMG,EAAa,AAAmB,YAAnB,OAAOH,EACpBI,EAAMD,EAAaH,EAAQ7C,GAAS6C,EACpCK,EAAQF,GAAcC,EAAI,QAAQ,CAAC,KAAOA,EAAM,CAAC,EAAEA,EAAI,CAAC,CAAC,CAG/D,GAAI,CACFF,EAAO,IAAIJ,IAAIO,EAAOJ,GAAU,IAAI,AACtC,CAAE,KAAM,CACN,MAAM,IAAI5C,EACR,iBACA,CAAC,sCAAsC,EAAEmC,KAAK,SAAS,CAACY,GAAK,+GAA+G,CAAC,CAEjL,CAEA,OAAOD,EAAa,CAAE,KAAMD,CAAK,EAAI,CAAE,KAAMA,CAAK,CACpD,ECvOMI,EAAW/B,QAAQ,OAAO,GCvC1BgC,EAAeC,OAAO,GAAG,CAAC,4BCsC1BC,EAAOD,OAAO,QAGdE,EAAa,IAAI/D,IAAI,CAAC,EAAG,EAAE,EAOpBgE,EAAe,AAACC,GAK3BA,AAAoBlD,SAApBkD,EAAK,UAAU,EAAkBF,EAAW,GAAG,CAACE,EAAK,UAAU,EAC3D,IAAIvD,EAAY,OAAQuD,EAAK,OAAO,CAAE,CACpC,MAAOA,EAAK,KAAK,CACjB,WAAYA,EAAK,UAAU,AAC7B,GACAlD,OAqBOmD,EAAc,AAACvC,GAC1B,IAAIwC,OACuC,IAAIhB,IAC3C,qBACA,YAAY,GAAG,EAEjB,CAAExB,KAAAA,EAAM,KAAM,QAAS,GCpGdyC,EAAgB,AAC3BC,QAGIC,EADJ,GAAI,CAACD,EAAQ,MAAO,CAAE,QAAStD,OAAW,SAAU,KAAO,CAAE,EAE7D,IAAMwD,EAAU,IAAI3C,QAAe,CAAC4C,EAAGC,KACrCH,EAAU,IAAMG,EAAOJ,EAAO,MAAM,EACpCA,EAAO,gBAAgB,CAAC,QAASC,EAAS,CAAE,KAAM,EAAK,EACzD,GAGA,OADAC,EAAQ,KAAK,CAAC,KAAO,GACd,CACLA,QAAAA,EACA,SAAU,KACJD,GAASD,EAAO,mBAAmB,CAAC,QAASC,EACnD,CACF,CACF,EAMaI,EAAQ,gBAGnBC,CAAkB,CAClBnC,CAAW,CACXoC,CAAkB,CAClB9D,CAAmD,EAEnD,GAAM,CAAEuD,OAAAA,CAAM,CAAEQ,UAAAA,CAAS,CAAEC,QAAAA,CAAO,CAAE,CAAGhE,GAAW,CAAC,EAGnD,GAAIuD,GAAQ,QAAS,MAAMA,EAAO,MAAM,CAExC,GAAM,CAAEE,QAAAA,CAAO,CAAEQ,SAAAA,CAAQ,CAAE,CAAGX,EAAcC,GACtCW,EAAWL,EAAO,KAAK,CAAInC,EAAKoC,EAAQ,CAAEC,UAAAA,EAAWC,QAAAA,CAAQ,GACnE,GAAI,CACF,OAAa,CAIX,IAAMG,EAAOV,EACT,MAAM3C,QAAQ,IAAI,CAAC,CAACoD,EAAS,IAAI,GAAIT,EAAQ,EAC7C,MAAMS,EAAS,IAAI,GACvB,GAAIC,EAAK,IAAI,CAAE,KAEX,AAAsB,WAAtB,OAAOA,EAAK,KAAK,EAAe,OAAMA,EAAK,KAAK,AAAD,CACrD,CACF,QAAU,CACRF,IAKAJ,EAAO,SAAS,GACXK,EAAS,MAAM,CAACjE,QAAW,KAAK,CAAC,KAAO,EAC/C,CACF,EAEamE,EAAa,gBAGxBP,CAAkB,CAClBnC,CAAW,CACXoC,CAAkB,CAClB9D,CAA4B,EAE5B,UAAW,IAAMqE,KAAQT,EAASC,EAAQnC,EAAKoC,EAAQ9D,GACrD,IAAK,IAAMsE,KAAOD,EAAM,MAAMC,CAElC,EAEaC,EAAa,MAGxBV,EACAnC,EACAoC,EACA9D,KAEA,IAAMwE,EAAc,EAAE,CACtB,UAAW,IAAMH,KAAQT,EAASC,EAAQnC,EAAKoC,EAAQ9D,GACrDwE,EAAO,IAAI,IAAIH,GAEjB,OAAOG,CACT,EASaC,EAAc,MAGzBZ,EACAnC,EACAoC,EACA9D,KAEA,UAAW,IAAMqE,KAAQT,EAASC,EAAQnC,EAAKoC,EAAQ,CACrD,GAAG9D,CAAO,CACV,UAAW,EAIX,QAAS,CACX,GACE,OAAOqE,CAAI,CAAC,EAAE,AAGlB,EAEaK,EAAc,MAGzBb,EACAnC,EACAoC,EACA9D,KAEA,GAAM,CAAEuD,OAAAA,CAAM,CAAE,CAAGvD,GAAW,CAAC,EAG/B,GAAIuD,GAAQ,QAAS,MAAMA,EAAO,MAAM,CAExC,GAAM,CAAEE,QAAAA,CAAO,CAAEQ,SAAAA,CAAQ,CAAE,CAAGX,EAAcC,GACtCW,EAAWL,EAAO,KAAK,CAAInC,EAAKoC,EAAQ,CAAC,GACzCU,EAAc,EAAE,CAClBG,EAAW,EACf,GAAI,CACF,OAAa,CAIX,IAAMR,EAAOV,EACT,MAAM3C,QAAQ,IAAI,CAAC,CAACoD,EAAS,IAAI,GAAIT,EAAQ,EAC7C,MAAMS,EAAS,IAAI,GACvB,GAAIC,EAAK,IAAI,CAAE,KAGX,AAAsB,WAAtB,OAAOA,EAAK,KAAK,CAAeQ,EAAWR,EAAK,KAAK,CACpDK,EAAO,IAAI,IAAIL,EAAK,KAAK,CAChC,CACF,QAAU,CACRF,IAEAJ,EAAO,SAAS,GACXK,EAAS,MAAM,CAACjE,QAAW,KAAK,CAAC,KAAO,EAC/C,CACA,MAAO,CAAEuE,OAAAA,EAAQG,SAAAA,CAAS,CAC5B,EChJMC,EAAO,MAAOf,EAAoBnC,KACtC,MAAM6C,EAAWV,EAAQnC,EAC3B,ECgLImD,EAAc,EA+CLC,EAAqB,CAChCzE,EACA0E,KAIA,IAmFIC,EAoaAC,EAuCAC,EA3bMC,EA8CAC,EJxVJC,EACAC,EACAC,EGrBLC,EC0NKC,EAASrD,EAAsB/B,GAWrC,GAAI,CAAC0E,GAAe,IAClB,MAAM,IAAInF,EACR,iBACA,CAAC,iBAAiB,EAAEhB,EAAgB,kQAAkQ,CAAC,EAI3S,IAAM8G,EAAc,EAAEb,EAEhBc,EAAe,CAAC,EAAEZ,EAAc,IAAI,EAAI,SAAS,CAAC,EAAEW,EAAY,CAAC,CAEjEE,EAAWb,EAAc,QAAQ,EAhOf,EAiOlBc,EAAmC,EAAE,CAErClH,EAAMoG,EAAc,GAAG,CACvBrF,EAAQqF,EAAc,KAAK,EAAIrG,EAAgBC,GAE/CmH,EAAarH,CAAgB,CAACE,EAAI,CAKxC,GAAI,CAAEmH,EAAW,MAAM,CAA4B,QAAQ,CAACpG,GAC1D,MAAM,IAAIE,EACR,iBACA,CAAC,EAAEjB,EAAI,oBAAoB,EAAEe,EAAM,oBAAoB,EAAEoG,EAAW,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAO5F,IAAMC,EAAOzD,EAAoByC,EAAc,OAAO,CAAErF,EAAOsG,SAAS,IAAI,EAE5E,GAAIF,AAA2B,OAA3BA,EAAW,WAAW,EAAaF,EAAWE,EAAW,WAAW,CACtE,MAAM,IAAIlG,EACR,iBACA,CAAC,EAAEjB,EAAI,0CAA0C,EAAEmH,EAAW,WAAW,CAAC,EAAE,EAAEA,EAAW,eAAe,CAAC,gBAAgB,EAAEA,EAAW,WAAW,CAAC,CAAC,CAAC,EAMxJ,IAAMG,EAASxG,EAAed,EAAKe,EAAOL,KAC1C,GAAI4G,EACF,MAAM,IAAIrG,EACR,iBACAsG,AT3NyB,EAC7BvH,EACAe,EACAH,KAEA,IAAM4G,EAAQhH,CAAa,CAACI,EAAQ,CAEpC,GACGf,CAAkB,CAACkB,EAAM,CAAgC,QAAQ,CAACH,GACnE,CACA,IAAM6G,EAAS3H,CAAgB,CAACE,EAAI,CAAC,MAAM,CAAC,MAAM,CAAC,AAAC0H,GAAMA,IAAM3G,GAC1D4G,EAASF,EAAO,MAAM,CACxB,CAAC,CAAC,EAAEzH,EAAI,eAAe,EAAEyH,EAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAC7C,GACJ,MAAO,CAAC,8BAA8B,EAAED,EAAM,aAAa,EAAEzG,EAAM,iBAAiB,EAAE4G,EAAO,CAAC,AAChG,CAEA,IAAMC,EAAgBnH,OAAO,IAAI,CAACX,GAAkC,MAAM,CACxE,AAACoC,GACC,CAAEpC,CAAgB,CAACoC,EAAK,CAAC,QAAQ,CAAgC,QAAQ,CACvEtB,IAGA+G,EAASC,EAAa,MAAM,CAC9B,CAAC,oCAAoC,EAAEA,EAAa,IAAI,CAAC,MAAM,CAAC,CAAC,CACjE,GACJ,MAAO,CAAC,8BAA8B,EAAEJ,EAAM,QAAQ,EAAExH,EAAI,UAAU,EAAE2H,EAAO,CAAC,AAClF,GSgMsB3H,EAAKe,EAAOuG,GAK5BlB,CAAAA,EAAc,OAAO,EN3GzB3F,OAAO,OAAO,CM2G2B2F,EAAc,OAAO,EN3GtC,GAAG,CAAC,CAAC,CAACyB,EAAK5D,EAAM,IACvC,GAAI,CAACX,EAAY,IAAI,CAACuE,GACpB,MAAM,IAAI5G,EACR,iBACA,CAAC,oBAAoB,EAAEmC,KAAK,SAAS,CAACyE,GAAK,2BAA2B,EAAEvE,EAAY,CAAC,CAAC,EAE1F,IAAMU,EAAM8D,OAAO7D,GAAO,IAAI,GAC9B,GAAIV,EAAe,IAAI,CAACS,IAAQV,EAAY,IAAI,CAACU,IAK7CR,EAAe,IAAI,CAACQ,GAJtB,MAAO,CAAC,OAAO,EAAE6D,EAAI,CAAC,EAAE7D,EAAI,CAAC,AAK/B,OAAM,IAAI/C,EACR,iBACA,CAAC,cAAc,EAAEmC,KAAK,SAAS,CAACa,GAAO,aAAa,EAAE4D,EAAI,qEAAqE,CAAC,CAEpI,GM+FA,IAAME,GAAoB3B,EACvB,wBAAwB,CACrB4B,GACJ,AAA4B,YAA5B,OAAOD,GAAkCA,GAAmBzG,OAW1D2G,GAAY,GAKVC,GAAgB,IAAIC,IAMpBC,GAAYC,ACjPW,EAC7BC,EA4BI,CAAC,CAAC,IAEN,IAyBIC,EACAC,EA1BEC,EAA6B,EAAE,CAgB/BzH,EAAY,IAAIT,IAEhBmI,EAAO,IAAInI,IACXoI,EAAS,IAAIpI,IAGbqI,EAAc,IAAIT,IAClBU,EAAM,AAACC,GAAkBF,EAAY,GAAG,CAACE,IAAU,EAYnDC,EAAe,IAAIxI,IACrByI,EAAYV,AAAAA,CAAAA,EAAK,QAAQ,EAAI,KAAO,EAClCW,EAAe9G,QAAQ,aAAa,EACtC6G,CAAAA,GAAUC,EAAa,OAAO,GAG7BA,EAAa,OAAO,CAAC,KAAK,CAAC,KAAO,GAIvC,IAAMC,EAAoB,IAAI3I,IAC1B4I,EAAmB,GAGnBC,EAAe,EAEbC,EAAiB,CAACP,EAAeQ,KACrC,IAAIN,CAAAA,GAAYD,EAAa,GAAG,CAACD,EAAK,IACtCC,EAAa,GAAG,CAACD,GACbQ,AAAS,WAATA,GAAmBJ,EAAkB,GAAG,CAACJ,IACzCC,CAAAA,EAAa,IAAI,CAAIT,CAAAA,EAAK,QAAQ,EAAI,EAAC,IAG3C,GAAIA,EAAK,aAAa,EAAI,CAACa,EAAkB,CAC3CA,EAAmB,GACnB,IAAMI,EAAgB,IAAIR,EAAa,CAAC,MAAM,CAC5C,AAACS,GAAM,CAACN,EAAkB,GAAG,CAACM,IAWhC,GATAlB,EAAK,aAAa,CAAC,CACjB,YAAaY,EAAkB,IAAI,CACnCK,cAAAA,CACF,GAMIR,EAAa,IAAI,CAAIT,CAAAA,EAAK,QAAQ,EAAI,IAAMC,EAAgB,MAClE,CAEAS,EAAW,GACXC,EAAa,OAAO,GACpBX,EAAK,UAAU,KACjB,EAEMmB,EAGD,EAAE,CACDC,EAGD,EAAE,CAcHC,EAAqB,GAOrBC,EAAkB,GAEhBC,EAAevB,EAAK,kBAAkB,EAAM,KAAK,EAAG,EAOpDwB,EAAmB,AAAC5E,GACxB,CAAI,CAACwE,EAAY,MAAM,EACnBC,CAAAA,IAAuBzE,EAAO,KAAK,EAAIyE,AAAuB,KAAvBA,CAAwB,GAG/DA,CAAAA,AAAuB,KAAvBA,IAA6B,CAACE,EAAa3E,EAAO,KAAK,KAG3DyE,EAAqBzE,EAAO,KAAK,CACjC0E,EAAkB1E,EAAO,KAAK,CAC9BwE,EAAY,KAAK,IAAI,QAAQxE,GACtB,IAGH6E,EAAgB,KAChBvB,GAAoBG,AAAgB,IAAhBA,EAAO,IAAI,EAAQH,EAAiB,OAAO,EACrE,EA2BMwB,EAAY,AAAC9E,IACjByD,EAAO,GAAG,CAACzD,EAAO,KAAK,EACvB,IAAM+E,EAAQpB,EAAI3D,EAAO,KAAK,EAC1BgF,EAAW,GACf,MAAO,CACLhF,OAAAA,EACA,QAAS,KACP,IAAIgF,GAEJ,GADAA,EAAW,GACPrB,EAAI3D,EAAO,KAAK,IAAM+E,EAAO,YAI/BF,IAGFpB,EAAO,MAAM,CAACzD,EAAO,KAAK,EAC1BiF,AA1CW,CAACjF,IAChB,IAAI4E,EAAiB5E,IAcrB,GAFIyE,IAAuBzE,EAAO,KAAK,EAAEyE,CAAAA,EAAqB,EAAC,EAE3DF,EAAY,MAAM,CAAE,OAEtBA,EAAY,KAAK,IAAI,QAAQvE,GAI/BlE,EAAU,GAAG,CAACkE,EAAO,KAAK,EAC1BoD,EAAK,MAAM,GAAGpD,GAChB,GAmBeA,GACT6E,IACF,CACF,CACF,EAiDA,MAAO,CACL,IAAK,AAAC7E,IAUJ,GAPAmE,EAAenE,EAAO,KAAK,CAAE,UAE7BwD,EAAK,MAAM,CAACxD,EAAO,KAAK,EACxBuD,CAAO,CAACvD,EAAO,KAAK,CAAC,CAAGA,GAIpB4E,EAAiB5E,IACrB,GAAIuE,EAAY,MAAM,CAAE,YAEtBA,EAAY,KAAK,IAAI,QAAQvE,GAG/BlE,EAAU,GAAG,CAACkE,EAAO,KAAK,EAC5B,EAEA,OAAQ,AAAC4D,IAGPO,EAAeP,EAAO,UAEtBJ,EAAK,GAAG,CAACI,GACT9H,EAAU,MAAM,CAAC8H,GACjBH,EAAO,MAAM,CAACG,GACdL,CAAO,CAACK,EAAM,CAAGxH,OAGjBsH,EAAY,GAAG,CAACE,EAAOD,EAAIC,GAAS,GAChCa,IAAuBb,GAAOa,CAAAA,EAAqB,EAAC,EAGpDC,IAAoBd,GAAOc,CAAAA,EAAkB,EAAC,EAClDG,GACF,EAEA,SAAU,AAACK,IAQT,IAAK,IAAMC,KANNrB,IACHA,EAAW,GACXC,EAAa,MAAM,CAACmB,IAEtB7B,IAAmB6B,EACnB5B,IAAqBrG,QAAQ,aAAa,GACrBsH,EAAY,MAAM,CAAC,IAAIY,EAAO,MAAM,CAACD,GAC1D,IAAK,IAAMC,KAAUX,EAAY,MAAM,CAAC,GAAIW,EAAO,MAAM,CAACD,GAE1D,OADAL,IACOvB,EAAiB,OAAO,AACjC,EAEA,MAAO,IAAO,EACZ,KAAMiB,EAAY,MAAM,CACxB,MAAOC,EAAY,MAAM,CACzB,UAAW1I,EAAU,IAAI,CACzB,OAAQ2H,EAAO,IAAI,CACnB,MAAOS,CACT,GAEA,UAAW,AAACN,IACN,AAACE,GAAUD,EAAa,MAAM,CAACD,EACrC,EAEA,QAAS,MAAOQ,EAAM1E,KACpB,GAAI2D,EAAgB,MAAMA,EAQ1B,GALA3D,GAAQ,iBAKJ,CAACoE,EAAU,CAGbI,GAAgB,EAChB,GAAI,CAOF,GAAIxE,EAAQ,CACV,GAAM,CAAE,QAAS0F,CAAM,CAAE,OAAQC,CAAW,CAAE,CAC5CpI,QAAQ,aAAa,GACjBqI,EAAc,IAAMD,EAAY3F,EAAO,MAAM,EACnDA,EAAO,gBAAgB,CAAC,QAAS4F,EAAa,CAAE,KAAM,EAAK,GAC3D,GAAI,CACF,MAAMrI,QAAQ,IAAI,CAAC,CAAC8G,EAAa,OAAO,CAAEqB,EAAO,CACnD,QAAU,CACR1F,EAAO,mBAAmB,CAAC,QAAS4F,EACtC,CACF,MACE,MAAMvB,EAAa,OAAO,AAE9B,QAAU,CACRG,GAAgB,CAClB,CACF,CAKA,GAAIb,EAAgB,MAAMA,EAE1B,IAAMkC,EAAQnB,AAAS,UAATA,EAERoB,EAAYC,AA7JA,CAACF,IACrB,GAAIA,GAASd,EAAqB,GAAI,CACpC,GAAI,CAAC3I,EAAU,GAAG,CAAC2I,GAAqB,OAExC,OADA3I,EAAU,MAAM,CAAC2I,GACVlB,CAAO,CAACkB,EAAmB,AACpC,CAaA,IAAMiB,EAAYnC,CAAO,CAACmB,EAAgB,CAC1C,GACEgB,AAActJ,SAAdsJ,GACA5J,EAAU,GAAG,CAAC4I,IACb,EAACa,GAASZ,EAAaD,EAAe,EAIvC,OAFA5I,EAAU,MAAM,CAAC4I,GACba,GAAOd,CAAAA,EAAqBC,CAAc,EACvCgB,EAKT,IAAMjK,EAAQ8H,EAAQ,IAAI,CACxB,AAACvD,GACCA,AAAW5D,SAAX4D,GACAlE,EAAU,GAAG,CAACkE,EAAO,KAAK,GACzB,EAACuF,GAASZ,EAAa3E,EAAO,KAAK,IAExC,GAAKvE,EAOL,OALAK,EAAU,MAAM,CAACL,EAAM,KAAK,EACxB8J,IACFd,EAAqBhJ,EAAM,KAAK,CAChCiJ,EAAkBjJ,EAAM,KAAK,EAExBA,CACT,GAgHoC8J,GAChC,GAAIC,EAAW,OAAOV,EAAUU,GAEhC,GAAM,CAAEG,QAAAA,CAAO,CAAEC,QAAAA,CAAO,CAAE9F,OAAAA,CAAM,CAAE,CAAG7C,QAAQ,aAAa,GACpD4I,EAAQN,EAAQf,EAAcD,EAC9BY,EAAS,CAAES,QAAAA,EAAS9F,OAAAA,CAAO,EAGjC,GAFA+F,EAAM,IAAI,CAACV,GAEP,CAACzF,EAAQ,OAAOoF,EAAU,MAAMa,GAEpC,IAAMhG,EAAU,KACd,IAAMmG,EAAKD,EAAM,OAAO,CAACV,EAgBH,MAAPW,IAEfD,EAAM,MAAM,CAACC,EAAI,GACjBhG,EAAOJ,EAAO,MAAM,EACtB,EACAA,EAAO,gBAAgB,CAAC,QAASC,EAAS,CAAE,KAAM,EAAK,GACvD,GAAI,CACF,OAAOmF,EAAU,MAAMa,EACzB,QAAU,CACRjG,EAAO,mBAAmB,CAAC,QAASC,EACtC,CACF,CACF,CACF,IDpKY2B,EAAgB,AAACX,IAIrB,GAAIA,AAAuB,IAAvBA,EAAO,WAAW,CAAQ,CAe5B,IAAK,GAAM,CAACiD,EAAOmC,EAAM,GADzBhD,GAAY,GACiBC,IAC3BgD,GAAepC,EAAOmC,GAExB/C,GAAc,KAAK,GACnBiD,GACE9E,GACE,IAAIpF,EACF,iBACA,6CAGN,MACF,CAOA,IAAK,IAAM6H,KAASjD,EAAO,aAAa,CACtCuC,GAAU,SAAS,CAACU,GACpBsC,GAAMtC,EAIV,EAEMrC,EAAa,SAIb4E,EACJ,IAAK,GAAM,CAACvC,EAAOmC,EAAM,GAJzBhD,GAAY,GAIiBC,IAQvBoD,AAAY,gBAFAC,GAAW,MAAM,CAACzC,EAAO,SAEVuC,CAAAA,IAAoBJ,CAAI,EAIzD,IAAK,GAAM,CAACnC,EAAOmC,EAAM,GAAI/C,GAC3BgD,GAAepC,EAAOmC,GAExB/C,GAAc,KAAK,GAOjBmD,CAAAA,AAAoB/J,SAApB+J,GACAnE,AAAgC,IAAhCA,EAAK,MAAM,CAACsE,SAAS,MAAM,AAAK,GAEhCL,GACEE,GACEhF,GACA,IAAIpF,EACF,iBACA,4CAIV,EAEO+G,GACH,CACE,mBAAoBA,GACpBf,SAAAA,EACAT,cAAAA,EACAC,WAAAA,CACF,EACA,CAAEQ,SAAAA,EAAUT,cAAAA,EAAeC,WAAAA,CAAW,IAIxCgF,GAAcrF,EAAc,KAAK,CAKjCsF,GAASC,AEhbW,EAC1BC,EACAC,EACAC,EAAaC,OAAO,IAEpB,IAAMC,EAAO,AAAC5K,GAAoB,CAAC,CAAC,EAAEwK,EAAO,EAAE,EAAExK,EAAQ,CAAC,CAIpD6K,EAAS,CAAE,KAAM,AAAC7K,GAAoB0K,EAAK,IAAI,CAACE,EAAK5K,GAAU,SAErE,AAAKyK,EAGE,CACL,KAAM,AAACzK,GAAY0K,EAAK,KAAK,CAACE,EAAK5K,IACnC,KAAM,AAACA,GAAY0K,EAAK,IAAI,CAACE,EAAK5K,IAClC,MAAO,AAACA,GAAY0K,EAAK,KAAK,CAACE,EAAK5K,IACpC6K,OAAAA,CACF,EAPS,CAAE,KAAM,KAAO,EAAG,KAAM,KAAO,EAAG,MAAO,KAAO,EAAGA,OAAAA,CAAO,CAQrE,GF0ZI,AAAuB,UAAvB,OAAOR,GAA2BA,GAAczE,EAET,CAAC,CAACyE,IAErCS,GAAcT,GAChBU,AG/T2B,EAC/BzK,EACAwF,EACAd,EAGAgG,KAEA,GAAM,CAAEpM,IAAAA,CAAG,CAAEqM,QAAAA,CAAO,CAAEnK,KAAAA,CAAI,CAAE,CAAGkE,EAgBzBkG,EAAgC,CACpC5K,KAAAA,EACA1B,IAAAA,EACAqM,QAAAA,EACAnK,KAAAA,EACA6I,MAjBY,CACZ,IAAI,MAAO,CACT,OAAOqB,IAAQ,IAAI,AACrB,EACA,IAAI,OAAQ,CACV,OAAOA,IAAQ,KAAK,AACtB,EACA,IAAI,OAAQ,CACV,OAAOA,IAAQ,KAAK,AACtB,CACF,EAQE,QAAS,EAAE,AACb,EAsEA,MAAO,CACL,MAAOE,EACPC,uBAtE6B,CAACzD,EAAe5G,KAC7C,IAAMsK,EAA0B,IAAIC,MAClC,CACE3D,MAAAA,EACA5G,KAAAA,EACA,SAAU,EAAE,CACZ,OAAQgF,CAAI,CAAC4B,EAAM,EAAE,QAAU,QAC/B,aAAc4D,KAAK,GAAG,EACxB,EACA,CACE,IAAK,CAACC,EAAQC,IACZ,AAAIA,AAAS,WAATA,EACK1F,CAAI,CAAC4B,EAAM,EAAE,QAAU,QAEzB6D,CAAM,CAACC,EAA4B,AAE9C,GAGF,OADAN,EAAY,OAAO,CAACxD,EAAM,CAAG0D,EACtBA,CACT,EAmDEK,wBAjD8B,KAC9B,IAAML,EAA2B,CAC/B,QAAS,EAAE,CACX,UAAWE,KAAK,GAAG,GACnB,aAAc,CAChB,EACA,MAAO,CACLF,MAAAA,EACA,OAAQ,AAAC1D,IACP,IAAM5D,EAASoH,EAAY,OAAO,CAACxD,EAAM,CACrC5D,IACFsH,EAAM,WAAW,CAAGE,KAAK,GAAG,GAGxBxH,EAAO,QAAQ,CAAC,MAAM,EAvED,IAwEvBA,EAAO,QAAQ,CAAC,KAAK,GACvBA,EAAO,QAAQ,CAAC,IAAI,CAACsH,GACrBtH,EAAO,cAAc,CAAGsH,EAE5B,CACF,CACF,EA6BEM,sBA3B4B,CAC5BC,EACAhK,EACAoC,KAEA,IAAMqH,EAAyB,CAC7BzJ,IAAAA,EACAoC,OAAAA,EACA,UAAWuH,KAAK,GAAG,GACnB,aAAc,EACd,SAAU,CACZ,EACMxH,EAASoH,EAAY,OAAO,CAACS,EAAY,CAQ/C,OAPI7H,GAAQ,iBACNA,EAAO,cAAc,CAAC,OAAO,CAAC,MAAM,EA/Fb,IAgGzBA,EAAO,cAAc,CAAC,OAAO,CAAC,KAAK,GAErCA,EAAO,cAAc,CAAC,OAAO,CAAC,IAAI,CAACsH,GACnCtH,EAAO,cAAc,CAAC,YAAY,CAAGsH,GAEhCA,CACT,CAOA,CACF,GHqNQ1F,EACAI,EACA,CACElH,IAAAA,EACA,QAASoG,EAAc,OAAO,EAAI,CAAC,EACnC,KAAMA,EAAc,IAAI,EAAI,QAC9B,EACA,IAAMgC,GAAU,KAAK,IAEvB9G,OAEE0L,GAAQd,IAAa,MAErBe,IJhaArG,EAAaD,CADbA,EAAWD,CADXA,EAAMwG,AAvBG,MACf,IAAMC,EAAOlL,WACP0E,EAAWwG,CAAI,CAAChJ,EAAa,CACnC,GAAIwC,EAAU,OAAOA,EACrB,IAAMyG,EAAoB,IAAIjF,IAE9B,OADAgF,CAAI,CAAChJ,EAAa,CAAGiJ,EACdA,CACT,MAiBuB,GAAG,CIiaCtG,KJhaM,CAAE,MAAO,CAAE,EACtC,AAACH,GAAUD,EAAI,GAAG,CI+ZGI,EJ/ZIF,GACtB,CACL,QAAS,IAAMA,EAAK,KAAK,CACzB,KAAM,KACJA,EAAK,KAAK,EAAI,EACPA,EAAK,KAAK,CAErB,GImaMyG,GAAe,MAAOnI,IAC1B,IAAMyH,EAASM,GAAO,OAAO,GAE7B,GADA/H,EAAO,WAAW,CAAGyH,EACjBzH,EAAO,IAAI,EAAIyH,EAAQ,OAK3B,IAAMW,EAAcpI,EAAO,KAAK,CJndT,qCImduB5D,OAAW,CACvD,SAAU,EACZ,GACA,KAAO,CAAE,OAAMgM,EAAY,IAAI,EAAC,EAAG,IAAI,GAKvCpI,EAAO,IAAI,CAAGyH,CAChB,EAGMY,GAAa,AAACrI,QJnapBsI,EACAb,EACAnH,EAFAgI,EIoa4BtI,EAAO,IAAI,CJnavCyH,EImayCzH,EAAO,WAAW,CJla3DM,EIka6DyH,GAAO,IAAI,GAAtE/H,EAAO,IAAI,CJjaDsI,IAASb,GAAUnH,IAASmH,EAAS,EAAInH,EAAOgI,CIka5D,EAMMC,GAAmB,MACvBnE,EACA1E,KAIA,IAAM8I,EACJxB,GACA,uBAAuB,GACnByB,EAAQ,MAAMvF,GAAU,OAAO,CAACkB,EAAM1E,GAG5C,OAFA8I,EAAQ,MAAM,CAACC,EAAM,MAAM,CAAC,KAAK,EAE1B,CACL,OAAQA,EAAM,MAAM,CACpB,QAAS,KACPD,EAAQ,KAAK,CAAC,WAAW,CAAGhB,KAAK,GAAG,GACpCiB,EAAM,OAAO,EACf,CACF,CACF,EAYMC,GAAsB,MAC1BtE,EACA1E,KAEA,IAAM+I,EAAQzB,GACV,MAAMuB,GAAiBnE,EAAM1E,GAC7B,MAAMwD,GAAU,OAAO,CAACkB,EAAM1E,GAClC,GAAI,CAgBF,GAAM,CAAEE,QAAAA,CAAO,CAAEQ,SAAAA,CAAQ,CAAE,CAAGX,EAAcC,GAC5C,GAAI,CACF,IAAMiJ,EAAUR,GAAaM,EAAM,MAAM,CACzC,OAAO7I,CAAAA,EAAU3C,QAAQ,IAAI,CAAC,CAAC0L,EAAS/I,EAAQ,EAAI+I,CAAM,CAC5D,QAAU,CACRvI,GACF,CACF,CAAE,MAAO2F,EAAO,CAOd,MAJK0C,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,IAEf1C,CACR,CACA,OAAO0C,CACT,EAYMG,GAAO,MAGX/K,EACAoC,EACA9D,KAEA2B,EAAeD,EAAK,QACpB,IAAM4K,EAAQ,MAAMC,GAAoB,OAAQvM,GAAS,QACzD,GAAI,CACF,OAAO,MAAMuE,EAAc+H,EAAM,MAAM,CAAE5K,EAAKoC,EAAQ9D,EACxD,QAAU,CAIHsM,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,GAEvB,CACF,EAUM1I,GAAQ,gBAEZlC,CAAW,CAAEoC,CAAkB,CAAE9D,CAA4B,EAC7D2B,EAAeD,EAAK,SACpB,IAAM4K,EAAQ,MAAMC,GAAoB,OAAQvM,GAAS,QACzD,GAAI,CACF,MAAO0M,EAAeJ,EAAM,MAAM,CAAE5K,EAAKoC,EAAQ9D,EACnD,QAAU,CAIHsM,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,GAEvB,CACF,EASMK,GAAS,gBAEbjL,CAAW,CAAEoC,CAAkB,CAAE9D,CAA4B,EAC7D2B,EAAeD,EAAK,UACpB,IAAM4K,EAAQ,MAAMC,GAAoB,OAAQvM,GAAS,QACzD,GAAI,CACF,MAAOoE,EAAckI,EAAM,MAAM,CAAE5K,EAAKoC,EAAQ9D,EAClD,QAAU,CAIHsM,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,GAEvB,CACF,EAMMlD,GAAQ,MAGZ1H,EACAoC,EACA9D,KAEA,IAAMsM,EAAQ,MAAMC,GAAoB,QAASvM,GAAS,QAC1D,GAAI,CACF,OAAO,MAAM0E,EAAe4H,EAAM,MAAM,CAAE5K,EAAKoC,EAAQ9D,EACzD,QAAU,CAMRkM,GAAWI,EAAM,MAAM,EAIlBA,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,GAEvB,CACF,EAUMM,GAAQ,MAGZlL,EACAoC,EACA9D,KAEA2B,EAAeD,EAAK,SACpB,IAAM4K,EAAQ,MAAMC,GAAoB,OAAQvM,GAAS,QACzD,GAAI,CACF,OAAO,MAAMyE,EAAe6H,EAAM,MAAM,CAAE5K,EAAKoC,EAAQ9D,EACzD,QAAU,CAIHsM,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,GAEvB,CACF,EAEMO,GAAUC,AL5oBQ,CAACC,IAMzB,IAcIC,EAdE,CAAE3M,KAAAA,CAAI,CAAE4M,MAAAA,CAAK,CAAEC,aAAAA,EAAe,KAAK,CAAE7C,OAAAA,CAAM,CAAE,CAAG0C,EAgBtD,OAAO,AAACzB,IAKN,GAAM,CAAEmB,KAAAA,CAAI,CAAErD,MAAAA,CAAK,CAAE+D,YAAAA,CAAW,CAAE,CAAG7B,EAgB/B8B,EAAY,CAChB9M,EACA+M,EACArN,EAEAsN,KAEA,IAkBIC,EAOAC,EAzBEjK,EAASvD,GAAS,OAClByN,EAAgBC,KAAK,KAAK,CAACR,EAAeG,EAAK,MAAM,EAYrDM,EAAYD,KAAK,GAAG,CAAC,EAAG1N,GAAS,WAAa,EAAIyN,GAElDG,EAAiC,EAAE,CAErCC,EAAe/M,QAAQ,OAAO,CAAS,GAEvCgN,EAAS,GACTC,EAAc,EACdC,EAAiB,EAEjBC,EAAa,EAIXC,EAAc,KAClBV,GAAM,UACNA,EAAOvN,MACT,EAOAsD,GAAQ,iBAAiB,QAAS2K,EAAa,CAAE,KAAM,EAAK,GAE5D,IAAMC,EAAO,IACX,IAAIjO,EACF,CAAC,gBAAgB,EAAEI,EAAM,eAAe,EAAEyN,EAAY,SAAS,EAAEC,EAAe,yBAAyB,CAAC,CAC1G,CAAED,YAAAA,EAAaC,eAAAA,CAAe,EAC9B,CAAE,MAAOT,CAAQ,GAGfa,EAAQ,KACZ,IAAMC,EAAW,IAAIT,EAAO,AAC5BA,CAAAA,EAAO,MAAM,CAAG,EAChBK,GAAcI,EAAS,MAAM,CAG7B,IAAMC,EAAW,MAAOC,IACtB,GAAIhB,GAMAhK,GAAQ,QAJV,OADAyK,GAAkBK,EAAS,MAAM,CAC1BE,EAQT,GAAI,CACEjB,GAAQ,MAAMA,EAalB,GAAM,CAAE3I,SAAAA,CAAQ,CAAE,CAAG,MAAMyE,EACzB,CAAC,YAAY,EAAEtH,EAAWxB,GAAO,EAAE,EAAE+M,EAAK,GAAG,CAACvL,GAAY,IAAI,CAAC,KAAK,SAAS,EAAEuM,EAAS,GAAG,CAAC,IAAM,CAAC,CAAC,EAAEhB,EAAK,GAAG,CAAC,IAAM,KAAK,CAAC,CAAC,EAAE,CAAC,CAC/HgB,EAAS,OAAO,CAAC,AAAClL,GAASkK,EAAK,GAAG,CAAC,AAACmB,GAAMrL,CAAI,CAACqL,EAAE,GAClD,CAAEjL,OAAAA,CAAO,GAGX,OADAwK,GAAeM,EAAS,MAAM,CACvBE,EAAkB5J,CAC3B,CAAE,MAAOiF,EAAO,CAGd,GAAIrG,GAAQ,QAEV,OADAyK,GAAkBK,EAAS,MAAM,CAC1BE,EAKT,OAHAhB,EAAU3D,EAEVoE,GAAkBK,EAAS,MAAM,CAC1BE,CACT,CACF,EACAV,EAAeA,EAAa,IAAI,CAAC,MAAOU,IACtC,GAAI,CACF,OAAO,MAAMD,EAASC,EACxB,QAAU,CAKJN,AADJA,CAAAA,GAAcI,EAAS,MAAM,AAAD,EACXV,GAAWO,GAC9B,CACF,EACF,EAEMO,EAAa,IACjB,IAAIvO,EAAqB,CAAC,iBAAiB,EAAEI,EAAM,YAAY,CAAC,CAAE,CAChEyN,YAAAA,EACAC,eAAAA,CACF,GAEF,MAAO,CACL,QAAS,AAAC7K,QA9KZsG,EA+KI,GAAIqE,EAAQ,MAAMW,IAKlB,GADAlL,GAAQ,iBACJgK,EAAS,MAAMY,UAGnB,CAFAP,EAAO,IAAI,CAACzK,GACRyK,EAAO,MAAM,EAAIH,GAAeW,IAChCH,EAAaN,GAAkB9K,EAI5B2K,AADPA,CAAAA,IAtLD,CAAEhE,QAHO,IAAI1I,QAAc,AAAC4N,IACjCjF,EAAUiF,CACZ,GACkBjF,QAAAA,CAAQ,CAsLA,EACN,OAAO,AACrB,EACA,MAAO,UACL,GAAIqE,EAAQ,MAAMW,IAClB,GAAI,CACEb,EAAO,MAAM,EAAEQ,IACnB,IAAMzJ,EAAW,MAAMkJ,EAIvB,GADAtK,GAAQ,iBACJgK,EAAS,MAAMY,IAEnB,OADAL,EAAS,GACFnJ,CACT,QAAU,CACRpB,GAAQ,oBAAoB,QAAS2K,EACvC,CACF,CACF,CACF,EAqMA,MAAO,CAAEd,UAAAA,EAAWuB,OAzGL,CACbrO,EACAsO,EACA5O,KAEA,IFxU2B6O,EEwUrBC,GFxUqBD,EEwUME,OAAO,UAAU,GFvUtD,iBAAoBF,EAAK,OAAO,CAAC,KAAM,MAAM,EEyUnCG,EAAmB5P,OAAO,OAAO,CAACwP,GAAQ,GAAG,CAAC,CAAC,CAACJ,EAAGS,EAAE,IACzD,IAAMC,EAAOC,ADxNW,EAACD,EAAcE,KAC7C,IAAMC,EAAUH,EAAK,IAAI,GACzB,GAAI,CAAClN,EAAY,IAAI,CAACqN,GACpB,MAAM,IAAIzP,EACR,qBACA,CAAC,QAAQ,EAAEwP,EAAO,+BAA+B,EAAErN,KAAK,SAAS,CAACmN,GAAM,oGAAE,CAAC,EAG/E,OAAOG,CACT,GC+MsC,AAAa,UAAb,OAAOJ,EAAiBA,EAAIA,EAAE,IAAI,CAAET,GAC5Dc,EAAS,AAAa,UAAb,OAAOL,GAAkB,CAAC,CAACA,EAAE,MAAM,CAC5CM,EAAU,AAAa,UAAb,OAAON,GAAkB,CAAC,CAACA,EAAE,QAAQ,CAC/CO,EACJ,AAAa,UAAb,OAAOP,GAAkBA,EAAE,SAAS,CAChCQ,AD9M2B,EACvCC,EACAN,KAEA,IAAMC,EAAUK,EAAK,IAAI,GACzB,GACE,CAACL,EAAQ,UAAU,CAAC,MACpB,CAACA,EAAQ,QAAQ,CAAC,MAClBA,EAAQ,QAAQ,CAAC,KAEjB,MAAM,IAAIzP,EACR,qBACA,CAAC,QAAQ,EAAEwP,EAAO,2CAA2C,EAAErN,KAAK,SAAS,CAAC2N,GAAM,iEAAE,CAAC,EAG3F,OAAOL,CACT,GC8LwCJ,EAAE,SAAS,CAAET,GACvCvO,OACN,MAAO,CAAE,KAAMuO,EAAGU,KAAAA,EAAMI,OAAAA,EAAQC,QAAAA,EAASC,UAAAA,CAAU,CACrD,GAIMG,EAAW1C,EAAM,IAAI,CAAC7M,EAAgBC,EAAMyO,IAE5Cc,EAAgBC,AAhHN,KAKhB,AAAK5C,EAAM,SAAS,CAiBpBD,IAAUC,EACP,WAAW,CFvP2B,CAAC,UAAU,EEuPvB5M,EFvP8B,CAAC,CEuPxB,cF5OxCyP,EEgPQ,IF7OF5O,EE6OQ6O,EAAS1L,AAHF,OAAMoI,EACjB,oFAAmF,EAGlF,GAAG,CAAC,AAACnI,GAASA,EAA2B,IAAI,EAC7C,MAAM,CACL,AAACzD,GAAkC,AAAgB,UAAhB,OAAOA,GAE9C,GAAKkP,EAAO,MAAM,CAMlB,IAAK,IAAMC,KF3PnBF,EEwPU,MAAM7C,EAAM,SAAS,GFrPzB/L,EAAO,IAAIhC,IAAI4Q,GACdC,AEmPGA,EFnPI,MAAM,CAAC,AAACzP,GAAU,CAACY,EAAK,GAAG,CAACd,EEqPhCC,EFrPsDC,MEwPtD,MAAM8I,EAAM,CAAC,qBAAqB,EAAEtH,EAAWkO,GAAQ,CAAC,CAE5D,GACC,IAAI,CAAC,IAAM/P,QACX,KAAK,CAAC,KAEP,IAvCcA,SAAV+M,IACFA,EAAQlM,QAAQ,OAAO,GACvBuJ,EAAO,IAAI,CACT,+DAGG2C,EAmCX,IAkEK,IAAI,CAAC,IACJ5D,EAAM;gBACA,EAAEtH,EAAWgN,GAAS;IAClC,EAAEE,EACK,GAAG,CAAC,CAAC,CAAEnO,KAAAA,CAAI,CAAEqO,KAAAA,CAAI,CAAEI,OAAAA,CAAM,CAAEC,QAAAA,CAAO,CAAEC,UAAAA,CAAS,CAAE,GACvC,CAAC,EAAE1N,EAAWjB,GAAM,CAAC,EAAEqO,EAAK,CAAC,EAAEI,EAAS,SAAW,GAAG,CAAC,EAAEC,EAAU,WAAa,GAAG,CAAC,EAAEC,EAAY,CAAC,oBAAoB,EAAEA,EAAU,CAAC,CAAG,GAAG,CAAC,EAEnJ,IAAI,CAAC;IACZ,CAAC,GAEI,IAAI,CAAC,IAAMvP,QAER,CAAEgQ,QAAAA,CAAO,CAAEC,MAAAA,CAAK,CAAE,CAAG9C,EACzB0B,EACA1P,OAAO,IAAI,CAACwP,GAAQ,MAAM,CACxB,AAACuB,GAAQ,AAAuB,UAAvB,OAAOvB,CAAM,CAACuB,EAAI,EAAiB,CAACvB,CAAM,CAACuB,EAAI,CAAC,SAAS,EAEpE,CAAE,OAAQnQ,GAAS,OAAQ,UAAWA,GAAS,SAAU,EACzD4P,GAGIQ,EAAc,UACjB,OAAMT,CAAO,GAChB,EAEMU,EAAc,IAClBvP,QAAQ,IAAI,CAAC,CACXsI,EAAM,CAAC,qBAAqB,EAAEtH,EAAWgN,GAAS,CAAC,EAWnD,IAAIhO,QAAQ,AAAC2I,GAAY6G,WAAW7G,EAhXjB,MAiXpB,EAAE,KAAK,CAAC,KAET,GAEF,MAAO,CACL,QAAS,AAACtG,GAAkC8M,EAAQ9M,GAEpD,MAAO,UACL,IAAIwB,EACJ,GAAI,CAIF,MAAMiL,EACNjL,EAAW,MAAMuL,GACnB,CAAE,MAAOtG,EAAO,CAGd,MAFA,MAAMyG,IACN,MAAMD,IACAxG,CACR,CAEA,GAAI,CACF,MAAMuD,EAAY,MAAOoD,IAKvB,IAAK,IAAMC,KAJX,MAAMD,EAAG,KAAK,CAAC,CAAC,qBAAqB,EAAEzO,EAAWxB,GAAO,CAAC,EAC1D,MAAMiQ,EAAG,KAAK,CACZ,CAAC,YAAY,EAAEzO,EAAWgN,GAAS,WAAW,EAAEhN,EAAWxB,GAAO,CAAC,EAE7CmQ,AAjIV,EACtBnQ,EACAN,KAEA,IAAM0Q,EAAuB,EAAE,CAC/B,IAAK,IAAMjJ,KAASzH,GAAS,SAAW,EAAE,CAAE,CAC1C,IAAM2Q,EAAUC,MAAM,OAAO,CAACnJ,GAC1BA,EACA,AAAiB,UAAjB,OAAOA,EACL,WAAYA,EACV,CAACA,EAAM,MAAM,CAAC,CACdA,EAAM,OAAO,CACf,CAACA,EAAM,CACP6H,EACJ,CAACsB,MAAM,OAAO,CAACnJ,IAAU,AAAiB,UAAjB,OAAOA,GAAsB,CAAC,CAACA,EAAM,MAAM,CACtE,GAAI,CAACkJ,GAAS,OAAQ,SACtB,IAAME,EAAQF,EAAQ,GAAG,CAAClK,QAC1BiK,EAAW,IAAI,CACb,CAAC,MAAM,EAAEpB,EAAS,UAAY,GAAG,qBAAqB,EAAExN,EAAW,CAAC,EAAExB,EAAM,CAAC,EAAEuQ,EAAM,IAAI,CAAC,KAAK,CAAC,EAAEvB,EAAS,IAAM,MAAM,CAAC,EAAE,IAAI,EAAExN,EAAWxB,GAAO,CAAC,EAAEuQ,EAAM,GAAG,CAAC/O,GAAY,IAAI,CAAC,KAAK,CAAC,CAAC,CAE3L,CACA,OAAO4O,CACT,GA2GkDpQ,EAAON,IAC7C,MAAMuQ,EAAG,KAAK,CAACC,EAEnB,EACF,CAAE,MAAO5G,EAAO,CAEd,MADA,MAAMyG,IACAzG,CACR,QAAU,CACR,MAAMwG,GACR,CAEA,OAAOzL,CACT,CACF,CACF,CAE2B,CAC7B,CACF,GK+P6B,CAAE,KAAMc,EAAQ,MAAO/E,IAAe2J,OAAAA,EAAO,GAElE8C,IDvrBL3H,ECurBqC,CACpC,UAAW,CAAE,GAAGuB,EAAS,CAAE,QAASwF,EAAoB,EACxDL,WAAAA,GAGA,WAAY,CAACzE,EAAOmC,IAAUkH,GAAYrJ,EAAOmC,GACjDiD,QAAAA,EACF,EDrqBA,MACEkE,EACA/Q,KAEA,GAAM,CAAEgR,SAAAA,EAAW,EAAK,CAAEC,WAAAA,EAAa,EAAI,CAAE1N,OAAAA,CAAM,CAAE,CAAGvD,GAAW,CAAC,EAI9DsM,EAAQ,MAAM9G,EAAK,SAAS,CAAC,OAAO,CACxCwL,EAAW,OAAS,QACpBzN,GAEIM,EAASyI,EAAM,MAAM,CAErB4E,EAAW,AAACxP,IAChB,GAAIsP,GLLmC,CAACvP,EKKXC,GAC3B,MAAM,IAAI9B,EACR,wBACA,4CAEJ,OAAO8B,CACT,EAEIyP,EAAO,GAIPC,EAAQ,GASNC,EAAa,AACjBC,IAEA,GAAM,CAAE,OAAQC,CAAM,CAAEtQ,QAAAA,CAAO,CAAE,CAAGuQ,ALdd,EAC1BC,EACApL,KAEA,IAAMqL,EAAO,KAAO,EACpB,GAAI,CAACD,GAAKA,IAAMpL,EAAG,MAAO,CAAE,OAAQA,EAAG,QAASqL,CAAK,EACrD,GAAI,CAACrL,GACDoL,EAAE,OAAO,CADL,MAAO,CAAE,OAAQA,EAAG,QAASC,CAAK,EAE1C,GAAIrL,EAAE,OAAO,CAAE,MAAO,CAAE,OAAQA,EAAG,QAASqL,CAAK,EAEjD,IAAMH,EAAS,IAAII,gBACbC,EAAQ,AAACC,GAAwB,IAAMN,EAAO,KAAK,CAACM,EAAO,MAAM,EACjEC,EAAMF,EAAMH,GACZM,EAAMH,EAAMvL,GAGlB,OAFAoL,EAAE,gBAAgB,CAAC,QAASK,EAAK,CAAE,KAAM,EAAK,GAC9CzL,EAAE,gBAAgB,CAAC,QAAS0L,EAAK,CAAE,KAAM,EAAK,GACvC,CACL,OAAQR,EAAO,MAAM,CACrB,QAAS,KACPE,EAAE,mBAAmB,CAAC,QAASK,GAC/BzL,EAAE,mBAAmB,CAAC,QAAS0L,EACjC,CACF,CACF,GKTuDxO,EAAQ+N,GAAO,QAChE,MAAO,CAAE,QAAS,CAAE,GAAGA,CAAK,CAAE,OAAQC,CAAO,EAAQtQ,QAAAA,CAAQ,CAC/D,EAGM+Q,EAAY,gBAChBH,CAAyB,CACzB5Q,CAAmB,EAEnB,GAAI,CACF,MAAO4Q,CACT,QAAU,CACR5Q,GACF,CACF,EAKMgR,EAAS,AAACrQ,GAAmB,KACjC,MAAM,IAAIhC,EACR,wBACA,CAAC,EAAEgC,EAAO,6CAA6C,CAAC,CAE5D,EAEMsQ,EAAOlB,EACT,CACE,UAAWiB,EAAO,aAClB,OAAQA,EAAO,SACjB,EACAzM,EAAK,OAAO,CAAC,CACX,KAAM,CAAC9D,EAAKoC,EAAQwN,KAClB,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAO/M,EAAWV,EAAQsO,EAAOrO,EAAQ9D,GAAS,OAAO,CAACiB,EAC5D,EACA,MAAO,CAACS,EAAKoC,EAAQwN,KACnB,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAO5M,EAAYb,EAAQsO,EAAOrO,EAAQ9D,GAAS,OAAO,CAACiB,EAC7D,EAKA,YAAa,AAACR,GAAOA,EAAG2R,EAC1B,GAEEA,EAA0B,CAC9B,KAAM,CACJ1Q,EACAoC,EACAwN,KAEA,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAO/M,EAAcV,EAAQsO,EAAOrO,EAAQ9D,GAAS,OAAO,CAACiB,EAC/D,EAEA,MAAO,CACLS,EACAoC,EACAwN,KAEA,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAO5M,EAAeb,EAAQsO,EAAOrO,EAAQ9D,GAAS,OAAO,CAACiB,EAChE,EAEA,MAAO,CACLS,EACAoC,EACAwN,KAEA,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAOU,EACLtF,EAAe7I,EAAQsO,EAAOrO,EAAQ9D,GACtCiB,EAEJ,EAEA,OAAQ,CACNS,EACAoC,EACAwN,KAEA,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAOU,EACL5N,EAAcP,EAAQsO,EAAOrO,EAAQ9D,GACrCiB,EAEJ,EAEA,MAAO,CACLS,EACAoC,EACAwN,KAEA,IAAMa,EAAQjB,EAASxP,GACjB,CAAE1B,QAAAA,CAAO,CAAEiB,QAAAA,CAAO,CAAE,CAAGoQ,EAAWC,GACxC,OAAO7M,EAAeZ,EAAQsO,EAAOrO,EAAQ9D,GAAS,OAAO,CAACiB,EAChE,EAEA,UAAWiR,EAAK,SAAS,CACzB,OAAQA,EAAK,MAAM,CAEnB,OAAQ,UAKN3O,GAAQ,iBACR,MAAMqB,EAAKf,EAAQ,UACnBsN,EAAO,EACT,EAEA,SAAU,UACR,MAAMvM,EAAKf,EAAQ,YACnBsN,EAAO,EACT,CACF,EAEM,CAAE1N,QAAAA,CAAO,CAAEQ,SAAAA,CAAQ,CAAE,CAAGX,EAAcC,GAE5C,GAAI,CACFA,GAAQ,iBAQR,MAAMqB,EAAKf,EAAQ,SACnBuN,EAAQ,GAGR7N,GAAQ,iBAER,IAAM8O,EAAUtB,EAASqB,GAQzBC,EAAQ,KAAK,CAAC,KAEd,GACA,IAAM7N,EAASf,EACX,MAAM3C,QAAQ,IAAI,CAAC,CAACuR,EAAS5O,EAAQ,EACrC,MAAM4O,EASV,OAPKlB,IACCF,EACF,MAAMmB,EAAG,MAAM,GAEf,MAAMA,EAAG,QAAQ,IAGd5N,CACT,CAAE,MAAO8N,EAAG,CAIV,GAAIlB,GAAS,CAACD,EACZ,GAAI,CACF,MAAMiB,EAAG,QAAQ,EACnB,CAAE,KAAM,CAMN5M,EAAK,UAAU,CACb3B,EAAO,KAAK,CACZ,IAAIjE,EACF,iBACA,CAAC,OAAO,EAAEiE,EAAO,KAAK,CAAG,EAAE,sDAAsD,CAAC,CAClF,CAAE,MAAOyO,CAAE,GAGjB,CAEF,MAAMA,CACR,QAAU,CACRrO,IAII,AAAC+M,GAAUxL,EAAK,UAAU,CAAC3B,GAI1ByI,EAAM,MAAM,CAAC,OAAO,GAAG,IAAI,CAC9B,IAAMA,EAAM,OAAO,GACnB,IAAMA,EAAM,OAAO,GAEvB,CACF,GCqbM,CAAEc,UAAAA,EAAS,CAAEuB,OAAAA,EAAM,CAAE,CAAG9B,GAAQ,CAAEJ,KAAAA,GAAMrD,MAAAA,GAAO+D,YAAAA,EAAY,GAG3DoF,GAAU,MAAO/I,EAA2BgJ,KAChD,IAAIC,EACJ,GAAI,CACF,MAAM3R,QAAQ,IAAI,CAAC,CACjB0I,EACA,IAAI1I,QAAc,AAAC2I,IACjBgJ,EAAQnC,WAAW7G,EAAS+I,EAC9B,GACD,CACH,QAAU,CACRE,aAAaD,EACf,CACF,EAiCME,GAAc5N,EAAc,WAAW,EAAI,IAC3C6N,GAAe7N,EAAc,YAAY,EAAI,IAE7CmF,GAAa2I,AI9vBW,CAAC7S,IAI/B,GAAM,CAAE8S,KAAAA,CAAI,CAAEC,kBAAAA,EAAoB,CAAC,CAAE,CAAG/S,EAElCgT,EAAgBpC,MAAM,IAAI,CAAC,CAAE,OAAQkC,CAAK,EAAG,IAAO,EACxD,UAAW,GACX,MAAO,GACP,KAAM,GACN,SAAU,CACZ,IAEMG,EAAY,IAAMD,EAAM,MAAM,CAAC,AAACE,GAASA,EAAK,KAAK,EAAE,MAAM,CAEjE,MAAO,CACL,OAAQ,CAACzL,EAAO0L,KACd,IAAMD,EAAOF,CAAK,CAACvL,EAAM,CACzB,GAAKyL,GAEL,GAAIC,AAAU,YAAVA,EAAqB,CAQvB,GAAID,EAAK,IAAI,CAAE,MACfA,CAAAA,EAAK,KAAK,CAAG,GACb,MACF,CAEA,GAAIC,AAAU,UAAVA,EAAmB,CAGrB,GAAID,EAAK,IAAI,CAAE,MACfA,CAAAA,EAAK,SAAS,CAAG,GACjBA,EAAK,KAAK,CAAG,GAGb,MACF,CAEA,GAAIC,AAAU,WAAVA,EAAoB,CAGtB,GAAI,CAACD,EAAK,KAAK,CAAE,MACjBA,CAAAA,EAAK,QAAQ,CAAG,EAChB,MACF,CAEA,GAAIC,AAAU,SAAVA,EAAkB,CAWpB,GAAI,CAACD,EAAK,KAAK,CAAE,OAGjB,OAFAA,EAAK,KAAK,CAAG,GACbA,EAAK,IAAI,CAAG,GACLD,AAAgB,IAAhBA,IAAoB,cAAgB,MAC7C,CAIA,GAAKC,EAAK,KAAK,OAKf,CAJAA,EAAK,KAAK,CAAG,GAITA,EAAK,SAAS,EAAIA,EAAK,QAAQ,CAAGH,IACpCG,EAAK,QAAQ,EAAI,EACV,YAGTA,EAAK,IAAI,CAAG,GACLD,AAAgB,IAAhBA,IAAoB,cAAgB,QAC7C,CACF,CACF,GJyqBsC,CAClC,KAAMrN,EACN,kBAAmBb,EAAc,iBAAiB,AACpD,GAIM+E,GAAa,AAACF,IAGlB,IAAK,IAAMwJ,KAFXlO,IAAU0E,EACL7C,GAAU,QAAQ,CAAC7B,GACJW,GAAMuN,GAAO,WACnC,EAEMrJ,GAAQ,AAACtC,IAMbyC,GAAW,MAAM,CAACzC,EAAO,WACzB,IAAMgL,EAAQnC,WAAW,KACvBQ,GACErJ,EACA,IAAI7H,EACF,UACA,CAAC,OAAO,EAAE6H,EAAQ,EAAE,6BAA6B,EAAEkL,GAAY,uFAAK,CAAC,EAI3E,EAAGA,IAEEU,AH1rBuB,CAAC7N,IAoB/B,IA8BI8N,EAOAC,EAIAC,EACAC,EAYAC,EAtDE,CACJjM,MAAAA,CAAK,CACL5B,KAAAA,CAAI,CACJF,aAAAA,CAAY,CACZtF,KAAAA,CAAI,CACJ1B,IAAAA,CAAG,CACHe,MAAAA,CAAK,CACLqG,KAAAA,CAAI,CACJiF,QAAAA,CAAO,CACP2I,mBAAAA,CAAkB,CACnB,CAAGnO,EACE,CAAE0F,uBAAAA,CAAsB,CAAEO,sBAAAA,CAAqB,CAAEpB,OAAAA,CAAM,CAAE,CAAG7E,EAE5DoO,EAAe9S,QAAQ,aAAa,GAEpC+S,EAAa,CAAC,EAAElO,EAAa,UAAU,EAAE8B,EAAQ,EAAE,CAAC,CACpD5D,EAASzE,OAAO,MAAM,CAACgE,EAAYyQ,GAA2B,CAClEpM,MAAAA,EACA,OAAQ,MACR,KAAM,GACN,YAAa,CACf,EACA5B,CAAAA,CAAI,CAAC4B,EAAM,CAAG5D,EACdwG,EAAO,IAAI,CAAC,CAAC,OAAO,EAAE5C,EAAQ,EAAE,QAAQ,CAAC,EAEzC,IAAM0D,EAAQD,IAAyBzD,EAAOoM,GAE1CC,EAAgB,EAOhBC,EAAiB,GAUjB1M,EAAO,GACP2M,EAAQ,GACNC,EAAgBnT,QAAQ,aAAa,GAG3CmT,EAAc,OAAO,CAAC,KAAK,CAAC,KAAO,GAOnC,IAAMC,EAAM,AAACtK,IACPvC,IACJA,EAAO,GACPxD,EAAO,MAAM,CAAG,OAChBoQ,EAAc,MAAM,CAACrK,GACrBgK,EAAa,MAAM,CAAChK,GACpBpE,EAAK,OAAO,GAAGiC,EAAOmC,GACxB,CAEA/F,CAAAA,EAAO,OAAO,CAAG,AAACsP,IAEhB,IAAMgB,EACJ,AAAiB,UAAjB,OAAOhB,GAAsBA,AAAU,OAAVA,GAAkB,YAAaA,EACxD1M,OAAO2N,AAHMjB,EAGK,OAAO,EAAI,IAC7B,GAeAkB,EAAYD,AAnBCjB,EAmBU,QAAQ,CACrC9I,EAAO,KAAK,CAAC,CAAC,OAAO,EAAE5C,EAAQ,EAAE,UAAU,EAAE0M,EAAO,CAAC,EACrDD,EACE,IAAItU,EACF,iBACAoU,EACI,CAAC,OAAO,EAAEvM,EAAQ,EAAE,SAAS,EAAE0M,GAAU,iBAAiB,CAAC,CAC3D,CAAC,wCAAwC,EACvCE,EACI,CAAC,MAAM,EAAEA,EAAU,CAAC,CACpB,CAAC,oCAAoC,EAAE,YAAY,GAAG,CAAC,0CAA0C,CAAC,CAGrG,gKAAsE,EAAEF,EAAO,CAF9E,CAGR,CAAE,MAAOhB,CAAM,GAGrB,EAEAtP,EAAO,gBAAgB,CAAC,eAAgB,KACtCwG,EAAO,KAAK,CAAC,CAAC,OAAO,EAAE5C,EAAQ,EAAE,iCAAiC,CAAC,EACnEiM,GAAM,OACJ,IAAI9T,EACF,iBACA,CAAC,OAAO,EAAE6H,EAAQ,EAAE,gFAAgF,CAAC,EAG3G,GAGA5D,EAAO,SAAS,CAAG,CAAC,CAAEV,KAAAA,CAAI,CAAmC,IAC3D,GAAM,CAAEmR,OAAAA,CAAM,CAAEpF,KAAAA,CAAI,CAAE,CAAG/L,EACzB,OAAQ+L,GACN,IAAK,QACY,IAAXoF,IACFN,EAAQ,GACRnQ,EAAO,MAAM,CAAG,QACZsH,GAAOA,CAAAA,EAAM,kBAAkB,CAAGE,KAAK,GAAG,EAAC,EAC/ChB,EAAO,IAAI,CAAC,CAAC,OAAO,EAAE5C,EAAQ,EAAE,MAAM,CAAC,EACvCmM,EAAa,OAAO,CAAC/P,IAEvB,KAEF,KAAK,aACY,IAAXyQ,IACFjK,EAAO,KAAK,CAAC,CAAC,OAAO,EAAE5C,EAAQ,EAAE,iBAAiB,EAAEtE,EAAK,OAAO,CAAC,CAAC,EAClE+Q,EACEhR,EAAaC,IACX,IAAIvD,EAAY,iBAAkBuD,EAAK,OAAO,CAAE,CAC9C,MAAOA,EAAK,KAAK,AACnB,KAGN,KAEF,KAAK,SACY,IAAXmR,IACFjK,EAAO,IAAI,CAAC,CAAC,OAAO,EAAE5C,EAAQ,EAAE,OAAO,CAAC,EACxC5D,EAAO,MAAM,CAAG,SAChB0P,GAAe,WAEjB,KAEF,KAAK,QACCD,GAAiBgB,IAAWR,IAC1B3I,GAAO,gBAAgB,cACzBA,CAAAA,EAAM,cAAc,CAAC,YAAY,CAAC,YAAY,GAAKE,KAAK,GAAG,EAAC,EAE9DiI,EAAc,OAAO,CAACnQ,EAAK,IAAI,EAC/BmQ,EAAgBxS,QAAQ,aAAa,IAEvC,KAEF,KAAK,OACH,GAAIwS,GAAiBgB,IAAWR,EAAe,CAC7C,IAAMnP,EAAWxB,EAAK,QAAQ,CAC1BgI,GAAO,gBAAgB,eACzBA,EAAM,cAAc,CAAC,YAAY,CAAC,YAAY,CAAGxG,EACjDwG,EAAM,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAGhI,EAAK,QAAQ,CAC1DgI,EAAM,cAAc,CAAC,YAAY,EAAIxG,EACrCwG,EAAM,cAAc,CAAC,YAAY,CAAC,OAAO,CAAGE,KAAK,GAAG,IAEtDiI,EAAc,OAAO,CAAC3O,GACtB2O,EAAgBrT,OACZ,AAAC8T,GAAgBvO,EAAK,QAAQ,GAAGiC,GACrCsM,EAAiB,EACnB,CACA,KAEF,KAAK,QACH,GAAIT,GAAiBgB,IAAWR,EAAe,CAC7C,IAAMlK,EAhOV1G,EAgO8BC,IAhOR,AAAItD,MAAMsD,AAgOFA,EAhOO,OAAO,CAAE,CAAE,MAAOA,AAgOzBA,EAhO8B,KAAK,AAAC,GAiO1DgI,GAAO,gBAAgB,eACzBA,EAAM,cAAc,CAAC,YAAY,CAAC,KAAK,CAAGvB,EAC1CuB,EAAM,cAAc,CAAC,YAAY,CAAC,OAAO,CAAGE,KAAK,GAAG,IAEtDiI,EAAc,MAAM,CAAC1J,GAYrB0J,EAAc,OAAO,CAAC,KAAK,CAAC,KAAO,EACrC,CACA,KAEF,KAAK,UAGH,KAEF,SAEE,MAAM,AAAIzT,MACR,CAAC,0BAA0B,EAAEkC,KAAK,SAAS,CAFlBoB,GAEgC,CAAC,CAGhE,CACF,EAMA,IAAMgP,EAAQ,gBAGZzQ,CAAW,CACXoC,CAAkB,CAClB9D,CAAgC,EAEhC,GAAI,CACF,GAAIsT,EAEF,MADA5I,QAAQ,KAAK,CAAC,CAAC,sCAAsC,EAAEjD,EAAQ,EAAE,CAAC,EAC5D,AAAI5H,MAAM,wCAGlB,GAAIsL,GAAO,eAAgB,CACzB,IAAMoJ,EAAa9I,IAAwBhE,EAAO/F,EAAKoC,EACvDqH,CAAAA,EAAM,cAAc,CAAC,YAAY,CAAGoJ,CACtC,CAGA,GAAM,CACJxQ,UAAAA,EAAY,GAAG,CACfC,QAAAA,EAAUwQ,CAAqB,CAC/BC,SAAAA,EAAW,EAAK,CACjB,CAAGzU,GAAW,CAAC,EAqBhB,IApBA+T,EAAiBU,EAGjBnB,EAAgBxS,QAAQ,aAAa,GAErC4S,AADAA,CAAAA,EAAO5S,QAAQ,aAAa,IACvB,OAAO,CAAC,KAAK,CAAC,KAAO,GAC1B0S,EAAO1S,QAAQ,aAAa,GAC5B2S,EAAgB3S,QAAQ,aAAa,GAGrC+C,EAAO,WAAW,CAAC,CACjB,KAAM,QACN,OAAQ,EAAEiQ,EACVpS,IAAAA,EACAoC,OAAAA,EACA,QAAS,CAAEC,UAAAA,EAAWC,QAAAA,CAAQ,CAChC,GACAH,EAAO,MAAM,CAAG,UAGTyP,GAAe,CACpB,IAAM1P,EAAQ,MAAM9C,QAAQ,IAAI,CAAC,CAC/BwS,EAAc,OAAO,CACrBG,EAAc,OAAO,CACrBC,EAAK,OAAO,CACZO,EAAc,OAAO,CACtB,EACD,GAAIrQ,IAAUZ,EAAM,KACpB,OAAMY,EAKF,AAAiB,UAAjB,OAAOA,GACTC,EAAO,WAAW,CAAC,CAAE,KAAM,SAAU,OAAQiQ,EAAe,EAAG,CAAE,EAErE,CACF,QAAU,CAMR,GAAIR,GAAiB,CAACjM,EAAM,KAMtBoL,CALJ5O,CAAAA,EAAO,MAAM,CAAG,WAIhBA,EAAO,WAAW,CAAC,CAAE,KAAM,OAAQ,OAAQiQ,CAAc,GAEzD,IAAMY,EAAS,IAAI5T,QAAe,CAAC4C,EAAGC,KACpC8O,EAAQnC,WACN,IACE3M,EACE,IAAI/D,EACF,iBACA,CAAC,OAAO,EAAE6H,EAAQ,EAAE,wCAAwC,EAAEjC,EAAK,YAAY,CAAC,mBAAmB,CAAC,GAG1GA,EAAK,YAAY,CAErB,GACA,GAAI,CACF,KAAO8N,GACL,MAAMxS,QAAQ,IAAI,CAAC,CAACwS,EAAc,OAAO,CAAEoB,EAAO,CAEtD,CAAE,MAAO9K,EAAO,CAIVA,aAAiBhK,GAAegK,AAAe,mBAAfA,EAAM,IAAI,EAC5CsK,EAAItK,EAER,QAAU,CACR8I,aAAaD,EACf,CACF,CACAa,EAAgBrT,OAChByT,EAAOzT,OACPwT,EAAgBxT,OAGhB8T,EAAiB,GACjBlQ,EAAO,MAAM,CAAGwD,EAAO,OAAS,QAChCmM,GAAM,UACNA,EAAOvT,MACT,CACF,EAmCA,OAhCAb,OAAO,MAAM,CAACyE,EAAQ,CACpBsO,MAAAA,EAMA,UAAW,KACTsB,GAAe,QAAQzQ,EACzB,EACA,QAAS,IAAMwQ,GAAM,SAAW1S,QAAQ,OAAO,GAC/C,MAAO,UACAyS,IACHA,EAAgBzS,QAAQ,aAAa,GACrC+C,EAAO,WAAW,CAAC,CAAE,KAAM,QAAS,OAAQ,CAAE,IAEhD,MAAM0P,EAAc,OAAO,AAC7B,CACF,GAGA1P,EAAO,WAAW,CAAC,CACjB,OAAQ,EACR,KAAM,OACNxD,KAAAA,EACA1B,IAAAA,EACAe,MAAAA,EACAqG,KAAAA,EACAiF,QAAAA,EACA2I,mBAAAA,CACF,GAEOC,EAAa,OAAO,AAC7B,GG4T0B,CACpBnM,MAAAA,EACA5B,KAAAA,EACAF,aAAAA,EACA,KAAMF,EACN9G,IAAAA,EACAe,MAAAA,EACAqG,KAAAA,EACA,QAAShB,EAAc,OAAO,CAC9B,mBApwB+B,GAqwB/B,QAAS+L,GACT,SAAU,AAAC6D,IACTzK,GAAW,MAAM,CAACyK,EAAQ,SAC5B,EACA/B,aAAAA,GACA,uBAAwB/H,IAAa,uBACrC,sBAAuBA,IAAa,sBACpCR,OAAAA,EACF,GACG,IAAI,CAAC,AAACxG,IACLqG,GAAW,MAAM,CAACzC,EAAO,SAIzBZ,GAAc,MAAM,CAACY,GACrBV,GAAU,GAAG,CAAClD,EAChB,GACC,KAAK,CAAC,KAEP,GACC,OAAO,CAAC,IAAM6O,aAAaD,GAChC,EAOM5I,GAAiB,CAACpC,EAAemC,KAErC,IAAMgL,EAAO/O,EAAK,MAAM,CAACsE,SAAS,MAAM,CACxCE,GAAO,MAAM,CAAC,IAAI,CAChB,CAAC,OAAO,EAAE5C,EAAQ,EAAE,mBAAmB,EAAEmN,EAAK,IAAI,EAAEhP,EAAS,CAAC,EAEhE,IAAMiP,EAAK9P,EAAc,YAAY,CACrC,GAAI8P,EACF,GAAI,CACFA,EAAG,CAAEpN,MAAAA,EAAOmN,KAAAA,EAAM,KAAMhP,EAAU,MAAOgE,CAAM,EACjD,CAAE,MAAOkL,EAAS,CAChBzK,GAAO,MAAM,CAAC,IAAI,CAChB,CAAC,6BAA6B,EAAEyK,aAAmBjV,MAAQiV,EAAQ,OAAO,CAAGrO,OAAOqO,GAAS,CAAC,CAElG,CAEJ,EAEMhE,GAAc,CAACrJ,EAAemC,KAGlC,IAAMmL,EAAenO,GAyBrB,GAvBImO,IAQF/P,IAAsB4E,EAItB/C,GAAc,GAAG,CAACY,EAAOmC,IAM3B/D,CAAI,CAAC4B,EAAM,EAAE,YACb5B,CAAI,CAAC4B,EAAM,CAAGxH,OAEd8G,GAAU,MAAM,CAACU,GAEbsN,EAAc,OAGlB,IAAMC,EAAW9K,GAAW,MAAM,CAACzC,EAAO,OACtCuN,AAAa,aAAbA,GACF3K,GAAO,IAAI,CAAC,CAAC,kBAAkB,EAAE5C,EAAQ,EAAE,CAAC,EACvCsC,GAAMtC,IACFuN,AAAa,SAAbA,EAETnL,GAAepC,EAAOmC,GACA,gBAAboL,IAETnL,GAAepC,EAAOmC,GACtBE,GAAWF,GAEf,EAGA,IAAK,IAAInC,EAAQ,EAAGA,EAAQ7B,EAAU6B,GAAS,EAAGsC,GAAMtC,GAgBxD,MAbY,CACV7D,MAAAA,GACA6I,KAAAA,GACArD,MAAAA,GACAuD,OAAAA,GACAC,MAAAA,GACAO,YAAAA,GACAC,UAAAA,GACAuB,OAAAA,GACAuB,MA5KY,IACZ,AAAIjL,GACJA,CAAAA,EAAW,WACToF,GAAO,IAAI,CAAC,kBAGZ,IAAM4K,EAAWlO,GAAU,QAAQ,CACjC,IAAInH,EAAY,gBAAiB,sCAInC,OAAM2S,GAAQ0C,EAAUrC,IACxB,MAAM9R,QAAQ,GAAG,CACf+E,EAAK,GAAG,CAAC,MAAOhC,IACTA,IACL,MAAM0O,GAAQ1O,EAAO,KAAK,GAAI+O,IAC9B/O,EAAO,SAAS,GAClB,IAEFgC,EAAK,MAAM,CAAG,CAChB,IAAG,EA0JH8F,MAAAA,EACF,CAEF,EKv3BauJ,EAAiB,MAC5B7U,EACAL,KAEA,GAAI,CAACA,GAAS,IACZ,MAAM,IAAIJ,EACR,iBACA,CAAC,8DAA8D,EAAEhB,EAAgB,2JAA2J,CAAC,EAIjP,IAAMD,EAAMqB,EAAQ,GAAG,CACjBN,EAAQM,EAAQ,KAAK,EAAItB,EAAgBC,GACzCmH,EAAarH,CAAgB,CAACE,EAAI,CAExC,GAAI,CAAEmH,EAAW,MAAM,CAA4B,QAAQ,CAACpG,GAC1D,MAAM,IAAIE,EACR,iBACA,CAAC,EAAEjB,EAAI,oBAAoB,EAAEe,EAAM,oBAAoB,EAAEoG,EAAW,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAM5F,GAAIA,AAAsB,WAAtBA,EAAW,MAAM,CAAe,OAEpC,IAAML,EAASrD,EAAsB/B,GAC/B0F,EAAOzD,EAAoBtC,EAAQ,OAAO,CAAEN,EAAOsG,SAAS,IAAI,EAMtE,GAAI,CAJQ,MAAMtF,IAAc,WAAW,CZ3BC,CAAC,SAAS,EY2BG+E,EZ3BI,CAAC,CY2BI,IAChE0P,EAAU,CAAE,KAAM1P,EAAQ9G,IAAAA,EAAKe,MAAAA,EAAOqG,KAAAA,CAAK,IAI3C,MAAM,IAAInG,EACR,OACA,CAAC,EAAE6F,EAAO,6FAA6F,CAAC,CAG9G,EAYM0P,EAAY,AAACpV,GAMjB,IAAIe,QAAc,CAAC2I,EAAS9F,KAC1B,IAAME,EAAST,EAAY,CAAC,gBAAgB,EAAErD,EAAQ,IAAI,CAAC,CAAC,EAEtD0S,EAAQnC,WAAW,KACvB8E,EACE,IAAIxV,EACF,UACA,YAAYG,EAAQ,IAAI,4FAAwG,EAGtI,EAlBmB,KAoBbqV,EAAS,AAACxL,IACd8I,aAAaD,GACb5O,EAAO,SAAS,GACZ+F,EAAOjG,EAAOiG,GACbH,GACP,CAEA5F,CAAAA,EAAO,SAAS,CAAG,AAACsP,IAClB,IAAMhQ,EAAOgQ,EAAM,IAAI,OACvB,AAAIhQ,AAAc,YAAdA,EAAK,IAAI,CAAuBiS,IAChCjS,AAAc,UAAdA,EAAK,IAAI,CACJiS,EACLlS,EAAaC,IACX,IAAIvD,EAAY,iBAAkBuD,EAAK,OAAO,CAAE,CAC9C,MAAOA,EAAK,KAAK,AACnB,UAGR,EAEAU,EAAO,OAAO,CAAG,AAACsP,IAChBiC,EACE,IAAIxV,EACF,iBACA,CAAC,8BAA8B,EAAEG,EAAQ,IAAI,CAAC,EAAE,EAAGoT,EAAqB,OAAO,EAAI,GAAG,CAAC,EAG7F,EAEAtP,EAAO,WAAW,CAAC,CAAE,KAAM,SAAU,OAAQ,EAAG,GAAG9D,CAAO,AAAC,EAC7D,U"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A thin wrapper over `navigator.locks`, used by `output()` to make its staging
3
+ * tables collectable across tabs (D3).
4
+ *
5
+ * The staging lock is NOT mutual exclusion — nothing contends for its name. It
6
+ * is a liveness marker: a lock held for as long as a staging table exists is
7
+ * what lets another tab's sweep tell an in-flight table from an orphan. A tab
8
+ * that is killed has its locks released by the browser, so its orphans become
9
+ * collectable immediately, with no timestamp and no grace period.
10
+ */
11
+ /** The slice of the Web Locks API this module uses. */
12
+ type LockManager = {
13
+ request: (name: string, optionsOrCallback: any, callback?: (lock: unknown) => Promise<unknown>) => Promise<unknown>;
14
+ query: () => Promise<{
15
+ held?: {
16
+ name?: string;
17
+ }[];
18
+ }>;
19
+ };
20
+ export type Locks = {
21
+ /** False when the Web Locks API is missing; every method then no-ops. */
22
+ readonly available: boolean;
23
+ /** Acquires `name` and resolves with the function that releases it. */
24
+ hold: (name: string) => Promise<() => void>;
25
+ /** Runs `fn` while holding `name` exclusively. */
26
+ withLock: <T>(name: string, fn: () => Promise<T>) => Promise<T>;
27
+ /**
28
+ * Runs `fn` while holding `name`, or skips it entirely when the lock is held
29
+ * elsewhere. Never waits — which is the point: the staging sweep is
30
+ * opportunistic, and awaiting this lock inside an open transaction would
31
+ * hold SQLite's write lock while waiting on a holder that may itself be
32
+ * waiting for that write lock.
33
+ *
34
+ * Resolves `true` if `fn` ran, `false` if it was skipped.
35
+ */
36
+ tryWithLock: (name: string, fn: () => Promise<unknown>) => Promise<boolean>;
37
+ /** Names currently held anywhere in this origin — every tab included. */
38
+ heldNames: () => Promise<string[]>;
39
+ };
40
+ export declare const stagingTableName: (uuid: string) => string;
41
+ export declare const isStagingTable: (table: string) => boolean;
42
+ export declare const stagingLockName: (file: string, table: string) => string;
43
+ export declare const sweepLockName: (file: string) => string;
44
+ /** Serializes database opening across the pool — replaces the SAB init mutex. */
45
+ export declare const initLockName: (file: string) => string;
46
+ /**
47
+ * Which staging tables no live `output()` is using — pure, so it is driven by
48
+ * Node tests rather than by two browser tabs.
49
+ */
50
+ export declare const staleStagingTables: (tables: string[], heldNames: string[], file: string) => string[];
51
+ /** The no-op Locks value for environments where the Web Locks API is absent. */
52
+ export declare const noOpLocks: Locks;
53
+ export declare const createLocks: (manager?: LockManager | undefined) => Locks;
54
+ export {};