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/README.md +66 -430
- package/dist/abandon.d.ts +77 -0
- package/dist/api.d.ts +62 -12
- package/dist/bulk.d.ts +15 -0
- package/dist/client.d.ts +43 -20
- package/dist/delete.d.ts +11 -5
- package/dist/epochs.d.ts +36 -6
- package/dist/errors.d.ts +44 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +96 -0
- package/dist/locks.d.ts +124 -6
- package/dist/pool.d.ts +101 -8
- package/dist/queries.d.ts +36 -7
- package/dist/scheduler.d.ts +14 -0
- package/dist/sqlite-codes.d.ts +154 -0
- package/dist/supervisor.d.ts +1 -1
- package/dist/transaction.d.ts +24 -1
- package/dist/types.d.ts +322 -28
- package/dist/utils.d.ts +42 -2
- package/dist/worker/probes.d.ts +26 -0
- package/dist/worker/sqlite-code.d.ts +9 -0
- package/dist/worker/statement-cache.d.ts +17 -3
- package/dist/worker/worker.js +1 -1
- package/dist/worker/worker.js.map +1 -1
- package/package.json +15 -7
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { LockEntries, Locks } from './locks';
|
|
2
|
+
import type { SQLiteVFS } from './types';
|
|
3
|
+
export declare const resolveRealmId: (locks: Locks, snapshot: LockEntries, ownMarkerName?: string) => Promise<string>;
|
|
4
|
+
/** One live client on a database. */
|
|
5
|
+
export type DatabaseClient = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
/** The realm holding it. Every client in one tab reports the same value. */
|
|
9
|
+
readonly tab: string;
|
|
10
|
+
/** That realm is the caller's. A same-origin iframe is another tab here. */
|
|
11
|
+
readonly sameTab: boolean;
|
|
12
|
+
/** Four VFS share the `opfs` namespace, and therefore the file. */
|
|
13
|
+
readonly vfs: SQLiteVFS;
|
|
14
|
+
};
|
|
15
|
+
export type InspectionBase = {
|
|
16
|
+
readonly file: string;
|
|
17
|
+
readonly vfs: SQLiteVFS;
|
|
18
|
+
/** Distinct realms among the clients. */
|
|
19
|
+
readonly tabs: number;
|
|
20
|
+
readonly write: {
|
|
21
|
+
/** The realm writing now, never the client. `null` when nobody writes. */
|
|
22
|
+
readonly tab: string | null;
|
|
23
|
+
/** Always false when `tab` is null. */
|
|
24
|
+
readonly sameTab: boolean;
|
|
25
|
+
/** Writers queued behind it, across the whole origin. */
|
|
26
|
+
readonly waiting: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export type DatabaseInspection = InspectionBase & {
|
|
30
|
+
readonly clients: readonly DatabaseClient[];
|
|
31
|
+
};
|
|
32
|
+
export type ClientInspection = InspectionBase & {
|
|
33
|
+
/**
|
|
34
|
+
* This client, or `null` when this client's own marker is not in the
|
|
35
|
+
* snapshot — the brief window before its Web Locks grant has landed, or when
|
|
36
|
+
* the grant could not be taken at all.
|
|
37
|
+
*/
|
|
38
|
+
readonly self: DatabaseClient | null;
|
|
39
|
+
readonly siblings: readonly DatabaseClient[];
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The census, given locks that are already known to work.
|
|
43
|
+
*
|
|
44
|
+
* One `entries()` call supplies the roster, the writer and the queue together,
|
|
45
|
+
* so those three always describe the same instant and cannot compose a state
|
|
46
|
+
* that never existed. The one exception: a realm whose id has never been
|
|
47
|
+
* resolved and that was given no marker pays one extra query to read its own
|
|
48
|
+
* `clientId` back — once, and never again for that realm's lifetime.
|
|
49
|
+
*/
|
|
50
|
+
export declare const inspectWith: (locks: Locks, file: string, vfs: SQLiteVFS, ownMarkerName?: string) => Promise<DatabaseInspection>;
|
|
51
|
+
/**
|
|
52
|
+
* Whether any OTHER client of this library holds `file` on `vfs`.
|
|
53
|
+
*
|
|
54
|
+
* The discriminator behind the `openTimeout` message: a slot that never became
|
|
55
|
+
* ready is usually blamed on another tab, and that is often false — a page
|
|
56
|
+
* reloaded without `close()` leaves a dead context holding the database, and
|
|
57
|
+
* no live client to find. One `entries()` call tells the two apart.
|
|
58
|
+
*
|
|
59
|
+
* Deliberately NOT `inspectWith`: this runs while the pool is half-open, so it
|
|
60
|
+
* resolves no realm id (which would take a lock), touches nothing the client
|
|
61
|
+
* owns, and never throws. `undefined` is "could not be answered" — Web Locks
|
|
62
|
+
* missing, a VFS whose pages never leave their worker, a registry that rejects,
|
|
63
|
+
* or one that does not answer within `deadlineMs`. A caller that reads
|
|
64
|
+
* `undefined` as `false` would state the opposite of what was observed.
|
|
65
|
+
*
|
|
66
|
+
* `false` means no client of THIS library holds it — never that nobody does.
|
|
67
|
+
* Another library, another origin's tooling and native code are all invisible
|
|
68
|
+
* to the Web Locks registry.
|
|
69
|
+
*/
|
|
70
|
+
export declare const libraryClientsHold: (locks: Locks, file: string, vfs: SQLiteVFS, ownId: string, deadlineMs?: number) => Promise<boolean | undefined>;
|
|
71
|
+
export type InspectDatabaseOptions = {
|
|
72
|
+
/**
|
|
73
|
+
* The VFS the database was created with. Required, and not defaulted: four
|
|
74
|
+
* VFS share one underlying file, and the others are separate stores
|
|
75
|
+
* entirely, so guessing would report on a different database.
|
|
76
|
+
*/
|
|
77
|
+
vfs: SQLiteVFS;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Who is live on a database, without opening it.
|
|
81
|
+
*
|
|
82
|
+
* This is a snapshot, stale the instant it resolves. It informs a UI; it never
|
|
83
|
+
* authorizes an action — `deleteDatabase` raising `DATABASE_IN_USE` is the only
|
|
84
|
+
* authority on whether a database can be removed.
|
|
85
|
+
*
|
|
86
|
+
* Takes `file` positionally like `createSQLiteClient` and `deleteDatabase`:
|
|
87
|
+
* every root export of this library names the database the same way.
|
|
88
|
+
*
|
|
89
|
+
* @throws {SQLiteError} `INVALID_OPTION` when `vfs` is missing, unknown, or a
|
|
90
|
+
* memory VFS, where two clients are two databases and the question has no
|
|
91
|
+
* meaning.
|
|
92
|
+
* @throws {SQLiteError} `UNSUPPORTED` where the Web Locks API is unavailable.
|
|
93
|
+
* Reporting zero there would be indistinguishable from a database nobody
|
|
94
|
+
* holds.
|
|
95
|
+
*/
|
|
96
|
+
export declare const inspectDatabase: (file: string, options: InspectDatabaseOptions) => Promise<DatabaseInspection>;
|
package/dist/locks.d.ts
CHANGED
|
@@ -8,20 +8,46 @@
|
|
|
8
8
|
* that is killed has its locks released by the browser, so its orphans become
|
|
9
9
|
* collectable immediately, with no timestamp and no grace period.
|
|
10
10
|
*/
|
|
11
|
+
import type { SQLiteVFS } from './types';
|
|
12
|
+
/** One entry in the lock registry as returned by `query()`. */
|
|
13
|
+
type QueriedLock = {
|
|
14
|
+
name?: string;
|
|
15
|
+
mode?: string;
|
|
16
|
+
clientId?: string;
|
|
17
|
+
};
|
|
11
18
|
/** The slice of the Web Locks API this module uses. */
|
|
12
19
|
type LockManager = {
|
|
13
20
|
request: (name: string, optionsOrCallback: any, callback?: (lock: unknown) => Promise<unknown>) => Promise<unknown>;
|
|
14
21
|
query: () => Promise<{
|
|
15
|
-
held?:
|
|
16
|
-
|
|
17
|
-
}[];
|
|
22
|
+
held?: QueriedLock[];
|
|
23
|
+
pending?: QueriedLock[];
|
|
18
24
|
}>;
|
|
19
25
|
};
|
|
20
26
|
export type Locks = {
|
|
21
27
|
/** False when the Web Locks API is missing; every method then no-ops. */
|
|
22
28
|
readonly available: boolean;
|
|
23
|
-
/**
|
|
24
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Acquires `name` and resolves with the function that releases it.
|
|
31
|
+
*
|
|
32
|
+
* `mode: 'shared'` is what the epoch marker uses: many realms may hold the
|
|
33
|
+
* same name at once, so publishing never waits and two realms can never
|
|
34
|
+
* collide on one epoch number. `signal` aborts the WAIT — never the hold —
|
|
35
|
+
* and makes the request reject with `AbortError`.
|
|
36
|
+
*
|
|
37
|
+
* `ifAvailable: true` mirrors `tryWithLock`'s semantics: the real API hands
|
|
38
|
+
* the callback `null` rather than waiting when the lock is held elsewhere.
|
|
39
|
+
* Resolves with `undefined` in that case instead of waiting. Never waits —
|
|
40
|
+
* that is the point for the connection guard.
|
|
41
|
+
*/
|
|
42
|
+
hold(name: string, options?: {
|
|
43
|
+
mode?: 'exclusive' | 'shared';
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}): Promise<() => void>;
|
|
46
|
+
hold(name: string, options: {
|
|
47
|
+
mode?: 'exclusive' | 'shared';
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
ifAvailable: true;
|
|
50
|
+
}): Promise<(() => void) | undefined>;
|
|
25
51
|
/** Runs `fn` while holding `name` exclusively. */
|
|
26
52
|
withLock: <T>(name: string, fn: () => Promise<T>) => Promise<T>;
|
|
27
53
|
/**
|
|
@@ -36,13 +62,105 @@ export type Locks = {
|
|
|
36
62
|
tryWithLock: (name: string, fn: () => Promise<unknown>) => Promise<boolean>;
|
|
37
63
|
/** Names currently held anywhere in this origin — every tab included. */
|
|
38
64
|
heldNames: () => Promise<string[]>;
|
|
65
|
+
/**
|
|
66
|
+
* The origin's whole lock registry: held AND pending, each with the realm
|
|
67
|
+
* holding or awaiting it.
|
|
68
|
+
*
|
|
69
|
+
* `heldNames()` answers a different, cheaper question and keeps its own
|
|
70
|
+
* shape — `epochsFor` only ever needs names.
|
|
71
|
+
*/
|
|
72
|
+
entries: () => Promise<LockEntries>;
|
|
73
|
+
};
|
|
74
|
+
/** One entry of the origin's lock registry, held or pending. */
|
|
75
|
+
export type LockEntry = {
|
|
76
|
+
readonly name: string;
|
|
77
|
+
readonly mode: 'exclusive' | 'shared';
|
|
78
|
+
readonly clientId: string;
|
|
79
|
+
};
|
|
80
|
+
export type LockEntries = {
|
|
81
|
+
readonly held: readonly LockEntry[];
|
|
82
|
+
readonly pending: readonly LockEntry[];
|
|
39
83
|
};
|
|
40
84
|
export declare const stagingTableName: (uuid: string) => string;
|
|
41
85
|
export declare const isStagingTable: (table: string) => boolean;
|
|
42
86
|
export declare const stagingLockName: (file: string, table: string) => string;
|
|
43
87
|
export declare const sweepLockName: (file: string) => string;
|
|
88
|
+
/**
|
|
89
|
+
* The marker a client holds to publish that it is alive on a database.
|
|
90
|
+
*
|
|
91
|
+
* Held in SHARED mode and contended by NOBODY: like `bsq:staging` this is a
|
|
92
|
+
* liveness marker, not mutual exclusion. `bsq:conn` stays the only occupancy
|
|
93
|
+
* detector `deleteDatabase` rests on — a second one would diverge from it.
|
|
94
|
+
*
|
|
95
|
+
* The label is `encodeURIComponent`d, which escapes `:` as `%3A`. That is what
|
|
96
|
+
* makes the tail split unambiguously into exactly three segments whatever the
|
|
97
|
+
* consumer names their client. The FILE may itself contain a colon, which is
|
|
98
|
+
* why the reader rebuilds the exact prefix instead of scanning for separators —
|
|
99
|
+
* the same trap `epochsFor` documents.
|
|
100
|
+
*/
|
|
101
|
+
export declare const clientMarkerName: (vfs: SQLiteVFS, file: string, id: string, clientName: string) => string;
|
|
102
|
+
export type ClientMarker = {
|
|
103
|
+
readonly id: string;
|
|
104
|
+
readonly vfs: SQLiteVFS;
|
|
105
|
+
readonly name: string;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Reads one of our markers, or `undefined` for anything else.
|
|
109
|
+
*
|
|
110
|
+
* Every rejection below is deliberate: a marker this version does not
|
|
111
|
+
* understand — a future one carrying more segments, say — must be SKIPPED, not
|
|
112
|
+
* guessed at. Guessing is how a reader reports another database's state.
|
|
113
|
+
*/
|
|
114
|
+
export declare const parseClientMarker: (lockName: string, vfs: SQLiteVFS, file: string) => ClientMarker | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* The storage namespace a VFS writes into — derived from `layout`, NEVER from
|
|
117
|
+
* the VFS name.
|
|
118
|
+
*
|
|
119
|
+
* `OPFSAdaptiveVFS`, `OPFSAnyContextVFS`, `OPFSCoopSyncVFS` and
|
|
120
|
+
* `OPFSWriteAheadVFS` all walk from `navigator.storage.getDirectory()` and open
|
|
121
|
+
* `getFileHandle(filename)`, so one database name is ONE file for all four. A
|
|
122
|
+
* per-VFS key would let two of them write the same bytes without ever
|
|
123
|
+
* excluding each other: a missed conflict corrupts, an invented one only slows.
|
|
124
|
+
*
|
|
125
|
+
* `idb-store` goes finer than its layout on purpose — its two VFS each own an
|
|
126
|
+
* IndexedDB database named after their class, so grouping them would invent a
|
|
127
|
+
* conflict for free. `opfs-pool` and `memory` are alone in their layout, so the
|
|
128
|
+
* VFS name is already the namespace.
|
|
129
|
+
*
|
|
130
|
+
* `worker/worker.ts:627` gates on `layout` for the same reason, in those words.
|
|
131
|
+
*/
|
|
132
|
+
export declare const namespaceFor: (vfs: SQLiteVFS) => string;
|
|
133
|
+
/**
|
|
134
|
+
* Whether two clients on this VFS can reach the same bytes at all.
|
|
135
|
+
*
|
|
136
|
+
* False for the memory VFS: its pages live in the worker that opened them and
|
|
137
|
+
* `maxPoolSize` is 1, so two clients on one name are two independent
|
|
138
|
+
* databases. Locking them against each other would be wrong as well as slow —
|
|
139
|
+
* an origin round trip charged to the VFS chosen for speed. `delete.ts:79`
|
|
140
|
+
* skips the same layout, for the same reason.
|
|
141
|
+
*/
|
|
142
|
+
export declare const sharesStorage: (vfs: SQLiteVFS) => boolean;
|
|
44
143
|
/** Serializes database opening across the pool — replaces the SAB init mutex. */
|
|
45
|
-
export declare const initLockName: (file: string) => string;
|
|
144
|
+
export declare const initLockName: (vfs: SQLiteVFS, file: string) => string;
|
|
145
|
+
/**
|
|
146
|
+
* Serializes WRITERS across every client and tab in the origin. Exclusive, so
|
|
147
|
+
* at most one is held per database at any instant however many clients exist.
|
|
148
|
+
*/
|
|
149
|
+
export declare const writeLockName: (vfs: SQLiteVFS, file: string) => string;
|
|
150
|
+
/**
|
|
151
|
+
* Origin-wide exclusive connection lock for VFS that cannot safely share a
|
|
152
|
+
* database across clients (`exclusiveConnection: true` in `VFS_CAPABILITIES`).
|
|
153
|
+
*
|
|
154
|
+
* Held for the client's lifetime. A second `createSQLiteClient` that tries to
|
|
155
|
+
* open the same database will fail its first query with `BUSY` instead of
|
|
156
|
+
* silently reading a broken, frozen view — the failure mode measured as
|
|
157
|
+
* AHP-2TAB (2026-09-01) where `SELECT 1` passes and `SELECT count(*) FROM
|
|
158
|
+
* sqlite_master` returns 0 on an unfixable connection.
|
|
159
|
+
*
|
|
160
|
+
* The key uses `namespaceFor(vfs)` for the same reason `writeLockName` does:
|
|
161
|
+
* the gate is by layout declaration, not by VFS name.
|
|
162
|
+
*/
|
|
163
|
+
export declare const connectionLockName: (vfs: SQLiteVFS, file: string) => string;
|
|
46
164
|
/**
|
|
47
165
|
* Which staging tables no live `output()` is using — pure, so it is driven by
|
|
48
166
|
* Node tests rather than by two browser tabs.
|
package/dist/pool.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { SQLiteError } from './errors';
|
|
1
|
+
import { SQLiteError, type SQLiteErrorCode } from './errors';
|
|
2
2
|
import type { Logger } from './logger';
|
|
3
|
-
import type {
|
|
3
|
+
import type { SQLiteResultCode } from './sqlite-codes';
|
|
4
|
+
import type { PlatformFeature, SavepointOp, SQLiteBuild, SQLiteVFS, WasmLocation } from './types';
|
|
4
5
|
/**
|
|
5
6
|
* Query execution options forwarded to a pool worker.
|
|
6
7
|
*/
|
|
7
8
|
export type PoolWorkerQueryOptions = {
|
|
8
9
|
chunkSize?: number | undefined;
|
|
9
10
|
credits?: number | undefined;
|
|
11
|
+
timeout?: number | undefined;
|
|
12
|
+
/** Forwarded to the worker so it installs the async progress handler (§4 D2). */
|
|
13
|
+
abortable?: boolean | undefined;
|
|
10
14
|
/**
|
|
11
15
|
* When true, the query's completion does not call `deps.onServed`. Set for
|
|
12
16
|
* the commit-propagation barrier: it is a synthetic probe, not user work, and
|
|
@@ -16,6 +20,13 @@ export type PoolWorkerQueryOptions = {
|
|
|
16
20
|
* to prove the barrier stays conditional.
|
|
17
21
|
*/
|
|
18
22
|
noServed?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Read exactly once, when the query is POSTED — below the reuse guard,
|
|
25
|
+
* never when the query is created. A transaction hands its pending savepoint
|
|
26
|
+
* conclusion over in here, so a query the guard refuses must not consume it
|
|
27
|
+
* (spec 2026-09-11, §4).
|
|
28
|
+
*/
|
|
29
|
+
savepoint?: (() => SavepointOp | undefined) | undefined;
|
|
19
30
|
};
|
|
20
31
|
/**
|
|
21
32
|
* A Worker extended with pool-specific properties.
|
|
@@ -38,28 +49,103 @@ export type PoolWorker = Worker & {
|
|
|
38
49
|
seen: number;
|
|
39
50
|
/** The epoch captured when the current lease was granted. */
|
|
40
51
|
epochTarget: number;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the connection was inside a transaction when its last query
|
|
54
|
+
* ended, as the worker read it (`sqlite3_get_autocommit`). `undefined` until
|
|
55
|
+
* a query has reported it.
|
|
56
|
+
*
|
|
57
|
+
* CONNECTION state, not availability: nothing schedules on it and nothing
|
|
58
|
+
* may. Availability lives in `scheduler.ts` alone — see the `available`
|
|
59
|
+
* declaration there — and a flag on this object that the pool consulted would
|
|
60
|
+
* reopen B1. Its one reader is `transaction.ts`, which asks it whether a
|
|
61
|
+
* ROLLBACK is still owed and whether its transaction is still alive.
|
|
62
|
+
*/
|
|
63
|
+
inTransaction?: boolean | undefined;
|
|
41
64
|
query: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: PoolWorkerQueryOptions) => AsyncGenerator<T[] | number>;
|
|
42
65
|
/**
|
|
43
66
|
* Ask the worker to stop. Also settles a `next()` already in flight, which
|
|
44
67
|
* is what lets the consumer's queued `return()` reach the generator's
|
|
45
68
|
* finally instead of waiting behind a chunk that may be minutes away.
|
|
69
|
+
*
|
|
70
|
+
* `on` is the transport iterator being stopped, and it is REQUIRED: the call
|
|
71
|
+
* is a no-op unless the worker is still serving that transport, which is what
|
|
72
|
+
* keeps a late stop off an unrelated query. It was briefly optional, and an
|
|
73
|
+
* omitted argument meant "stop whatever is running" — the exact semantics that
|
|
74
|
+
* let an abandoned generator's late cleanup truncate a healthy query. Nothing
|
|
75
|
+
* needs that form, so nothing may ask for it.
|
|
46
76
|
*/
|
|
47
|
-
interrupt: () => void;
|
|
77
|
+
interrupt: (on: object) => void;
|
|
48
78
|
/** Resolves when no query is in flight on this worker. */
|
|
49
79
|
quiesce: () => Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Resolves when this worker will accept another query — at `done`, which is
|
|
82
|
+
* earlier than `quiesce()`. Use it to wait for the WORKER; use `quiesce()`
|
|
83
|
+
* to wait for a statement to have been fully settled and judged.
|
|
84
|
+
*/
|
|
85
|
+
free: () => Promise<void>;
|
|
50
86
|
/** Posts `close`, awaits the `closed` reply, then the caller must terminate. */
|
|
51
87
|
close: () => Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Stops the thread AND the transport — never the browser's `terminate()`
|
|
90
|
+
* alone.
|
|
91
|
+
*
|
|
92
|
+
* `PoolWorker` IS the native `Worker`, so this used to be the engine's own
|
|
93
|
+
* method: it killed the thread and told the transport nothing. A request
|
|
94
|
+
* posted afterwards waited for a reply that could never come — a
|
|
95
|
+
* transaction's next statement, and its fallback `ROLLBACK`, which carries no
|
|
96
|
+
* signal by design and so could not even be aborted. Overriding the method,
|
|
97
|
+
* rather than adding one beside it, is deliberate: there are several
|
|
98
|
+
* terminate sites and one added later must not be able to forget this.
|
|
99
|
+
*
|
|
100
|
+
* `reason` is what in-flight and later requests reject with. It reports
|
|
101
|
+
* nothing to the client: whoever terminates has already decided this worker's
|
|
102
|
+
* fate, and going through `onDeath` here would re-enter that decision.
|
|
103
|
+
*/
|
|
104
|
+
terminate: (reason?: SQLiteError) => void;
|
|
105
|
+
};
|
|
106
|
+
/** What `createPoolWorker` settles with when its worker declined to open. */
|
|
107
|
+
export type DeclinedWorker = {
|
|
108
|
+
declined: PlatformFeature;
|
|
52
109
|
};
|
|
53
110
|
/**
|
|
54
111
|
* Returns a SQLiteError('BUSY', …) when data carries a lock-conflict result
|
|
55
|
-
* code (5 or 6), else undefined. Shared by
|
|
56
|
-
*
|
|
112
|
+
* code (5 or 6), else undefined. Shared by `statementError` and `startupError`
|
|
113
|
+
* so the BUSY_CODES decision lives in exactly one place. The extended code
|
|
114
|
+
* travels with it when it is a subtype (`subtypeOf`) — a query sends one, an
|
|
115
|
+
* open does not.
|
|
57
116
|
*/
|
|
58
117
|
export declare const busyFromCode: (data: {
|
|
59
118
|
message: string;
|
|
60
119
|
cause?: unknown;
|
|
61
|
-
sqliteCode?:
|
|
120
|
+
sqliteCode?: SQLiteResultCode;
|
|
121
|
+
sqliteExtendedCode?: number;
|
|
62
122
|
}) => SQLiteError | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* What a query's `error` message becomes (spec 2026-09-14 §5.3): a code the
|
|
125
|
+
* worker minted; else `BUSY` for a lock conflict; else `STATEMENT_FAILED` for
|
|
126
|
+
* any other code SQLite reported; else — a failure SQLite did not report, such
|
|
127
|
+
* as a JS exception in the worker — a plain Error, as before. `BUSY` and
|
|
128
|
+
* `STATEMENT_FAILED` carry `sqliteCode`, and `sqliteExtendedCode` when it is a
|
|
129
|
+
* subtype (`subtypeOf`).
|
|
130
|
+
*/
|
|
131
|
+
export declare const statementError: (data: {
|
|
132
|
+
message: string;
|
|
133
|
+
cause?: unknown;
|
|
134
|
+
sqliteCode?: SQLiteResultCode;
|
|
135
|
+
sqliteExtendedCode?: number;
|
|
136
|
+
errorCode?: SQLiteErrorCode;
|
|
137
|
+
}) => Error;
|
|
138
|
+
/**
|
|
139
|
+
* What a failed open or delete becomes: `BUSY` for a lock conflict, else
|
|
140
|
+
* `WORKER_CRASHED` — the slot dies either way — carrying SQLite's primary code
|
|
141
|
+
* when there is one (spec 2026-09-14, D2). No extended code: when
|
|
142
|
+
* `sqlite3_open_v2` itself fails there is no connection to ask (D7).
|
|
143
|
+
*/
|
|
144
|
+
export declare const startupError: (data: {
|
|
145
|
+
message: string;
|
|
146
|
+
cause?: unknown;
|
|
147
|
+
sqliteCode?: SQLiteResultCode;
|
|
148
|
+
}) => SQLiteError;
|
|
63
149
|
/**
|
|
64
150
|
* The single `new Worker(new URL(…))` expression in this package.
|
|
65
151
|
*
|
|
@@ -81,7 +167,7 @@ export declare const spawnWorker: (name: string) => Worker;
|
|
|
81
167
|
export declare const createPoolWorker: (deps: {
|
|
82
168
|
index: number;
|
|
83
169
|
pool: (PoolWorker | undefined)[];
|
|
84
|
-
|
|
170
|
+
clientName: string;
|
|
85
171
|
file: string;
|
|
86
172
|
vfs: SQLiteVFS;
|
|
87
173
|
build: SQLiteBuild;
|
|
@@ -89,10 +175,17 @@ export declare const createPoolWorker: (deps: {
|
|
|
89
175
|
wasm?: WasmLocation | undefined;
|
|
90
176
|
pragmas?: Record<string, string> | undefined;
|
|
91
177
|
statementCacheSize?: number | undefined;
|
|
178
|
+
statementCacheBytes?: number | undefined;
|
|
92
179
|
onDeath?: (index: number, error: SQLiteError) => void;
|
|
93
180
|
onServed?: (index: number) => void;
|
|
94
181
|
drainTimeout: number;
|
|
95
182
|
createWorkerDebugState?: ((index: number, name: string) => any) | undefined;
|
|
96
183
|
createQueryDebugState?: ((index: number, sql: string, params?: unknown[]) => any) | undefined;
|
|
97
184
|
logger: Logger;
|
|
98
|
-
|
|
185
|
+
abortSlots?: SharedArrayBuffer | undefined;
|
|
186
|
+
declineWithout?: readonly PlatformFeature[] | undefined;
|
|
187
|
+
/** Sent to slot 0 where exclusivity depends on a feature (spec 2026-09-15). */
|
|
188
|
+
probeFirst?: readonly PlatformFeature[] | undefined;
|
|
189
|
+
/** Worker 0's probe answer, with the function that lets it open. */
|
|
190
|
+
onProbed?: ((missing: PlatformFeature | null, proceed: () => void) => void) | undefined;
|
|
191
|
+
}) => Promise<PoolWorker | DeclinedWorker>;
|
package/dist/queries.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type AbandonRegistry } from './abandon';
|
|
2
|
+
import type { SQLiteChunkOptions, SQLiteQueryOptions } from './api';
|
|
2
3
|
import type { PoolWorker } from './pool';
|
|
3
4
|
/**
|
|
4
5
|
* Wires an AbortSignal into a promise that rejects the instant the signal
|
|
@@ -13,14 +14,42 @@ export declare const makeAbortRace: (signal: AbortSignal | undefined) => {
|
|
|
13
14
|
aborted: Promise<never> | undefined;
|
|
14
15
|
teardown: () => void;
|
|
15
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* `SQLiteChunkOptions` plus what only this library passes. `registry` is
|
|
19
|
+
* TEST-ONLY and unsupported; it exists so the abandonment path can be driven
|
|
20
|
+
* without a garbage collection.
|
|
21
|
+
*/
|
|
22
|
+
export type InternalChunkOptions = SQLiteChunkOptions & {
|
|
23
|
+
credits?: number;
|
|
24
|
+
/** The owning layer's teardown, run if the generator is abandoned. */
|
|
25
|
+
onAbandon?: (() => void) | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Handed the transport iterator, synchronously, before the factory returns.
|
|
28
|
+
*
|
|
29
|
+
* An owner that must close this generator from the outside needs it: a
|
|
30
|
+
* method call on an async generator queues behind a `next()` already in
|
|
31
|
+
* flight, so `return()` alone parks until a chunk arrives — which on an
|
|
32
|
+
* `ORDER BY` is the whole sort. `worker.interrupt(transport)` is what
|
|
33
|
+
* settles that `next()`, and it is a no-op unless the worker still serves
|
|
34
|
+
* that transport, so only its true owner can be handed it. `src/transaction.ts`
|
|
35
|
+
* is the only caller; the client path drops its generator instead of
|
|
36
|
+
* closing it and needs nothing here.
|
|
37
|
+
*/
|
|
38
|
+
onTransport?: ((iterator: AsyncGenerator<unknown>) => void) | undefined;
|
|
39
|
+
registry?: AbandonRegistry;
|
|
40
|
+
};
|
|
16
41
|
/**
|
|
17
42
|
* The single query primitive. Every other read path is a thin derivation, and
|
|
18
43
|
* abort is implemented here exactly once.
|
|
44
|
+
*
|
|
45
|
+
* **A factory, not a generator function**, so that the transport iterator
|
|
46
|
+
* exists before the generator does and can be handed to the abandonment
|
|
47
|
+
* registry. Building it early costs nothing: `worker.query()` runs no code
|
|
48
|
+
* until its first `next()`, so the query message and the reuse guard still
|
|
49
|
+
* happen when the consumer first pulls.
|
|
19
50
|
*/
|
|
20
|
-
export declare const chunk: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?:
|
|
21
|
-
|
|
22
|
-
}) => AsyncGenerator<T[]>;
|
|
23
|
-
export declare const streamRows: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteChunkOptions) => AsyncGenerator<T>;
|
|
51
|
+
export declare const chunk: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: InternalChunkOptions) => AsyncGenerator<T[]>;
|
|
52
|
+
export declare const streamRows: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: InternalChunkOptions) => AsyncGenerator<T>;
|
|
24
53
|
export declare const readWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteChunkOptions) => Promise<T[]>;
|
|
25
54
|
/**
|
|
26
55
|
* First row, then stop. This BREAKS rather than aborting: a break triggers the
|
|
@@ -29,8 +58,8 @@ export declare const readWorker: <T extends Record<string, unknown> = Record<str
|
|
|
29
58
|
* That is why there is no internal AbortController here and no need to tell an
|
|
30
59
|
* internal abort from the caller's.
|
|
31
60
|
*/
|
|
32
|
-
export declare const firstWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?:
|
|
33
|
-
export declare const writeWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?:
|
|
61
|
+
export declare const firstWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteQueryOptions) => Promise<T | undefined>;
|
|
62
|
+
export declare const writeWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteQueryOptions) => Promise<{
|
|
34
63
|
result: T[];
|
|
35
64
|
affected: number;
|
|
36
65
|
}>;
|
package/dist/scheduler.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export type WriterPolicy = (index: number) => boolean;
|
|
|
35
35
|
*/
|
|
36
36
|
export type InternalSQLiteClientOptions = CreateSQLiteClientOptions & {
|
|
37
37
|
__unsafeTestWriterPolicy?: WriterPolicy;
|
|
38
|
+
/**
|
|
39
|
+
* TEST-ONLY, UNSUPPORTED. The byte bound has no falsifier without it: at a
|
|
40
|
+
* fixed default nothing in the suite can tell a working bound from one that
|
|
41
|
+
* never fires. Absent from the public options type on purpose.
|
|
42
|
+
*/
|
|
43
|
+
__unsafeTestStatementCacheBytes?: number;
|
|
38
44
|
};
|
|
39
45
|
export type Scheduler<W> = {
|
|
40
46
|
add: (worker: W) => void;
|
|
@@ -55,6 +61,14 @@ export type Scheduler<W> = {
|
|
|
55
61
|
* counts towards `shutdown()`'s wait.
|
|
56
62
|
*/
|
|
57
63
|
remove: (index: number) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Takes a slot out of the pool for good because its worker DECLINED to open:
|
|
66
|
+
* the environment caps the pool below its requested size (spec 2026-09-13).
|
|
67
|
+
* Unlike `remove()`, the slot settles the readiness gate as neither opened
|
|
68
|
+
* nor failed, so it never appears in `onFirstSettle`'s `failedIndices` and
|
|
69
|
+
* never enters the startup retry round.
|
|
70
|
+
*/
|
|
71
|
+
retire: (index: number) => void;
|
|
58
72
|
/**
|
|
59
73
|
* Closes the front door. Queued waiters reject with `reason`, later
|
|
60
74
|
* acquisitions reject the same way, and the returned promise settles when the
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The result codes of SQLite 3.53.0, keyed without the `SQLITE_` prefix, in two
|
|
3
|
+
* tables that match `SQLiteError`'s two fields: test the family on `sqliteCode`
|
|
4
|
+
* against `SQLITE_CODES` (`CONSTRAINT`, `FULL`), the subtype on
|
|
5
|
+
* `sqliteExtendedCode` against `SQLITE_EXTENDED_CODES` (`CONSTRAINT_UNIQUE`).
|
|
6
|
+
* An extended code's low byte is its family (`(2067 & 0xff) === 19`), and its key
|
|
7
|
+
* begins with the family's key. What each one means:
|
|
8
|
+
* https://sqlite.org/rescode.html.
|
|
9
|
+
*
|
|
10
|
+
* Transcribed from `src/sqlite.h.in` — the source of `sqlite3.h` — at SQLite's
|
|
11
|
+
* tag `version-3.53.0`, checked 2026-09-14. That is the SQLite of the vendored
|
|
12
|
+
* wa-sqlite, v1.1.2 (its `package.json` still reads 1.1.1: upstream did not
|
|
13
|
+
* bump it): its source-id, read from the wasm, is `2026-04-09 11:41:38
|
|
14
|
+
* 4525003a53a7fc63ca75`, and the tag's `manifest.uuid` begins with the same
|
|
15
|
+
* hash. Not read from wa-sqlite's `sqlite-constants.js`, which has no
|
|
16
|
+
* `BUSY_*`, `LOCKED_*`, `CANTOPEN_*`, `CORRUPT_*` or `READONLY_*` codes;
|
|
17
|
+
* `tests/unit/sqlite-codes.test.ts` checks every name the two share.
|
|
18
|
+
* Re-transcribe when wa-sqlite moves to another SQLite.
|
|
19
|
+
*
|
|
20
|
+
* `SQLiteError.sqliteCode` is typed `SQLiteResultCode` (strict): since
|
|
21
|
+
* `sqliteCodeOf`, it is always a primary code of the bundled SQLite (D10).
|
|
22
|
+
* `sqliteExtendedCode` stays open (`SQLiteExtendedResultCode | (number & {})`):
|
|
23
|
+
* a wrong read must stay representable (D9).
|
|
24
|
+
*/
|
|
25
|
+
/** The 31 primary result codes — what `SQLiteError.sqliteCode` holds. */
|
|
26
|
+
export declare const SQLITE_CODES: Readonly<{
|
|
27
|
+
readonly OK: 0;
|
|
28
|
+
readonly ERROR: 1;
|
|
29
|
+
readonly INTERNAL: 2;
|
|
30
|
+
readonly PERM: 3;
|
|
31
|
+
readonly ABORT: 4;
|
|
32
|
+
readonly BUSY: 5;
|
|
33
|
+
readonly LOCKED: 6;
|
|
34
|
+
readonly NOMEM: 7;
|
|
35
|
+
readonly READONLY: 8;
|
|
36
|
+
readonly INTERRUPT: 9;
|
|
37
|
+
readonly IOERR: 10;
|
|
38
|
+
readonly CORRUPT: 11;
|
|
39
|
+
readonly NOTFOUND: 12;
|
|
40
|
+
readonly FULL: 13;
|
|
41
|
+
readonly CANTOPEN: 14;
|
|
42
|
+
readonly PROTOCOL: 15;
|
|
43
|
+
readonly EMPTY: 16;
|
|
44
|
+
readonly SCHEMA: 17;
|
|
45
|
+
readonly TOOBIG: 18;
|
|
46
|
+
readonly CONSTRAINT: 19;
|
|
47
|
+
readonly MISMATCH: 20;
|
|
48
|
+
readonly MISUSE: 21;
|
|
49
|
+
readonly NOLFS: 22;
|
|
50
|
+
readonly AUTH: 23;
|
|
51
|
+
readonly FORMAT: 24;
|
|
52
|
+
readonly RANGE: 25;
|
|
53
|
+
readonly NOTADB: 26;
|
|
54
|
+
readonly NOTICE: 27;
|
|
55
|
+
readonly WARNING: 28;
|
|
56
|
+
readonly ROW: 100;
|
|
57
|
+
readonly DONE: 101;
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* The 82 extended result codes — what `SQLiteError.sqliteExtendedCode` holds
|
|
61
|
+
* when SQLite reports a subtype. No primary code is repeated here.
|
|
62
|
+
*/
|
|
63
|
+
export declare const SQLITE_EXTENDED_CODES: Readonly<{
|
|
64
|
+
readonly ERROR_MISSING_COLLSEQ: 257;
|
|
65
|
+
readonly ERROR_RETRY: 513;
|
|
66
|
+
readonly ERROR_SNAPSHOT: 769;
|
|
67
|
+
readonly ERROR_RESERVESIZE: 1025;
|
|
68
|
+
readonly ERROR_KEY: 1281;
|
|
69
|
+
readonly ERROR_UNABLE: 1537;
|
|
70
|
+
readonly IOERR_READ: 266;
|
|
71
|
+
readonly IOERR_SHORT_READ: 522;
|
|
72
|
+
readonly IOERR_WRITE: 778;
|
|
73
|
+
readonly IOERR_FSYNC: 1034;
|
|
74
|
+
readonly IOERR_DIR_FSYNC: 1290;
|
|
75
|
+
readonly IOERR_TRUNCATE: 1546;
|
|
76
|
+
readonly IOERR_FSTAT: 1802;
|
|
77
|
+
readonly IOERR_UNLOCK: 2058;
|
|
78
|
+
readonly IOERR_RDLOCK: 2314;
|
|
79
|
+
readonly IOERR_DELETE: 2570;
|
|
80
|
+
readonly IOERR_BLOCKED: 2826;
|
|
81
|
+
readonly IOERR_NOMEM: 3082;
|
|
82
|
+
readonly IOERR_ACCESS: 3338;
|
|
83
|
+
readonly IOERR_CHECKRESERVEDLOCK: 3594;
|
|
84
|
+
readonly IOERR_LOCK: 3850;
|
|
85
|
+
readonly IOERR_CLOSE: 4106;
|
|
86
|
+
readonly IOERR_DIR_CLOSE: 4362;
|
|
87
|
+
readonly IOERR_SHMOPEN: 4618;
|
|
88
|
+
readonly IOERR_SHMSIZE: 4874;
|
|
89
|
+
readonly IOERR_SHMLOCK: 5130;
|
|
90
|
+
readonly IOERR_SHMMAP: 5386;
|
|
91
|
+
readonly IOERR_SEEK: 5642;
|
|
92
|
+
readonly IOERR_DELETE_NOENT: 5898;
|
|
93
|
+
readonly IOERR_MMAP: 6154;
|
|
94
|
+
readonly IOERR_GETTEMPPATH: 6410;
|
|
95
|
+
readonly IOERR_CONVPATH: 6666;
|
|
96
|
+
readonly IOERR_VNODE: 6922;
|
|
97
|
+
readonly IOERR_AUTH: 7178;
|
|
98
|
+
readonly IOERR_BEGIN_ATOMIC: 7434;
|
|
99
|
+
readonly IOERR_COMMIT_ATOMIC: 7690;
|
|
100
|
+
readonly IOERR_ROLLBACK_ATOMIC: 7946;
|
|
101
|
+
readonly IOERR_DATA: 8202;
|
|
102
|
+
readonly IOERR_CORRUPTFS: 8458;
|
|
103
|
+
readonly IOERR_IN_PAGE: 8714;
|
|
104
|
+
readonly IOERR_BADKEY: 8970;
|
|
105
|
+
readonly IOERR_CODEC: 9226;
|
|
106
|
+
readonly LOCKED_SHAREDCACHE: 262;
|
|
107
|
+
readonly LOCKED_VTAB: 518;
|
|
108
|
+
readonly BUSY_RECOVERY: 261;
|
|
109
|
+
readonly BUSY_SNAPSHOT: 517;
|
|
110
|
+
readonly BUSY_TIMEOUT: 773;
|
|
111
|
+
readonly CANTOPEN_NOTEMPDIR: 270;
|
|
112
|
+
readonly CANTOPEN_ISDIR: 526;
|
|
113
|
+
readonly CANTOPEN_FULLPATH: 782;
|
|
114
|
+
readonly CANTOPEN_CONVPATH: 1038;
|
|
115
|
+
readonly CANTOPEN_DIRTYWAL: 1294;
|
|
116
|
+
readonly CANTOPEN_SYMLINK: 1550;
|
|
117
|
+
readonly CORRUPT_VTAB: 267;
|
|
118
|
+
readonly CORRUPT_SEQUENCE: 523;
|
|
119
|
+
readonly CORRUPT_INDEX: 779;
|
|
120
|
+
readonly READONLY_RECOVERY: 264;
|
|
121
|
+
readonly READONLY_CANTLOCK: 520;
|
|
122
|
+
readonly READONLY_ROLLBACK: 776;
|
|
123
|
+
readonly READONLY_DBMOVED: 1032;
|
|
124
|
+
readonly READONLY_CANTINIT: 1288;
|
|
125
|
+
readonly READONLY_DIRECTORY: 1544;
|
|
126
|
+
readonly ABORT_ROLLBACK: 516;
|
|
127
|
+
readonly CONSTRAINT_CHECK: 275;
|
|
128
|
+
readonly CONSTRAINT_COMMITHOOK: 531;
|
|
129
|
+
readonly CONSTRAINT_FOREIGNKEY: 787;
|
|
130
|
+
readonly CONSTRAINT_FUNCTION: 1043;
|
|
131
|
+
readonly CONSTRAINT_NOTNULL: 1299;
|
|
132
|
+
readonly CONSTRAINT_PRIMARYKEY: 1555;
|
|
133
|
+
readonly CONSTRAINT_TRIGGER: 1811;
|
|
134
|
+
readonly CONSTRAINT_UNIQUE: 2067;
|
|
135
|
+
readonly CONSTRAINT_VTAB: 2323;
|
|
136
|
+
readonly CONSTRAINT_ROWID: 2579;
|
|
137
|
+
readonly CONSTRAINT_PINNED: 2835;
|
|
138
|
+
readonly CONSTRAINT_DATATYPE: 3091;
|
|
139
|
+
readonly NOTICE_RECOVER_WAL: 283;
|
|
140
|
+
readonly NOTICE_RECOVER_ROLLBACK: 539;
|
|
141
|
+
readonly NOTICE_RBU: 795;
|
|
142
|
+
readonly WARNING_AUTOINDEX: 284;
|
|
143
|
+
readonly AUTH_USER: 279;
|
|
144
|
+
readonly OK_LOAD_PERMANENTLY: 256;
|
|
145
|
+
readonly OK_SYMLINK: 512;
|
|
146
|
+
}>;
|
|
147
|
+
/** A primary result code of SQLite 3.53.0 — the type of `SQLiteError.sqliteCode`. */
|
|
148
|
+
export type SQLiteResultCode = (typeof SQLITE_CODES)[keyof typeof SQLITE_CODES];
|
|
149
|
+
/**
|
|
150
|
+
* An extended result code of SQLite 3.53.0. `SQLiteError.sqliteExtendedCode`
|
|
151
|
+
* is typed `SQLiteExtendedResultCode | (number & {})`: open, because a wrong
|
|
152
|
+
* read must stay representable (spec D9), yet still completing these values.
|
|
153
|
+
*/
|
|
154
|
+
export type SQLiteExtendedResultCode = (typeof SQLITE_EXTENDED_CODES)[keyof typeof SQLITE_EXTENDED_CODES];
|