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.
package/dist/api.d.ts CHANGED
@@ -10,23 +10,25 @@
10
10
  * caller passes to a query, and what comes back.
11
11
  */
12
12
  import type { ClientDebugState } from './debug';
13
+ import type { ClientInspection } from './inspect';
14
+ import type { SQLiteBuild, SQLiteVFS } from './types';
13
15
  /**
14
16
  * Marks an options type as carrying an abort signal.
15
17
  *
16
- * The name is the point. `options?: OptionsWithSignal<…>` says at the signature
18
+ * The name is the point. `options?: Interruptible<…>` says at the signature
17
19
  * that the method can be abandoned, where a bare alias would make a reader open
18
20
  * the type to find out. Every abortable option type in this file is built from
19
21
  * it, so `signal` is documented once and cannot drift between them.
20
22
  *
21
23
  * Not the bare `Abortable` that `@types/node` uses: this reads as an options
22
24
  * bag augmented with one member — `PropsWithChildren`, not an adjective — which
23
- * is what it is both wrapped, `OptionsWithSignal<{ chunkSize?: number }>`, and
24
- * alone, `options?: OptionsWithSignal`.
25
+ * is what it is both wrapped, `Interruptible<{ chunkSize?: number }>`, and
26
+ * alone, `options?: Interruptible`.
25
27
  *
26
28
  * `T = unknown` rather than `Record<string, never>`: intersecting with the
27
29
  * latter collapses `signal` to `never` and makes it unassignable.
28
30
  */
29
- export type OptionsWithSignal<T = unknown> = T & {
31
+ export type Interruptible<T = unknown> = T & {
30
32
  /**
31
33
  * Aborts the work. Rejects with `signal.reason` — your reason, not an error
32
34
  * of this library's making.
@@ -37,11 +39,26 @@ export type OptionsWithSignal<T = unknown> = T & {
37
39
  * `bulkWrite()` leaves the batches already written in place; an aborted
38
40
  * `output()` is observationally a no-op, dropping its staging table and
39
41
  * touching nothing else.
42
+ *
43
+ * Whether it also stops the statement SQLite is already executing depends on
44
+ * your build and your page: see the Interrupting a call section of API.md.
40
45
  */
41
46
  signal?: AbortSignal | undefined;
47
+ /**
48
+ * Milliseconds from the call within which it must finish, after which it is
49
+ * aborted and rejected with `OPERATION_TIMEOUT`. It is wall clock: time your
50
+ * own code spends — between two chunks of a `stream()`, inside a
51
+ * `transaction()` callback, between two `enqueue()` calls — counts against
52
+ * it, as does time spent waiting for a pool worker or for another tab's
53
+ * write lock.
54
+ *
55
+ * It aborts through the same path a `signal` does, so the same limit applies:
56
+ * see the Interrupting a call section of API.md.
57
+ */
58
+ timeout?: number | undefined;
42
59
  };
43
60
  /** Options every query method accepts. */
44
- export type SQLiteQueryOptions = OptionsWithSignal;
61
+ export type SQLiteQueryOptions = Interruptible;
45
62
  /**
46
63
  * Options for the methods that cross the worker boundary in chunks.
47
64
  *
@@ -50,7 +67,7 @@ export type SQLiteQueryOptions = OptionsWithSignal;
50
67
  * ahead of the consumer. On `stream()` that is the only lever on how many rows
51
68
  * are in flight.
52
69
  */
53
- export type SQLiteChunkOptions = OptionsWithSignal<{
70
+ export type SQLiteChunkOptions = Interruptible<{
54
71
  /** Rows per chunk. Defaults to 500. */
55
72
  chunkSize?: number;
56
73
  }>;
@@ -80,9 +97,9 @@ export type SQLiteWriteResult<T extends Record<string, unknown>> = {
80
97
  * That window is short on a VFS holding one access handle per connection, and
81
98
  * it is not on a VFS rotating a single exclusive one: there such a statement
82
99
  * waits for whichever client holds the file, and your signal cannot shorten
83
- * that wait. See the reduced mode described under VFS Selection.
100
+ * that wait. See the reduced mode described in VFS.md.
84
101
  */
85
- export type SQLiteTransactionOptions = OptionsWithSignal<{
102
+ export type SQLiteTransactionOptions = Interruptible<{
86
103
  /** Rejects write statements with `READ_ONLY_TRANSACTION`. Defaults to false. */
87
104
  readOnly?: boolean;
88
105
  /** Commits when the callback resolves. Defaults to true. */
@@ -102,7 +119,7 @@ export type Index<SCHEMA extends Schema> = keyof SCHEMA | (keyof SCHEMA)[] | ({
102
119
  } | {
103
120
  columns: (keyof SCHEMA)[];
104
121
  }));
105
- export type SQLiteOutputOptions<SCHEMA extends Schema> = OptionsWithSignal<{
122
+ export type SQLiteOutputOptions<SCHEMA extends Schema> = Interruptible<{
106
123
  indexes?: Index<SCHEMA>[];
107
124
  /** Rows queued for writing above which `enqueue()` defers. See `SQLiteBulkWriteOptions`. */
108
125
  queueSize?: number | undefined;
@@ -126,7 +143,7 @@ export type SQLiteOutputOptions<SCHEMA extends Schema> = OptionsWithSignal<{
126
143
  * 1 is raised to 1: a batch always holds at least one row, so a lower cap could
127
144
  * never be satisfied.
128
145
  */
129
- export type SQLiteBulkWriteOptions = OptionsWithSignal<{
146
+ export type SQLiteBulkWriteOptions = Interruptible<{
130
147
  /** Rows queued for writing above which `enqueue()` defers. */
131
148
  queueSize?: number | undefined;
132
149
  }>;
@@ -200,7 +217,7 @@ export type SQLiteQueryAPI = {
200
217
  * @returns Promise resolving to `{ result: T[], affected: number }` where
201
218
  * `affected` is the SQLite `changes()` count for the statement.
202
219
  */
203
- write: <T extends Record<string, unknown>>(sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<SQLiteWriteResult<T>>;
220
+ write: <T extends Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteQueryOptions) => Promise<SQLiteWriteResult<T>>;
204
221
  /**
205
222
  * Executes a query and yields result rows in chunks via an async generator.
206
223
  * Memory-efficient for large result sets — rows are not buffered in full.
@@ -253,7 +270,7 @@ export type SQLiteQueryAPI = {
253
270
  * @param options - Optional query options (`signal`).
254
271
  * @returns Promise resolving to the first row as `T`, or `undefined` if no rows.
255
272
  */
256
- first: <T extends Record<string, unknown>>(sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<T | undefined>;
273
+ first: <T extends Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteQueryOptions) => Promise<T | undefined>;
257
274
  /**
258
275
  * Creates a buffered bulk-insert utility that batches rows to stay within
259
276
  * SQLite's variable limit (`SQLITE_MAX_VARS = 32766`).
@@ -362,6 +379,31 @@ export type SQLiteDB = SQLiteQueryAPI & {
362
379
  * check what your chosen VFS actually writes.
363
380
  */
364
381
  close: () => Promise<void>;
382
+ /** This client's UUID, unique across the origin. */
383
+ readonly id: string;
384
+ /** This client's label, index included — what its log lines are prefixed with. */
385
+ readonly name: string;
386
+ /** The database file, normalized: the identity every lock name is built on. */
387
+ readonly file: string;
388
+ readonly vfs: SQLiteVFS;
389
+ /** The build actually loaded, resolved by `defaultBuildFor` when not passed. */
390
+ readonly build: SQLiteBuild;
391
+ /**
392
+ * The number of workers the pool runs: `poolSize` as requested, capped by
393
+ * the VFS and by the environment. Exact once every worker has opened or
394
+ * declined; every query waits for that, so it is settled by the time any
395
+ * query returns.
396
+ */
397
+ readonly poolSize: number;
398
+ /**
399
+ * Who else is live on this database, right now, in every tab of this origin.
400
+ *
401
+ * A snapshot, stale the instant it resolves: it informs a UI and never
402
+ * authorizes an action. Poll it if you want it to move — each call is a fresh
403
+ * census costing well under a tenth of a millisecond, and it takes no lock,
404
+ * so it cannot slow a query down.
405
+ */
406
+ inspect: () => Promise<ClientInspection>;
365
407
  /**
366
408
  * Internal diagnostic handle. Not part of the stable public API.
367
409
  * Shape is subject to change without notice.
@@ -372,5 +414,13 @@ export type SQLiteDB = SQLiteQueryAPI & {
372
414
  export type SQLiteTransactionDB = SQLiteQueryAPI & {
373
415
  commit: () => Promise<void>;
374
416
  rollback: () => Promise<void>;
417
+ /**
418
+ * Aborted when this transaction fails or is abandoned — its own signal or
419
+ * timeout, close(), an abandoned write, or an error the callback lets
420
+ * escape — with the value `transaction()` rejects with. Never aborted when
421
+ * it succeeds. Hand it to work of your own the callback awaits, such as a
422
+ * `fetch`, so that work stops with the transaction.
423
+ */
424
+ readonly signal: AbortSignal;
375
425
  };
376
426
  export {};
package/dist/bulk.d.ts CHANGED
@@ -29,6 +29,21 @@ export declare const createBulk: (shared: {
29
29
  read: ReadFn;
30
30
  write: WriteFn;
31
31
  transaction: TransactionFn;
32
+ /**
33
+ * Takes this batch's place in the caller's statement queue, SYNCHRONOUSLY,
34
+ * at the moment `flush()` commits to it — from `close()` or from the
35
+ * `enqueue()` that filled the buffer. `started` is what was issued before
36
+ * it; `done()` gives the place back.
37
+ *
38
+ * Supplied by a transaction, where every statement shares one connection.
39
+ * The client path supplies none: there each statement takes its own lease.
40
+ * Without it the batch posts a microtask after `flush()` returns, so a
41
+ * statement issued AFTER it runs first — a stale read rather than an error.
42
+ */
43
+ reserve?: () => {
44
+ started: Promise<void>;
45
+ done: () => void;
46
+ };
32
47
  }) => {
33
48
  bulkWrite: <KEYS extends string>(table: string, keys: KEYS[], options?: SQLiteBulkWriteOptions, before?: Promise<unknown>) => {
34
49
  enqueue: (data: { [K in KEYS]: any; }) => Promise<void>;
package/dist/client.d.ts CHANGED
@@ -1,35 +1,50 @@
1
- import type { OptionsWithSignal, SQLiteChunkOptions } from './api';
1
+ import type { SQLiteChunkOptions, SQLiteQueryOptions } from './api';
2
2
  import { SQLiteError } from './errors';
3
+ import { type ClientInspection } from './inspect';
3
4
  import { type SQLiteBuild, type SQLiteVFS } from './types';
4
5
  /**
5
6
  * Configuration options for creating a SQLite client.
6
7
  */
7
8
  export type CreateSQLiteClientOptions = {
8
9
  /**
9
- * Database file name within the OPFS origin private file system.
10
- * Each unique name maps to a distinct SQLite database file.
11
- * @defaultValue `"SQLite"` prefix + auto-incremented client index
10
+ * A label for this client, never for the database the database file is
11
+ * the FIRST argument to `createSQLiteClient`, and this option has no effect
12
+ * on what is opened or where.
13
+ *
14
+ * It is reported as `db.debug.name`, and prefixes the `debug` logger's
15
+ * console output as `"<name> <n>"`, where `n` counts the clients created in
16
+ * this tab. Neither form is unique across the origin: the counter is
17
+ * per-tab, so two tabs both produce `"SQLite 1"`.
18
+ *
19
+ * @defaultValue `"SQLite"`
12
20
  */
13
21
  name?: string;
14
22
  /**
15
23
  * Number of Web Workers spawned in the pool at initialization.
16
24
  * A larger pool allows more concurrent read operations but increases
17
25
  * memory consumption and OPFS file handle usage.
18
- * Must be `1` when using `AccessHandlePoolVFS` any larger value throws at construction time.
19
- * @defaultValue `2`
26
+ * A VFS that holds a single connection, or gains nothing from a second one,
27
+ * caps this at `1` (`OPFSCoopSyncVFS` among them), and passing more throws at
28
+ * construction time. Omitting it never throws: the default is capped to what
29
+ * the VFS allows.
30
+ * The environment can cap it too: `OPFSWriteAheadVFS` and `OPFSAdaptiveVFS`
31
+ * run on one worker wherever `readwrite-unsafe` is missing, with no error, and
32
+ * warn once only when this option was passed. `db.poolSize` reports the size
33
+ * the pool runs at.
34
+ * @defaultValue `2`, or the VFS's maximum when it is lower
20
35
  */
21
36
  poolSize?: number;
22
37
  /**
23
38
  * Which VFS stores the database. Required: a VFS decides *where* the bytes
24
39
  * live, and a database written through one VFS is not visible through
25
- * another. See the README's VFS Selection guide.
40
+ * another. See Browser compatibility and recommendations in VFS.md.
26
41
  */
27
42
  vfs: SQLiteVFS;
28
43
  /**
29
44
  * Which wa-sqlite WebAssembly build to load. Defaults to the first entry of
30
45
  * `VFS_CAPABILITIES[vfs]` — `sync` where the VFS supports it, since it is both the
31
46
  * fastest and the most portable, otherwise `async`. `jspi` needs engine
32
- * support; see the README's Builds section for versions.
47
+ * support; see the Builds section of VFS.md for versions.
33
48
  *
34
49
  * @throws at construction when the build is not one the chosen VFS supports.
35
50
  */
@@ -83,8 +98,10 @@ export type CreateSQLiteClientOptions = {
83
98
  maxWorkerRestarts?: number;
84
99
  /**
85
100
  * Milliseconds a worker has to post `ready` after its `open` message is sent.
86
- * On expiry the slot is failed immediately the most common cause is a
87
- * database held under an exclusive lock by another tab or client.
101
+ * On expiry the slot is failed immediately, with a message naming the cause
102
+ * the client roster supports: another live client of this library holding
103
+ * the database, or a holder the roster cannot see — a page reloaded without
104
+ * `close()`, another library, native code.
88
105
  * @defaultValue `30_000`
89
106
  */
90
107
  openTimeout?: number;
@@ -97,15 +114,15 @@ export type CreateSQLiteClientOptions = {
97
114
  /**
98
115
  * Turns on the introspection subsystem exposed as `db.debug`, and the
99
116
  * lifecycle log. A string is used as the log prefix; `true` falls back to the
100
- * client prefix (`"<name> <index>"`), which already names the workers.
117
+ * client name (`"<name> <index>"`), which already names the workers.
101
118
  *
102
119
  * @defaultValue undefined — no collection, no output, `db.debug` undefined.
103
120
  */
104
121
  debug?: string | boolean;
105
122
  /**
106
123
  * Called whenever a worker slot is permanently lost. Receives the slot index,
107
- * the number of workers still alive after the loss, the requested pool size,
108
- * and the error that killed the slot.
124
+ * the number of workers still alive after the loss, the pool's size
125
+ * (`db.poolSize`), and the error that killed the slot.
109
126
  *
110
127
  * Guaranteed to be called **before** the client is failed when the last slot
111
128
  * is lost. Wrapped in try/catch — a throwing callback is reported through
@@ -125,7 +142,7 @@ export type WorkerLostEvent = {
125
142
  index: number;
126
143
  /** Number of workers still alive after this loss. */
127
144
  live: number;
128
- /** The requested pool size (`poolSize` option). */
145
+ /** The number of workers the pool runs `db.poolSize`, not the `poolSize` option: the two differ where the environment caps the pool. */
129
146
  size: number;
130
147
  /** The error that killed the worker. */
131
148
  cause: SQLiteError;
@@ -164,7 +181,6 @@ export type WorkerLostEvent = {
164
181
  * import { createSQLiteClient } from 'browser-sqlite';
165
182
  *
166
183
  * const db = createSQLiteClient('myapp.sqlite', {
167
- * poolSize: 3,
168
184
  * vfs: 'OPFSAdaptiveVFS',
169
185
  * pragmas: { journal_mode: 'WAL', synchronous: 'NORMAL' },
170
186
  * });
@@ -176,14 +192,14 @@ export type WorkerLostEvent = {
176
192
  * ```
177
193
  */
178
194
  export declare const createSQLiteClient: (file: string, clientOptions: CreateSQLiteClientOptions) => {
179
- chunk: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteChunkOptions) => AsyncGenerator<T[], void, any>;
180
- read: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<T[]>;
181
- write: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<{
195
+ chunk: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteChunkOptions) => AsyncGenerator<T[], void, unknown>;
196
+ read: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteChunkOptions) => Promise<T[]>;
197
+ write: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteQueryOptions) => Promise<{
182
198
  result: T[];
183
199
  affected: number;
184
200
  }>;
185
- stream: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteChunkOptions) => AsyncGenerator<T, void, any>;
186
- first: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<T | undefined>;
201
+ stream: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteChunkOptions) => AsyncGenerator<T, void, unknown>;
202
+ first: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: SQLiteQueryOptions) => Promise<T | undefined>;
187
203
  transaction: <T = void>(callback: (db: import("./api").SQLiteTransactionDB) => Promise<T>, options?: import("./api").SQLiteTransactionOptions) => Promise<T>;
188
204
  bulkWrite: <KEYS extends string>(table: string, keys: KEYS[], options?: import("./api").SQLiteBulkWriteOptions, before?: Promise<unknown>) => {
189
205
  enqueue: (data: { [K in KEYS]: any; }) => Promise<void>;
@@ -194,5 +210,12 @@ export declare const createSQLiteClient: (file: string, clientOptions: CreateSQL
194
210
  close: () => Promise<number>;
195
211
  };
196
212
  close: () => Promise<void>;
213
+ readonly id: `${string}-${string}-${string}-${string}-${string}`;
214
+ readonly name: string;
215
+ readonly file: string;
216
+ readonly vfs: "AccessHandlePoolVFS" | "IDBBatchAtomicVFS" | "IDBMirrorVFS" | "MemoryAsyncVFS" | "MemoryVFS" | "OPFSAdaptiveVFS" | "OPFSAnyContextVFS" | "OPFSCoopSyncVFS" | "OPFSWriteAheadVFS";
217
+ readonly build: SQLiteBuild;
218
+ readonly poolSize: number;
219
+ inspect: () => Promise<ClientInspection>;
197
220
  debug: import("./debug").ClientDebugState | undefined;
198
221
  };
package/dist/delete.d.ts CHANGED
@@ -24,9 +24,11 @@ export type DeleteDatabaseOptions = {
24
24
  /**
25
25
  * Deletes a database and the two siblings SQLite may leave beside it.
26
26
  *
27
- * Deleting a database that is not there is success — SQLite's own `xDelete`
28
- * behaves the same way, and a caller who wanted it gone has got what they
29
- * asked for.
27
+ * Deleting a database that is not there REJECTS with `DATABASE_NOT_FOUND`.
28
+ * This paragraph said the opposite until 2026-09-16 that absence was success,
29
+ * as SQLite's own `xDelete` treats it — but the probe in `deleteDatabaseFiles`
30
+ * has always reported absence, and `delete.test.ts` pins that with a falsifier.
31
+ * The doc was the stale half, and it ships in the published `.d.ts`.
30
32
  *
31
33
  * Nothing a VFS keeps for itself is touched: not the IndexedDB store, which is
32
34
  * shared by every database that VFS holds on this origin, and not the
@@ -35,8 +37,12 @@ export type DeleteDatabaseOptions = {
35
37
  *
36
38
  * @throws {SQLiteError} `INVALID_OPTION` when `vfs` is missing or the `build`
37
39
  * is not one the VFS supports — synchronously in spirit, as a rejection here.
38
- * @throws {SQLiteError} `BUSY` when the database is open or being opened, in
39
- * this tab or another. A connection already holding its handles cannot be
40
+ * @throws {SQLiteError} `DATABASE_IN_USE` when the database is open, in this
41
+ * tab or another. A connection already holding its handles cannot be
40
42
  * revoked from here; see the README's Known Limitations.
43
+ * @throws {SQLiteError} `BUSY` when the database is being opened or deleted
44
+ * elsewhere. Try again in a moment.
45
+ * @throws {SQLiteError} `DATABASE_NOT_FOUND` when there is no such database.
46
+ * A caller deleting speculatively should catch this one code.
41
47
  */
42
48
  export declare const deleteDatabase: (file: string, options: DeleteDatabaseOptions) => Promise<void>;
package/dist/epochs.d.ts CHANGED
@@ -12,6 +12,8 @@
12
12
  * The `v1` suffix separates incompatible shapes. Bump it ONLY if the shape
13
13
  * changes — bumping it per release recreates the fragmentation it prevents.
14
14
  */
15
+ import type { Locks } from './locks';
16
+ import type { SQLiteVFS } from './types';
15
17
  /**
16
18
  * The statement the barrier runs and discards.
17
19
  *
@@ -23,19 +25,47 @@
23
25
  * the refresh still returns the stale result.
24
26
  */
25
27
  export declare const BARRIER_SQL = "SELECT count(*) FROM sqlite_master";
28
+ /**
29
+ * The marker a realm holds to publish the epoch it last committed.
30
+ *
31
+ * Held in SHARED mode: many realms may hold one name at once, so publishing
32
+ * never waits and two realms can never collide on a number. Nobody reads the
33
+ * lock — the NAME is the state, which is why this beats a BroadcastChannel:
34
+ * there is no message that can still be in flight.
35
+ */
36
+ export declare const epochLockName: (ns: string, file: string, n: number) => string;
37
+ /**
38
+ * The highest epoch any realm in this origin has published under `prefix`.
39
+ *
40
+ * The tail after the prefix must be ALL digits, which is stricter than a
41
+ * prefix match plus `lastIndexOf(':')` and is the point: a normalized file may
42
+ * contain a colon (`new URL('./a:b', 'file://').pathname` is `a:b`), so the
43
+ * loose form would read another database's epoch as this one's.
44
+ */
45
+ export declare const maxEpochIn: (heldNames: string[], prefix: string) => number;
26
46
  export type Epochs = {
27
- /** The number of commits observed in this realm for this database. */
47
+ /** The number of commits observed for this database, floor included. */
28
48
  current: () => number;
29
49
  /** Records one commit and returns the new epoch. */
30
50
  bump: () => number;
51
+ /** Raises the local floor. Never lowers it. */
52
+ raiseTo: (n: number) => void;
53
+ /** The highest epoch published by any realm in this origin. */
54
+ originMax: () => Promise<number>;
55
+ /** Publishes `n` for this realm, replacing its previous marker. */
56
+ publish: (n: number) => Promise<void>;
31
57
  };
32
58
  /**
33
- * Handles onto the counter for `file`, which MUST already be normalized by
34
- * `normalizeDatabaseFile`. Entries are never removed: deleting one would
35
- * restart the counter at 0, and a worker still alive with `seen = 5` would
36
- * then read `5 > 0`, believe itself current forever, and serve stale data.
59
+ * Handles onto the counter for `(namespace, file)`, which MUST already be
60
+ * normalized by `normalizeDatabaseFile`. Entries are never removed: deleting
61
+ * one would restart the counter at 0, and a worker still alive with `seen = 5`
62
+ * would then read `5 > 0`, believe itself current forever, and serve stale
63
+ * data.
64
+ *
65
+ * The cell is realm-wide, so every client in a tab shares one counter AND one
66
+ * marker — publication is per realm, not per client.
37
67
  */
38
- export declare const epochsFor: (file: string) => Epochs;
68
+ export declare const epochsFor: (vfs: SQLiteVFS, file: string, locks: Locks) => Epochs;
39
69
  /**
40
70
  * Where a worker's `seen` lands after the write it just served.
41
71
  *
package/dist/errors.d.ts CHANGED
@@ -2,19 +2,58 @@
2
2
  * Every failure this library raises on its own behalf. A caller discriminates
3
3
  * on `code`, or on `name` — they carry the same value, so `err.name` reads the
4
4
  * way `'AbortError'` does on the DOMException an aborted signal throws.
5
+ * `DATABASE_IN_USE` is this library's own: a database that a live client holds,
6
+ * as opposed to `BUSY`, which covers a transient conflict worth retrying.
7
+ * `DATABASE_NOT_FOUND` is raised only by `deleteDatabase`: there is nothing at
8
+ * that name to delete. `createSQLiteClient` creates what is absent, so it has
9
+ * no such case.
10
+ * `UNSUPPORTED` means the platform cannot answer the question — raised by
11
+ * `inspectDatabase` where Web Locks is missing, because reporting zero clients
12
+ * there would be indistinguishable from a database nobody holds.
13
+ * `OPERATION_TIMEOUT` is the `timeout` a caller set on a call being spent. It is
14
+ * deliberately not `TIMEOUT`, which means a deadline this library imposed on
15
+ * itself — a worker that never became ready, a deletion that did not complete.
16
+ * `STATEMENT_FAILED` is a statement SQLite refused or failed for any reason
17
+ * but a lock conflict — a constraint, a syntax error, a full disk. `message` is
18
+ * SQLite's own; `sqliteCode` carries its result code, and `sqliteExtendedCode`
19
+ * its subtype when SQLite reports one.
5
20
  */
6
- export type SQLiteErrorCode = 'NOT_A_READ_QUERY' | 'CLIENT_CLOSED' | 'WORKER_CRASHED' | 'TIMEOUT' | 'PROTOCOL_ERROR' | 'INVALID_IDENTIFIER' | 'INVALID_OPTION' | 'INVALID_PRAGMA' | 'BULK_WRITE_FAILED' | 'BUSY' | 'READ_ONLY_TRANSACTION';
21
+ import type { SQLiteExtendedResultCode, SQLiteResultCode } from './sqlite-codes';
22
+ export type SQLiteErrorCode = 'NOT_A_READ_QUERY' | 'CLIENT_CLOSED' | 'WORKER_CRASHED' | 'TIMEOUT' | 'PROTOCOL_ERROR' | 'INVALID_IDENTIFIER' | 'INVALID_OPTION' | 'INVALID_PRAGMA' | 'BULK_WRITE_FAILED' | 'BUSY' | 'STATEMENT_FAILED' | 'DATABASE_IN_USE' | 'DATABASE_NOT_FOUND' | 'READ_ONLY_TRANSACTION' | 'UNSUPPORTED' | 'WORKER_BUSY' | 'OPERATION_TIMEOUT' | 'TRANSACTION_CLOSED';
7
23
  export declare class SQLiteError extends Error {
8
24
  readonly code: SQLiteErrorCode;
9
25
  /**
10
26
  * SQLite's own numeric result code, present only when the failure came from
11
- * SQLite rather than from this library. `BUSY` covers both SQLITE_BUSY (5)
12
- * and SQLITE_LOCKED (6); this is how a caller tells them apart.
27
+ * SQLite rather than from this library. Always the PRIMARY code. `BUSY`
28
+ * covers both SQLITE_BUSY (5) and SQLITE_LOCKED (6); this is how a caller
29
+ * tells them apart. Typed `SQLiteResultCode` (D10): since `sqliteCodeOf`,
30
+ * it is always a primary code of the bundled SQLite, so comparing it with
31
+ * an extended code does not compile.
13
32
  */
14
- readonly sqliteCode?: number;
33
+ readonly sqliteCode?: SQLiteResultCode;
34
+ /**
35
+ * SQLite's extended result code, present only when a statement SQLite ran
36
+ * failed WITH A SUBTYPE — `STATEMENT_FAILED` or `BUSY` from a query, never
37
+ * an open or a delete: 2067 (`SQLITE_EXTENDED_CODES.CONSTRAINT_UNIQUE`)
38
+ * under `sqliteCode` 19. Absent when SQLite has no subtype for the failure
39
+ * (a full disk, a syntax error), since `sqliteCode` already says it. For a
40
+ * subtype SQLite reports, `(sqliteExtendedCode & 0xff) === sqliteCode` is
41
+ * SQLite's own guarantee, not something this library enforces: the client
42
+ * deliberately lets a differing value through (a 0 from a wrong read).
43
+ * Typed open (`SQLiteExtendedResultCode | (number & {})`, D10): D9 lets a
44
+ * wrong read through, which a strict type would misdescribe.
45
+ */
46
+ readonly sqliteExtendedCode?: SQLiteExtendedResultCode | (number & {});
47
+ /**
48
+ * The `timeout` that was exceeded, in milliseconds. Present only on
49
+ * `OPERATION_TIMEOUT`, so a log need not parse the message for it.
50
+ */
51
+ readonly timeout?: number;
15
52
  constructor(code: SQLiteErrorCode, message: string, options?: {
16
53
  cause?: unknown;
17
- sqliteCode?: number;
54
+ sqliteCode?: SQLiteResultCode;
55
+ sqliteExtendedCode?: SQLiteExtendedResultCode | (number & {});
56
+ timeout?: number;
18
57
  });
19
58
  }
20
59
  /**
package/dist/index.d.ts CHANGED
@@ -3,4 +3,6 @@ export { detectFeatures, missingFeature } from './capabilities';
3
3
  export * from './client';
4
4
  export * from './delete';
5
5
  export * from './errors';
6
+ export { type ClientInspection, type DatabaseClient, type DatabaseInspection, type InspectDatabaseOptions, type InspectionBase, inspectDatabase, } from './inspect';
7
+ export { SQLITE_CODES, SQLITE_EXTENDED_CODES, type SQLiteExtendedResultCode, type SQLiteResultCode, } from './sqlite-codes';
6
8
  export { defaultBuildFor, type PlatformFeature, type SQLiteBuild, type SQLiteVFS, VFS_CAPABILITIES, type VFSCapability, type VFSLayout, type VFSMemoryModel, type VFSStorage, } from './types';