browser-sqlite 1.0.0-rc.3 → 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/NOTICE +56 -0
- package/README.md +112 -104
- package/dist/LICENSE +21 -0
- package/dist/NOTICE +56 -0
- package/dist/abandon.d.ts +77 -0
- package/dist/api.d.ts +426 -0
- package/dist/bulk.d.ts +57 -0
- package/dist/capabilities.d.ts +23 -0
- package/dist/client.d.ts +221 -0
- package/dist/credits.d.ts +31 -0
- package/dist/{esm/src/debug.d.ts → debug.d.ts} +21 -10
- package/dist/delete.d.ts +48 -0
- package/dist/epochs.d.ts +85 -0
- package/dist/errors.d.ts +76 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +96 -0
- package/dist/locks.d.ts +172 -0
- package/dist/logger.d.ts +24 -0
- package/dist/pool.d.ts +191 -0
- package/dist/queries.d.ts +65 -0
- package/dist/scheduler.d.ts +145 -0
- package/dist/sqlite-codes.d.ts +154 -0
- package/dist/supervisor.d.ts +17 -0
- package/dist/transaction.d.ts +61 -0
- package/dist/types.d.ts +642 -0
- package/dist/utils.d.ts +156 -0
- package/dist/worker/cloneable.d.ts +25 -0
- package/dist/worker/probes.d.ts +26 -0
- package/dist/worker/sqlite-code.d.ts +9 -0
- package/dist/worker/statement-cache.d.ts +36 -0
- package/dist/worker/wa-sqlite-async.wasm +0 -0
- package/dist/worker/wa-sqlite-jspi.wasm +0 -0
- package/dist/worker/wa-sqlite.wasm +0 -0
- package/dist/worker/worker.js +11 -0
- package/dist/worker/worker.js.map +1 -0
- package/package.json +46 -22
- package/dist/esm/index.js +0 -424
- package/dist/esm/rslib.config.d.ts +0 -2
- package/dist/esm/rstest.config.d.ts +0 -2
- package/dist/esm/src/client.d.ts +0 -332
- package/dist/esm/src/index.d.ts +0 -1
- package/dist/esm/src/orchestrator.d.ts +0 -87
- package/dist/esm/src/types.d.ts +0 -83
- package/dist/esm/src/utils.d.ts +0 -6
- /package/dist/{esm/src → worker}/worker.d.ts +0 -0
|
@@ -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
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A thin wrapper over `navigator.locks`, used by `output()` to make its staging
|
|
3
|
+
* tables collectable across tabs (D3).
|
|
4
|
+
*
|
|
5
|
+
* The staging lock is NOT mutual exclusion — nothing contends for its name. It
|
|
6
|
+
* is a liveness marker: a lock held for as long as a staging table exists is
|
|
7
|
+
* what lets another tab's sweep tell an in-flight table from an orphan. A tab
|
|
8
|
+
* that is killed has its locks released by the browser, so its orphans become
|
|
9
|
+
* collectable immediately, with no timestamp and no grace period.
|
|
10
|
+
*/
|
|
11
|
+
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
|
+
};
|
|
18
|
+
/** The slice of the Web Locks API this module uses. */
|
|
19
|
+
type LockManager = {
|
|
20
|
+
request: (name: string, optionsOrCallback: any, callback?: (lock: unknown) => Promise<unknown>) => Promise<unknown>;
|
|
21
|
+
query: () => Promise<{
|
|
22
|
+
held?: QueriedLock[];
|
|
23
|
+
pending?: QueriedLock[];
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
export type Locks = {
|
|
27
|
+
/** False when the Web Locks API is missing; every method then no-ops. */
|
|
28
|
+
readonly available: boolean;
|
|
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>;
|
|
51
|
+
/** Runs `fn` while holding `name` exclusively. */
|
|
52
|
+
withLock: <T>(name: string, fn: () => Promise<T>) => Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Runs `fn` while holding `name`, or skips it entirely when the lock is held
|
|
55
|
+
* elsewhere. Never waits — which is the point: the staging sweep is
|
|
56
|
+
* opportunistic, and awaiting this lock inside an open transaction would
|
|
57
|
+
* hold SQLite's write lock while waiting on a holder that may itself be
|
|
58
|
+
* waiting for that write lock.
|
|
59
|
+
*
|
|
60
|
+
* Resolves `true` if `fn` ran, `false` if it was skipped.
|
|
61
|
+
*/
|
|
62
|
+
tryWithLock: (name: string, fn: () => Promise<unknown>) => Promise<boolean>;
|
|
63
|
+
/** Names currently held anywhere in this origin — every tab included. */
|
|
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[];
|
|
83
|
+
};
|
|
84
|
+
export declare const stagingTableName: (uuid: string) => string;
|
|
85
|
+
export declare const isStagingTable: (table: string) => boolean;
|
|
86
|
+
export declare const stagingLockName: (file: string, table: string) => string;
|
|
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;
|
|
143
|
+
/** Serializes database opening across the pool — replaces the SAB init mutex. */
|
|
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;
|
|
164
|
+
/**
|
|
165
|
+
* Which staging tables no live `output()` is using — pure, so it is driven by
|
|
166
|
+
* Node tests rather than by two browser tabs.
|
|
167
|
+
*/
|
|
168
|
+
export declare const staleStagingTables: (tables: string[], heldNames: string[], file: string) => string[];
|
|
169
|
+
/** The no-op Locks value for environments where the Web Locks API is absent. */
|
|
170
|
+
export declare const noOpLocks: Locks;
|
|
171
|
+
export declare const createLocks: (manager?: LockManager | undefined) => Locks;
|
|
172
|
+
export {};
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prefixed logger the `debug` option turns on.
|
|
3
|
+
*
|
|
4
|
+
* Lifecycle events only — worker created, ready, open-error, crash, restart,
|
|
5
|
+
* worker loss, close, skipped sweep. A line per query would be illegible under
|
|
6
|
+
* real load and would put user values on the console; query throughput belongs
|
|
7
|
+
* in `db.debug`, not here.
|
|
8
|
+
*/
|
|
9
|
+
export type Logger = {
|
|
10
|
+
info: (message: string) => void;
|
|
11
|
+
warn: (message: string) => void;
|
|
12
|
+
error: (message: string) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Always writes through the sink regardless of the `enabled` flag.
|
|
15
|
+
* Use for events that must be visible even when debug logging is off —
|
|
16
|
+
* permanent pool shrinkage being the primary case.
|
|
17
|
+
*/
|
|
18
|
+
always: {
|
|
19
|
+
warn: (message: string) => void;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
type Sink = Pick<Console, 'debug' | 'warn' | 'error'>;
|
|
23
|
+
export declare const createLogger: (prefix: string, enabled: boolean, sink?: Sink) => Logger;
|
|
24
|
+
export {};
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { SQLiteError, type SQLiteErrorCode } from './errors';
|
|
2
|
+
import type { Logger } from './logger';
|
|
3
|
+
import type { SQLiteResultCode } from './sqlite-codes';
|
|
4
|
+
import type { PlatformFeature, SavepointOp, SQLiteBuild, SQLiteVFS, WasmLocation } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Query execution options forwarded to a pool worker.
|
|
7
|
+
*/
|
|
8
|
+
export type PoolWorkerQueryOptions = {
|
|
9
|
+
chunkSize?: number | undefined;
|
|
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;
|
|
14
|
+
/**
|
|
15
|
+
* When true, the query's completion does not call `deps.onServed`. Set for
|
|
16
|
+
* the commit-propagation barrier: it is a synthetic probe, not user work, and
|
|
17
|
+
* must not reset the supervisor's restart counter.
|
|
18
|
+
* `createQueryDebugState` is intentionally NOT suppressed: barrier statements
|
|
19
|
+
* still appear in the debug request tree, and a browser test counts them there
|
|
20
|
+
* to prove the barrier stays conditional.
|
|
21
|
+
*/
|
|
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;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A Worker extended with pool-specific properties.
|
|
33
|
+
*
|
|
34
|
+
* Note: no `available` field — availability lives in the Scheduler, not on
|
|
35
|
+
* the worker itself. This makes it impossible to republish a borrowed worker
|
|
36
|
+
* from outside the scheduler (the root cause of B1).
|
|
37
|
+
*/
|
|
38
|
+
export type PoolWorker = Worker & {
|
|
39
|
+
index: number;
|
|
40
|
+
/** Lifecycle label for the debug surface. Replaces the SAB status byte. */
|
|
41
|
+
status: string;
|
|
42
|
+
/**
|
|
43
|
+
* The commit epoch this connection has absorbed. Starts at -1: a worker
|
|
44
|
+
* opens the file — and reads page 1 — BEFORE it enters the pool, and a
|
|
45
|
+
* commit can land in between. At poolSize 2 that is the nominal startup
|
|
46
|
+
* ordering, not a rare race, so a new worker is always treated as behind and
|
|
47
|
+
* pays exactly one barrier statement in its lifetime.
|
|
48
|
+
*/
|
|
49
|
+
seen: number;
|
|
50
|
+
/** The epoch captured when the current lease was granted. */
|
|
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;
|
|
64
|
+
query: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: PoolWorkerQueryOptions) => AsyncGenerator<T[] | number>;
|
|
65
|
+
/**
|
|
66
|
+
* Ask the worker to stop. Also settles a `next()` already in flight, which
|
|
67
|
+
* is what lets the consumer's queued `return()` reach the generator's
|
|
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.
|
|
76
|
+
*/
|
|
77
|
+
interrupt: (on: object) => void;
|
|
78
|
+
/** Resolves when no query is in flight on this worker. */
|
|
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>;
|
|
86
|
+
/** Posts `close`, awaits the `closed` reply, then the caller must terminate. */
|
|
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;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Returns a SQLiteError('BUSY', …) when data carries a lock-conflict result
|
|
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.
|
|
116
|
+
*/
|
|
117
|
+
export declare const busyFromCode: (data: {
|
|
118
|
+
message: string;
|
|
119
|
+
cause?: unknown;
|
|
120
|
+
sqliteCode?: SQLiteResultCode;
|
|
121
|
+
sqliteExtendedCode?: number;
|
|
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;
|
|
149
|
+
/**
|
|
150
|
+
* The single `new Worker(new URL(…))` expression in this package.
|
|
151
|
+
*
|
|
152
|
+
* It must stay one literal, in one place: bundlers find the worker by static
|
|
153
|
+
* analysis of exactly this shape, and a second copy would have them emit a
|
|
154
|
+
* second, untransformed worker bundle. `pool.ts:191` records what that cost
|
|
155
|
+
* when the expression was written a second time for an error message.
|
|
156
|
+
*/
|
|
157
|
+
export declare const spawnWorker: (name: string) => Worker;
|
|
158
|
+
/**
|
|
159
|
+
* Creates a new pool worker and registers it in the pool array.
|
|
160
|
+
* Sets up message routing via callId for query responses.
|
|
161
|
+
*
|
|
162
|
+
* Moved verbatim from `createWorker` in client.ts, with three changes:
|
|
163
|
+
* 1. Closure variables become explicit `deps` parameters.
|
|
164
|
+
* 2. Both `available` assignments are deleted (availability lives in the Scheduler).
|
|
165
|
+
* 3. `worker.available = false/true` in the `query` generator are deleted.
|
|
166
|
+
*/
|
|
167
|
+
export declare const createPoolWorker: (deps: {
|
|
168
|
+
index: number;
|
|
169
|
+
pool: (PoolWorker | undefined)[];
|
|
170
|
+
clientName: string;
|
|
171
|
+
file: string;
|
|
172
|
+
vfs: SQLiteVFS;
|
|
173
|
+
build: SQLiteBuild;
|
|
174
|
+
/** Already resolved and absolute; relayed to the worker, never read here. */
|
|
175
|
+
wasm?: WasmLocation | undefined;
|
|
176
|
+
pragmas?: Record<string, string> | undefined;
|
|
177
|
+
statementCacheSize?: number | undefined;
|
|
178
|
+
statementCacheBytes?: number | undefined;
|
|
179
|
+
onDeath?: (index: number, error: SQLiteError) => void;
|
|
180
|
+
onServed?: (index: number) => void;
|
|
181
|
+
drainTimeout: number;
|
|
182
|
+
createWorkerDebugState?: ((index: number, name: string) => any) | undefined;
|
|
183
|
+
createQueryDebugState?: ((index: number, sql: string, params?: unknown[]) => any) | undefined;
|
|
184
|
+
logger: Logger;
|
|
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>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type AbandonRegistry } from './abandon';
|
|
2
|
+
import type { SQLiteChunkOptions, SQLiteQueryOptions } from './api';
|
|
3
|
+
import type { PoolWorker } from './pool';
|
|
4
|
+
/**
|
|
5
|
+
* Wires an AbortSignal into a promise that rejects the instant the signal
|
|
6
|
+
* fires, and returns a teardown that removes the listener. The rejection sink
|
|
7
|
+
* (`aborted?.catch`) suppresses the unhandled-rejection when the query ends
|
|
8
|
+
* normally and nobody is racing the promise any more.
|
|
9
|
+
*
|
|
10
|
+
* This is the only place in the module that reads an AbortSignal; both
|
|
11
|
+
* `chunk()` and `writeWorker()` delegate here.
|
|
12
|
+
*/
|
|
13
|
+
export declare const makeAbortRace: (signal: AbortSignal | undefined) => {
|
|
14
|
+
aborted: Promise<never> | undefined;
|
|
15
|
+
teardown: () => void;
|
|
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
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The single query primitive. Every other read path is a thin derivation, and
|
|
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.
|
|
50
|
+
*/
|
|
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>;
|
|
53
|
+
export declare const readWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteChunkOptions) => Promise<T[]>;
|
|
54
|
+
/**
|
|
55
|
+
* First row, then stop. This BREAKS rather than aborting: a break triggers the
|
|
56
|
+
* generator's return path, which runs chunk()'s finally and the transport's
|
|
57
|
+
* stop-and-drain — the same worker-stop routine, reached without an exception.
|
|
58
|
+
* That is why there is no internal AbortController here and no need to tell an
|
|
59
|
+
* internal abort from the caller's.
|
|
60
|
+
*/
|
|
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<{
|
|
63
|
+
result: T[];
|
|
64
|
+
affected: number;
|
|
65
|
+
}>;
|