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

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.
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export type SupervisorDecision = 'restart' | 'lost' | 'fail-client';
11
11
  export type Supervisor = {
12
- report: (index: number, event: 'spawned' | 'ready' | 'served' | 'died' | 'lost') => SupervisorDecision | undefined;
12
+ report: (index: number, event: 'spawned' | 'ready' | 'served' | 'died' | 'lost' | 'retired') => SupervisorDecision | undefined;
13
13
  };
14
14
  export declare const createSupervisor: (options: {
15
15
  size: number;
@@ -1,6 +1,7 @@
1
1
  import type { SQLiteQueryAPI, SQLiteTransactionDB, SQLiteTransactionOptions } from './api';
2
2
  import type { ReadFn, TransactionFn, WriteFn } from './bulk';
3
3
  import { SQLiteError } from './errors';
4
+ import type { Logger } from './logger';
4
5
  import type { PoolWorker } from './pool';
5
6
  import type { Scheduler } from './scheduler';
6
7
  /**
@@ -13,7 +14,7 @@ import type { Scheduler } from './scheduler';
13
14
  */
14
15
  export declare const createTransaction: (deps: {
15
16
  scheduler: Scheduler<PoolWorker>;
16
- afterWrite: (worker: PoolWorker) => void;
17
+ afterWrite: (worker: PoolWorker) => Promise<unknown>;
17
18
  /**
18
19
  * Called when a connection may still hold an open transaction. The worker
19
20
  * is lost rather than repaired: a "dirty worker" state is one more
@@ -21,6 +22,17 @@ export declare const createTransaction: (deps: {
21
22
  * connection is transaction-free by construction.
22
23
  */
23
24
  onPoisoned: (index: number, error: SQLiteError) => void;
25
+ /**
26
+ * Aborted when the client closes, with `CLIENT_CLOSED`.
27
+ *
28
+ * Merged into the transaction's own signal so that closing ABANDONS a
29
+ * running transaction the way a caller's `signal` does — the callback is
30
+ * not interrupted, it simply can no longer reach the database. Without it
31
+ * the caller of a transaction whose callback sits on an `await` that is not
32
+ * a statement waited for ever: nothing else in the transaction observes the
33
+ * client going away.
34
+ */
35
+ closeSignal: AbortSignal;
24
36
  /**
25
37
  * The client's bulk factory. Called per transaction with the transaction's
26
38
  * own read/write and a pass-through `transaction`, so output()'s swap runs
@@ -31,8 +43,19 @@ export declare const createTransaction: (deps: {
31
43
  read: ReadFn;
32
44
  write: WriteFn;
33
45
  transaction: TransactionFn;
46
+ /** See `src/bulk.ts`: the batch's place in this transaction's queue. */
47
+ reserve?: () => {
48
+ started: Promise<void>;
49
+ done: () => void;
50
+ };
34
51
  }) => {
35
52
  bulkWrite: SQLiteQueryAPI['bulkWrite'];
36
53
  output: SQLiteQueryAPI['output'];
37
54
  };
55
+ /**
56
+ * Reached only through `always`: `rollback()` on a transaction that has
57
+ * already committed warns whatever the `debug` option says (spec R4) — a
58
+ * warning visible only under debug would be the same as silence.
59
+ */
60
+ logger: Pick<Logger, 'always'>;
38
61
  }) => <T = void>(callback: (db: SQLiteTransactionDB) => Promise<T>, options?: SQLiteTransactionOptions) => Promise<T>;
package/dist/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { SQLiteErrorCode } from './errors';
2
+ import type { SQLiteResultCode } from './sqlite-codes';
1
3
  export type SQLiteWorkerMessageData<_T = unknown> = {
2
4
  callId: number;
3
5
  terminate?: boolean;
@@ -25,10 +27,25 @@ export declare const SharedArrayTypes: {
25
27
  STRING: number;
26
28
  OBJECT: number;
27
29
  };
30
+ /**
31
+ * The savepoint a transaction asks the worker to handle around one query
32
+ * (spec 2026-09-11, D4/D5). `conclude` settles the savepoint the previous
33
+ * savepointed write left open — `release` keeps that write, `undo` rolls it
34
+ * back first — and `open` starts one for this query's own statement. Both run
35
+ * before the statement, conclusion first. Internal: no consumer sets it.
36
+ */
37
+ export type SavepointOp = {
38
+ conclude?: 'release' | 'undo';
39
+ open?: true;
40
+ };
28
41
  type SQLOptions = {
29
42
  chunkSize?: number;
30
43
  /** Chunks the worker may send before waiting for a credit. Spec §3.2. */
31
44
  credits?: number;
45
+ /** When true, the worker installs an async progress handler so an AbortSignal can stop a running step(). */
46
+ abortable?: boolean;
47
+ /** See `SavepointOp`. */
48
+ savepoint?: SavepointOp;
32
49
  };
33
50
  /**
34
51
  * Where a worker fetches its `.wasm` from, when the consumer overrode it.
@@ -57,7 +74,26 @@ export type ClientMessageData = {
57
74
  pragmas?: Record<string, string>;
58
75
  /** Statements retained per worker; see `src/client.ts`. Internal. */
59
76
  statementCacheSize?: number;
77
+ /** Bytes retained per worker; see `src/client.ts`. Internal. */
78
+ statementCacheBytes?: number;
60
79
  wasm?: WasmLocation;
80
+ /** Shared abort slots, one Int32 per worker. Isolated contexts only. */
81
+ abortSlots?: SharedArrayBuffer;
82
+ /** This worker's index into `abortSlots`. */
83
+ abortIndex?: number;
84
+ /**
85
+ * Features this worker must find before opening; it declines instead of
86
+ * opening when one is missing (spec 2026-09-13). Sent to slots of index
87
+ * ≥ 1 only, and only by a VFS that declares `singleConnectionWithout`.
88
+ */
89
+ declineWithout?: readonly PlatformFeature[];
90
+ /**
91
+ * Features worker 0 probes before opening, where the VFS is exclusive
92
+ * without one (spec 2026-09-15, §3.2): it reports them with `probed`,
93
+ * then loads nothing until `proceed`. Sent to slot 0 only, and only by a
94
+ * VFS that declares `exclusiveConnectionWithout`.
95
+ */
96
+ probeFirst?: readonly PlatformFeature[];
61
97
  } | {
62
98
  type: 'query';
63
99
  callId: number;
@@ -74,6 +110,11 @@ export type ClientMessageData = {
74
110
  } | {
75
111
  type: 'stop';
76
112
  callId: number;
113
+ }
114
+ /** The client decided the connection lock; worker 0 may open (spec 2026-09-15). */
115
+ | {
116
+ type: 'proceed';
117
+ callId: number;
77
118
  } | {
78
119
  type: 'delete';
79
120
  callId: number;
@@ -85,6 +126,24 @@ export type ClientMessageData = {
85
126
  export type WorkerMessageData = {
86
127
  type: 'ready';
87
128
  callId: number;
129
+ }
130
+ /**
131
+ * The worker found a feature of `declineWithout` missing and opened nothing:
132
+ * the environment caps the pool (spec 2026-09-13).
133
+ */
134
+ | {
135
+ type: 'declined';
136
+ callId: number;
137
+ missing: PlatformFeature;
138
+ }
139
+ /**
140
+ * Worker 0's answer to `probeFirst`: the first feature missing, or null. It
141
+ * opens nothing until the client sends `proceed` (spec 2026-09-15, §3.2).
142
+ */
143
+ | {
144
+ type: 'probed';
145
+ callId: number;
146
+ missing: PlatformFeature | null;
88
147
  } | {
89
148
  type: 'chunk';
90
149
  callId: number;
@@ -100,26 +159,66 @@ export type WorkerMessageData = {
100
159
  * "for a sub-millisecond effect, count the round trips").
101
160
  */
102
161
  prepared: number;
162
+ /**
163
+ * Whether the connection is inside a transaction once this query has
164
+ * ended — `sqlite3_get_autocommit() === 0`. SQLite can leave a
165
+ * transaction by itself: an interrupted INSERT/UPDATE/DELETE rolls the
166
+ * whole transaction back. Absent when the worker could not read it.
167
+ */
168
+ inTransaction?: boolean | undefined;
103
169
  } | {
104
170
  type: 'error';
105
171
  callId: number;
106
172
  message: string;
107
173
  cause?: unknown;
108
174
  /** SQLite's numeric result code, when the failure came from SQLite. */
109
- sqliteCode?: number;
175
+ sqliteCode?: SQLiteResultCode;
176
+ /**
177
+ * SQLite's extended result code, read in the worker where the statement
178
+ * failed (spec 2026-09-14, §5.1). Sent by the query path only, and
179
+ * unfiltered: this is exactly what SQLite reported, including a value
180
+ * equal to `sqliteCode` (no subtype). The client is what drops it in
181
+ * that case (D9); when SQLite does report a subtype,
182
+ * `(sqliteExtendedCode & 0xff) === sqliteCode`.
183
+ */
184
+ sqliteExtendedCode?: number;
185
+ /**
186
+ * A code this library minted, when the worker knows the cause. The
187
+ * generic path by which a worker-side error keeps its code across the
188
+ * boundary — `worker.ts` copies it off any thrown error carrying one, so
189
+ * this is a structural contract and not a hook for one class.
190
+ *
191
+ * **Nothing sets it today.** `WorkerQueryTimeout` was its only producer
192
+ * and it went with the execution budget when `timeout` became a
193
+ * client-side wall-clock deadline. Kept rather than deleted: it is the
194
+ * twin of `sqliteCode` above, which is load-bearing, and rebuilding it
195
+ * would cost the same three sites it occupies.
196
+ */
197
+ errorCode?: SQLiteErrorCode;
198
+ /**
199
+ * Whether the connection is inside a transaction once this query has
200
+ * ended — `sqlite3_get_autocommit() === 0`. SQLite can leave a
201
+ * transaction by itself: an interrupted INSERT/UPDATE/DELETE rolls the
202
+ * whole transaction back. Absent when the worker could not read it.
203
+ */
204
+ inTransaction?: boolean | undefined;
110
205
  } | {
111
206
  type: 'closed';
112
207
  callId: number;
113
208
  } | {
114
209
  type: 'deleted';
115
210
  callId: number;
211
+ }
212
+ /** The delete worker found nothing at that name; deleteDatabase turns it into DATABASE_NOT_FOUND. */
213
+ | {
214
+ type: 'not-found';
116
215
  } | {
117
216
  type: 'open-error';
118
217
  callId: number;
119
218
  message: string;
120
219
  cause?: unknown;
121
220
  /** SQLite's numeric result code, when the failure came from SQLite. */
122
- sqliteCode?: number;
221
+ sqliteCode?: SQLiteResultCode;
123
222
  };
124
223
  /** Which wa-sqlite WebAssembly build a worker loads. */
125
224
  export type SQLiteBuild = 'sync' | 'async' | 'jspi';
@@ -139,13 +238,28 @@ export declare const BUILD_REQUIREMENTS: {
139
238
  readonly async: readonly [];
140
239
  readonly jspi: readonly ["jspi"];
141
240
  };
241
+ /**
242
+ * Platform features a build USES when present and works without, at a cost —
243
+ * the symmetric of `degradesWithout` on a VFS, at the level where this one
244
+ * actually lives. The `sync` build cannot carry an abort into a running
245
+ * `step()` without a `SharedArrayBuffer`, and there is no SharedArrayBuffer
246
+ * outside a cross-origin isolated context: measured 2026-09-04, it is not
247
+ * restricted there, it is absent. Nothing here names COOP/COEP or
248
+ * Document-Isolation-Policy: any of them satisfies the probe, and one of them
249
+ * is Chrome-only.
250
+ */
251
+ export declare const BUILD_DEGRADES_WITHOUT: {
252
+ readonly sync: readonly ["cross-origin-isolated"];
253
+ readonly async: readonly [];
254
+ readonly jspi: readonly [];
255
+ };
142
256
  /**
143
257
  * A platform feature a VFS may need. Which browser versions ship each one is
144
- * documentation data, not runtime data, so it lives in the README generator
258
+ * documentation data, not runtime data, so it lives in the VFS.md generator
145
259
  * (`scripts/render-vfs-matrix.ts`) with its sources — not here, where it would
146
260
  * ship to every consumer for nothing.
147
261
  */
148
- export type PlatformFeature = 'opfs' | 'readwrite-unsafe' | 'jspi' | 'writable-stream';
262
+ export type PlatformFeature = 'opfs' | 'readwrite-unsafe' | 'jspi' | 'writable-stream' | 'cross-origin-isolated';
149
263
  /** Where a VFS keeps the database. */
150
264
  export type VFSStorage = 'opfs' | 'indexeddb' | 'memory';
151
265
  /**
@@ -184,14 +298,40 @@ export type VFSCapability = {
184
298
  readonly storage: VFSStorage;
185
299
  /** How the database is arranged within that storage. */
186
300
  readonly layout: VFSLayout;
301
+ /**
302
+ * Whether this VFS takes its OPFS access handle in the EXCLUSIVE mode —
303
+ * `createSyncAccessHandle()` with no `mode`, rather than
304
+ * `mode: 'readwrite-unsafe'`.
305
+ *
306
+ * It decides whether an acquisition has to be retried. A terminated context
307
+ * releases its Web Locks at once but keeps its OPFS access handles for up to
308
+ * ~2 s on Chromium (HANDLE-CORPSE, `mem:measurements`), so a VFS taking an
309
+ * exclusive handle can meet a file held by something that answers nothing:
310
+ * no lock to wait on, no owner to ask, only time to wait out. Where
311
+ * `readwrite-unsafe` is used a second handle is granted regardless, and a
312
+ * dead holder blocks nobody.
313
+ *
314
+ * Declared rather than detected, because the error is not available where the
315
+ * decision has to be made: wa-sqlite's `jOpen` swallows it (measured
316
+ * 2026-09-18) and SQLite reports a bare `SQLITE_CANTOPEN`. Where the error IS
317
+ * available — VFS instantiation — `createVfsInstance` tests it directly and
318
+ * needs no declaration.
319
+ *
320
+ * NOT declared for `OPFSAdaptiveVFS` and `OPFSWriteAheadVFS`, which ask for
321
+ * `readwrite-unsafe` and fall back to an exclusive handle only on an engine
322
+ * that lacks it. That combination is real but out of reach: Firefox releases
323
+ * a dead worker's handle in 1-6 ms (HANDLE-ORPHAN), a window nothing loses.
324
+ */
325
+ readonly exclusiveFileHandle: boolean;
187
326
  /**
188
327
  * Platform features without which this VFS cannot work at all.
189
328
  *
190
329
  * `readwrite-unsafe` is the one that bites: WebIDL ignores the unknown
191
330
  * dictionary member on engines that do not implement it, so the handle
192
- * silently opens exclusive and the second connection hangs rather than
193
- * failing. Declaring it is what lets the conformance suite probe for it and
194
- * skip, instead of leaving it to surface as a 60-second timeout.
331
+ * silently opens exclusive, and a second connection then waits or fails
332
+ * depending on the VFS see `degradesWithout` and `singleConnectionWithout`.
333
+ * Declaring it is what lets the conformance suite probe for it and skip,
334
+ * instead of leaving it to surface as a 60-second timeout.
195
335
  */
196
336
  readonly requires: readonly PlatformFeature[];
197
337
  /**
@@ -199,20 +339,119 @@ export type VFSCapability = {
199
339
  *
200
340
  * `OPFSAdaptiveVFS` is the case this field exists for. Without
201
341
  * `readwrite-unsafe` it rotates a single exclusive access handle between
202
- * connections instead of holding one each. That works — 102 of 104 browser
203
- * tests pass on Firefox — but it serializes the whole pool for the duration
204
- * of a long uninterruptible statement.
342
+ * connections instead of holding one each. That works — Firefox is the engine
343
+ * the browser suite exercises it on — but a connection in a long
344
+ * uninterruptible statement holds the handle, and every other connection to
345
+ * the database, in another client or tab, waits for it. Within one client it
346
+ * runs a single worker there: see `singleConnectionWithout`.
205
347
  *
206
348
  * Without this distinction, a support table derived from browser specs would
207
349
  * mark that VFS broken everywhere outside Chromium, when it merely degrades.
208
350
  */
209
351
  readonly degradesWithout: readonly PlatformFeature[];
352
+ /**
353
+ * Platform features without which a pool of more than one worker buys this
354
+ * VFS nothing, so it runs on one (spec 2026-09-13, §3 and §10). Either the
355
+ * VFS holds its database file exclusively for a connection's whole life and
356
+ * a second worker cannot open at all (`OPFSWriteAheadVFS`), or it rotates one
357
+ * exclusive access handle between connections and a second worker only waits
358
+ * its turn (`OPFSAdaptiveVFS` — measured 2026-09-14 on Firefox: a pool of one
359
+ * was faster at startup and on bursts of reads, and equal everywhere else).
360
+ * The pool's surplus workers probe the feature before loading anything and
361
+ * decline (`src/worker/probes.ts`); every feature listed needs a probe there.
362
+ */
363
+ readonly singleConnectionWithout: readonly PlatformFeature[];
364
+ /**
365
+ * Files this VFS keeps beside the database, by suffix, beyond the three every
366
+ * layout may have (`''`, `-journal`, `-wal`). `deleteDatabase` removes them
367
+ * with the rest; a file missing from this list outlives its database.
368
+ *
369
+ * `OPFSWriteAheadVFS` keeps its write-ahead log in two files of its own,
370
+ * `-wa0` and `-wa1` (wa-sqlite's `#getWriteAheadNameFromDbName`) — measured
371
+ * left behind by every deletion until 2026-09-14.
372
+ */
373
+ readonly extraFileSuffixes: readonly string[];
374
+ /**
375
+ * Whether every statement on this VFS must hand its worker back to the event
376
+ * loop while it runs, abortable or not.
377
+ *
378
+ * `IDBBatchAtomicVFS` is why it exists. Its `jLock` opens a readwrite
379
+ * IndexedDB transaction on reaching SHARED, and IndexedDB commits a
380
+ * transaction only once its thread returns to the event loop. A worker inside
381
+ * one long statement never did, so every other connection's read queued
382
+ * behind it until the statement ended — measured 2026-09-14 on both engines
383
+ * (`mem:measurements`, IDB-SIGNAL). The worker then runs its progress handler
384
+ * on every statement, a task turn every `PROGRESS_OPS` VM ops, which measured
385
+ * no cost. The `sync` build cannot yield and ignores it.
386
+ */
387
+ readonly yieldsDuringStatements: boolean;
388
+ /**
389
+ * PRAGMAs this library applies on open for this VFS.
390
+ *
391
+ * Merged UNDER the consumer's `pragmas`, so any key they set wins and they
392
+ * never lose a default by setting an unrelated one — `foreign_keys` is the
393
+ * common case, and replacing rather than merging would silently disable
394
+ * everything below it.
395
+ *
396
+ * The bar is deliberately high, and almost nothing clears it: **more
397
+ * performance without less reliability, sourced rather than guessed.** Three
398
+ * things were weighed and rejected. `journal_mode=wal` universally, because
399
+ * no VFS here implements `xShmMap` and upstream gives write-ahead logging to
400
+ * `OPFSWriteAheadVFS` alone, inside the VFS and unreachable by pragma.
401
+ * `synchronous=normal`, because relaxing durability spends the consumer's
402
+ * data, not their milliseconds. And `cache_size`, because raising it changes
403
+ * a mode without a measurable gain — Firefox showed none at all, and the
404
+ * heap it can then reach is never given back (measured 2026-09-02).
405
+ */
406
+ readonly defaultPragmas: Readonly<Record<string, string>>;
407
+ /**
408
+ * Whether this VFS enforces an origin-wide exclusive connection lock for the
409
+ * client's lifetime.
410
+ *
411
+ * When `true`, `createSQLiteClient` acquires a `bsq:conn:…` Web Lock on first
412
+ * use. A second client that attempts to open the same database receives `DATABASE_IN_USE`
413
+ * immediately on its first query instead of silently reading a frozen, broken
414
+ * view. This field is the only thing standing between a consumer and an
415
+ * unfalsifiable silent failure — `SELECT 1` and even
416
+ * `SELECT count(*) FROM sqlite_master` pass on a broken second client.
417
+ *
418
+ * `true` only for `AccessHandlePoolVFS`, whose OPFS access-handle pool is not
419
+ * sharable across connections (measured AHP-2TAB, 2026-09-01).
420
+ * `false` for `IDBMirrorVFS` — despite `multiConnection: false` — because two
421
+ * clients on that VFS DO share data over its origin-wide `BroadcastChannel`
422
+ * (measured 2026-09-01, 3/3 both engines). `multiConnection: false` there marks
423
+ * concurrent-writer unsafety, not isolation.
424
+ * `false` for the memory VFS, which are isolated by construction and have
425
+ * nothing to exclude.
426
+ *
427
+ * `VFS_CAPABILITIES` is the single source of truth the client guard, the
428
+ * conformance suite, the VFS.md generator and the benchmark page all read.
429
+ * The gate is by this declaration, not by VFS name.
430
+ */
431
+ readonly exclusiveConnection: boolean;
432
+ /**
433
+ * Platform features without which this VFS holds its database file
434
+ * exclusively for a connection's whole life, across the origin — so the
435
+ * client takes `bsq:conn` exclusively, as for `exclusiveConnection`, and a
436
+ * second client gets `DATABASE_IN_USE` (spec 2026-09-15).
437
+ *
438
+ * `OPFSWriteAheadVFS` without `readwrite-unsafe`: upstream's VFS requires the
439
+ * mode and keeps its three access handles for the connection's life, so
440
+ * nothing else can open the file — every query of a second client failed
441
+ * with WORKER_CRASHED on Firefox, 20/20 per shape (2026-09-15).
442
+ *
443
+ * The page cannot probe these features, so worker 0 probes them before
444
+ * opening (`src/worker/probes.ts`): every feature listed needs a probe there,
445
+ * and must also be in `singleConnectionWithout`, whose surplus workers
446
+ * decline before they touch the file.
447
+ */
448
+ readonly exclusiveConnectionWithout: readonly PlatformFeature[];
210
449
  };
211
450
  /**
212
451
  * The single source of truth for VFS selection. `SQLiteVFS` is derived from its
213
452
  * keys, `worker/worker.ts` must supply a loader for every key, the guards in
214
453
  * `client.ts` read it, the conformance suite gates its scenarios on it, and the
215
- * README table is generated from it. Nothing may hold a second copy.
454
+ * VFS.md table is generated from it. Nothing may hold a second copy.
216
455
  *
217
456
  * Build order is a decision per VFS, not a rule: `sync` is both the fastest and
218
457
  * the most portable build, so it leads wherever supported; `OPFSAdaptiveVFS`
@@ -222,8 +461,8 @@ export type VFSCapability = {
222
461
  * wa-sqlite v1.1.2, never copied from upstream's table.
223
462
  */
224
463
  export declare const VFS_CAPABILITIES: {
225
- readonly OPFSAdaptiveVFS: {
226
- readonly builds: readonly ["async", "jspi"];
464
+ readonly OPFSWriteAheadVFS: {
465
+ readonly builds: readonly ["sync", "async", "jspi"];
227
466
  readonly maxPoolSize: null;
228
467
  readonly poolLimitReason: null;
229
468
  readonly multiConnection: true;
@@ -231,11 +470,18 @@ export declare const VFS_CAPABILITIES: {
231
470
  readonly memoryModel: 'page-cache';
232
471
  readonly storage: 'opfs';
233
472
  readonly layout: 'opfs-path';
473
+ readonly exclusiveFileHandle: false;
234
474
  readonly requires: readonly ["opfs"];
235
475
  readonly degradesWithout: readonly ["readwrite-unsafe"];
476
+ readonly singleConnectionWithout: readonly ["readwrite-unsafe"];
477
+ readonly extraFileSuffixes: readonly ["-wa0", "-wa1"];
478
+ readonly yieldsDuringStatements: false;
479
+ readonly exclusiveConnection: false;
480
+ readonly exclusiveConnectionWithout: readonly ["readwrite-unsafe"];
481
+ readonly defaultPragmas: {};
236
482
  };
237
- readonly OPFSWriteAheadVFS: {
238
- readonly builds: readonly ["sync", "async", "jspi"];
483
+ readonly OPFSAdaptiveVFS: {
484
+ readonly builds: readonly ["async", "jspi"];
239
485
  readonly maxPoolSize: null;
240
486
  readonly poolLimitReason: null;
241
487
  readonly multiConnection: true;
@@ -243,20 +489,34 @@ export declare const VFS_CAPABILITIES: {
243
489
  readonly memoryModel: 'page-cache';
244
490
  readonly storage: 'opfs';
245
491
  readonly layout: 'opfs-path';
492
+ readonly exclusiveFileHandle: false;
246
493
  readonly requires: readonly ["opfs"];
247
494
  readonly degradesWithout: readonly ["readwrite-unsafe"];
495
+ readonly singleConnectionWithout: readonly ["readwrite-unsafe"];
496
+ readonly extraFileSuffixes: readonly [];
497
+ readonly yieldsDuringStatements: false;
498
+ readonly exclusiveConnection: false;
499
+ readonly exclusiveConnectionWithout: readonly [];
500
+ readonly defaultPragmas: {};
248
501
  };
249
502
  readonly OPFSCoopSyncVFS: {
250
503
  readonly builds: readonly ["sync", "async", "jspi"];
251
- readonly maxPoolSize: null;
252
- readonly poolLimitReason: null;
504
+ readonly maxPoolSize: 1;
505
+ readonly poolLimitReason: 'it rotates one exclusive access handle between connections, so another worker only waits its turn';
253
506
  readonly multiConnection: true;
254
507
  readonly persistent: true;
255
508
  readonly memoryModel: 'page-cache';
256
509
  readonly storage: 'opfs';
257
510
  readonly layout: 'opfs-path';
511
+ readonly exclusiveFileHandle: true;
258
512
  readonly requires: readonly ["opfs"];
259
513
  readonly degradesWithout: readonly [];
514
+ readonly singleConnectionWithout: readonly [];
515
+ readonly extraFileSuffixes: readonly [];
516
+ readonly yieldsDuringStatements: false;
517
+ readonly exclusiveConnection: false;
518
+ readonly exclusiveConnectionWithout: readonly [];
519
+ readonly defaultPragmas: {};
260
520
  };
261
521
  readonly AccessHandlePoolVFS: {
262
522
  readonly builds: readonly ["sync", "async", "jspi"];
@@ -267,8 +527,18 @@ export declare const VFS_CAPABILITIES: {
267
527
  readonly memoryModel: 'page-cache';
268
528
  readonly storage: 'opfs';
269
529
  readonly layout: 'opfs-pool';
530
+ readonly exclusiveFileHandle: true;
270
531
  readonly requires: readonly ["opfs"];
271
532
  readonly degradesWithout: readonly [];
533
+ readonly singleConnectionWithout: readonly [];
534
+ readonly extraFileSuffixes: readonly [];
535
+ readonly yieldsDuringStatements: false;
536
+ readonly exclusiveConnection: true;
537
+ readonly exclusiveConnectionWithout: readonly [];
538
+ readonly defaultPragmas: {
539
+ readonly locking_mode: 'exclusive';
540
+ readonly journal_mode: 'wal';
541
+ };
272
542
  };
273
543
  readonly IDBBatchAtomicVFS: {
274
544
  readonly builds: readonly ["async", "jspi"];
@@ -279,8 +549,15 @@ export declare const VFS_CAPABILITIES: {
279
549
  readonly memoryModel: 'page-cache';
280
550
  readonly storage: 'indexeddb';
281
551
  readonly layout: 'idb-store';
552
+ readonly exclusiveFileHandle: false;
282
553
  readonly requires: readonly [];
283
554
  readonly degradesWithout: readonly [];
555
+ readonly singleConnectionWithout: readonly [];
556
+ readonly extraFileSuffixes: readonly [];
557
+ readonly yieldsDuringStatements: true;
558
+ readonly exclusiveConnection: false;
559
+ readonly exclusiveConnectionWithout: readonly [];
560
+ readonly defaultPragmas: {};
284
561
  };
285
562
  readonly IDBMirrorVFS: {
286
563
  readonly builds: readonly ["async", "jspi"];
@@ -291,8 +568,15 @@ export declare const VFS_CAPABILITIES: {
291
568
  readonly memoryModel: 'whole-database';
292
569
  readonly storage: 'indexeddb';
293
570
  readonly layout: 'idb-store';
571
+ readonly exclusiveFileHandle: false;
294
572
  readonly requires: readonly [];
295
573
  readonly degradesWithout: readonly [];
574
+ readonly singleConnectionWithout: readonly [];
575
+ readonly extraFileSuffixes: readonly [];
576
+ readonly yieldsDuringStatements: false;
577
+ readonly exclusiveConnection: false;
578
+ readonly exclusiveConnectionWithout: readonly [];
579
+ readonly defaultPragmas: {};
296
580
  };
297
581
  readonly OPFSAnyContextVFS: {
298
582
  readonly builds: readonly ["async", "jspi"];
@@ -303,8 +587,15 @@ export declare const VFS_CAPABILITIES: {
303
587
  readonly memoryModel: 'page-cache';
304
588
  readonly storage: 'opfs';
305
589
  readonly layout: 'opfs-path';
590
+ readonly exclusiveFileHandle: false;
306
591
  readonly requires: readonly ["opfs", "writable-stream"];
307
592
  readonly degradesWithout: readonly [];
593
+ readonly singleConnectionWithout: readonly [];
594
+ readonly extraFileSuffixes: readonly [];
595
+ readonly yieldsDuringStatements: false;
596
+ readonly exclusiveConnection: false;
597
+ readonly exclusiveConnectionWithout: readonly [];
598
+ readonly defaultPragmas: {};
308
599
  };
309
600
  readonly MemoryVFS: {
310
601
  readonly builds: readonly ["sync", "async", "jspi"];
@@ -315,8 +606,15 @@ export declare const VFS_CAPABILITIES: {
315
606
  readonly memoryModel: 'whole-database';
316
607
  readonly storage: 'memory';
317
608
  readonly layout: 'memory';
609
+ readonly exclusiveFileHandle: false;
318
610
  readonly requires: readonly [];
319
611
  readonly degradesWithout: readonly [];
612
+ readonly singleConnectionWithout: readonly [];
613
+ readonly extraFileSuffixes: readonly [];
614
+ readonly yieldsDuringStatements: false;
615
+ readonly exclusiveConnection: false;
616
+ readonly exclusiveConnectionWithout: readonly [];
617
+ readonly defaultPragmas: {};
320
618
  };
321
619
  readonly MemoryAsyncVFS: {
322
620
  readonly builds: readonly ["async", "jspi"];
@@ -327,22 +625,18 @@ export declare const VFS_CAPABILITIES: {
327
625
  readonly memoryModel: 'whole-database';
328
626
  readonly storage: 'memory';
329
627
  readonly layout: 'memory';
628
+ readonly exclusiveFileHandle: false;
330
629
  readonly requires: readonly [];
331
630
  readonly degradesWithout: readonly [];
631
+ readonly singleConnectionWithout: readonly [];
632
+ readonly extraFileSuffixes: readonly [];
633
+ readonly yieldsDuringStatements: false;
634
+ readonly exclusiveConnection: false;
635
+ readonly exclusiveConnectionWithout: readonly [];
636
+ readonly defaultPragmas: {};
332
637
  };
333
638
  };
334
639
  export type SQLiteVFS = keyof typeof VFS_CAPABILITIES;
335
640
  /** The build used when the caller does not name one. */
336
641
  export declare const defaultBuildFor: (vfs: SQLiteVFS) => SQLiteBuild;
337
- /**
338
- * The VFS this project recommends when a caller has no reason to choose
339
- * another. It is NOT a default — `vfs` is required, precisely so that the name
340
- * lives in the consumer's own source and cannot move underneath their data.
341
- *
342
- * It lives here, beside the table, because the README generator marks this row
343
- * `(recommended)` and would otherwise hold a second copy. It is deliberately
344
- * not exported: a consumer writing `vfs: RECOMMENDED_VFS` would be exposed to
345
- * the same displacement the day the recommendation changes.
346
- */
347
- export declare const RECOMMENDED_VFS: SQLiteVFS;
348
642
  export {};
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { SQLiteBuild, WasmLocation } from './types';
1
+ import { type SQLiteBuild, type SQLiteVFS, type WasmLocation } from './types';
2
2
  export declare const sqlParams: () => {
3
3
  addParam: (v: unknown) => string;
4
4
  addParamArray: (values: unknown[]) => string;
@@ -6,13 +6,20 @@ export declare const sqlParams: () => {
6
6
  };
7
7
  export declare const isReadQuery: (sql: string) => boolean;
8
8
  export declare const isWriteQuery: (sql: string) => boolean;
9
+ /**
10
+ * Whether `sql` manages the transaction or its savepoints rather than data
11
+ * (spec 2026-09-11, D8). Never wrapped in the library's savepoint: there is
12
+ * nothing to undo, and a `RELEASE u` run inside it would pop it along with
13
+ * `u`. The leading keyword decides: these statements are never compound.
14
+ */
15
+ export declare const isTransactionControl: (sql: string) => boolean;
9
16
  /**
10
17
  * Combines two abort signals into one that fires with the reason of whichever
11
18
  * source aborted first, plus the `release()` that unsubscribes it.
12
19
  *
13
20
  * NOT `AbortSignal.any()`. That is Chrome 116 / Firefox 124 / Safari 17.4, far
14
21
  * above this library's floor (Chrome 92 / Firefox 95 / Safari 15.4), and
15
- * adopting it would raise every row of the generated README matrix for every
22
+ * adopting it would raise every row of the generated VFS.md matrix for every
16
23
  * consumer.
17
24
  *
18
25
  * The common case allocates nothing: with one side absent, or one side already
@@ -23,6 +30,26 @@ export declare const mergeSignals: (a: AbortSignal | undefined, b: AbortSignal |
23
30
  signal: AbortSignal | undefined;
24
31
  release: () => void;
25
32
  };
33
+ /**
34
+ * The `timeout` option: a wall-clock budget in milliseconds, counted from the
35
+ * call. Returns the signal the call should actually use — the caller's own,
36
+ * merged with one this library owns and aborts when the budget is spent.
37
+ *
38
+ * The abort reason IS the error the caller receives. Every abort path in this
39
+ * library rejects with `signal.reason`, and `mergeSignals` relays a reason
40
+ * verbatim, so nothing downstream has to ask which signal fired. That is why
41
+ * this is an AbortController and a setTimeout rather than
42
+ * `AbortSignal.timeout()`, which offers no way to set the reason.
43
+ *
44
+ * `release()` is owed exactly once, however the call ends.
45
+ */
46
+ export declare const withDeadline: (options: {
47
+ signal?: AbortSignal | undefined;
48
+ timeout?: number | undefined;
49
+ } | undefined, method: string) => {
50
+ signal: AbortSignal | undefined;
51
+ release: () => void;
52
+ };
26
53
  /**
27
54
  * Routing guard for the read-shaped methods (`read`, `chunk`, `stream`, `first`).
28
55
  * Throws before a lease is taken, so a rejected statement costs no pool capacity.
@@ -68,6 +95,19 @@ export declare const assertGeneratedExpression: (expr: string, column: string) =
68
95
  * `createSQLiteClient()` rather than inside an unrelated query, and by the
69
96
  * worker at open, which is the only place the statements actually run.
70
97
  */
98
+ /**
99
+ * The PRAGMAs a client actually applies: the VFS's declared defaults with the
100
+ * consumer's own layered over them.
101
+ *
102
+ * **Merged, never replaced.** A consumer who passes one pragma is answering a
103
+ * question of their own — `foreign_keys` is the usual one — not declining the
104
+ * VFS's defaults, and replacing would silently drop every default the moment
105
+ * they set anything at all. A key they DO set always wins, which is how a
106
+ * default is refused: pass `journal_mode` yourself and yours is what runs.
107
+ *
108
+ * Order matters and is the whole function: spread the defaults first.
109
+ */
110
+ export declare const resolvePragmas: (vfs: SQLiteVFS, pragmas: Record<string, string> | undefined) => Record<string, string>;
71
111
  export declare const renderPragmas: (pragmas: Record<string, string>) => string[];
72
112
  /**
73
113
  * The single definition of database identity — one string used everywhere: