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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/NOTICE +56 -0
  2. package/README.md +435 -63
  3. package/dist/LICENSE +21 -0
  4. package/dist/NOTICE +56 -0
  5. package/dist/api.d.ts +376 -0
  6. package/dist/bulk.d.ts +42 -0
  7. package/dist/capabilities.d.ts +23 -0
  8. package/dist/client.d.ts +198 -0
  9. package/dist/credits.d.ts +31 -0
  10. package/dist/{esm/src/debug.d.ts → debug.d.ts} +21 -10
  11. package/dist/delete.d.ts +42 -0
  12. package/dist/epochs.d.ts +55 -0
  13. package/dist/errors.d.ts +37 -0
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +5 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/locks.d.ts +54 -0
  18. package/dist/logger.d.ts +24 -0
  19. package/dist/pool.d.ts +98 -0
  20. package/dist/queries.d.ts +36 -0
  21. package/dist/scheduler.d.ts +131 -0
  22. package/dist/supervisor.d.ts +17 -0
  23. package/dist/transaction.d.ts +38 -0
  24. package/dist/types.d.ts +348 -0
  25. package/dist/utils.d.ts +116 -0
  26. package/dist/worker/cloneable.d.ts +25 -0
  27. package/dist/worker/statement-cache.d.ts +22 -0
  28. package/dist/worker/wa-sqlite-async.wasm +0 -0
  29. package/dist/worker/wa-sqlite-jspi.wasm +0 -0
  30. package/dist/worker/wa-sqlite.wasm +0 -0
  31. package/dist/worker/worker.js +11 -0
  32. package/dist/worker/worker.js.map +1 -0
  33. package/package.json +36 -20
  34. package/dist/esm/index.js +0 -424
  35. package/dist/esm/rslib.config.d.ts +0 -2
  36. package/dist/esm/rstest.config.d.ts +0 -2
  37. package/dist/esm/src/client.d.ts +0 -332
  38. package/dist/esm/src/index.d.ts +0 -1
  39. package/dist/esm/src/orchestrator.d.ts +0 -87
  40. package/dist/esm/src/types.d.ts +0 -83
  41. package/dist/esm/src/utils.d.ts +0 -6
  42. /package/dist/{esm/src → worker}/worker.d.ts +0 -0
@@ -0,0 +1,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,98 @@
1
+ import { SQLiteError } from './errors';
2
+ import type { Logger } from './logger';
3
+ import type { SQLiteBuild, SQLiteVFS, WasmLocation } from './types';
4
+ /**
5
+ * Query execution options forwarded to a pool worker.
6
+ */
7
+ export type PoolWorkerQueryOptions = {
8
+ chunkSize?: number | undefined;
9
+ credits?: number | undefined;
10
+ /**
11
+ * When true, the query's completion does not call `deps.onServed`. Set for
12
+ * the commit-propagation barrier: it is a synthetic probe, not user work, and
13
+ * must not reset the supervisor's restart counter.
14
+ * `createQueryDebugState` is intentionally NOT suppressed: barrier statements
15
+ * still appear in the debug request tree, and a browser test counts them there
16
+ * to prove the barrier stays conditional.
17
+ */
18
+ noServed?: boolean;
19
+ };
20
+ /**
21
+ * A Worker extended with pool-specific properties.
22
+ *
23
+ * Note: no `available` field — availability lives in the Scheduler, not on
24
+ * the worker itself. This makes it impossible to republish a borrowed worker
25
+ * from outside the scheduler (the root cause of B1).
26
+ */
27
+ export type PoolWorker = Worker & {
28
+ index: number;
29
+ /** Lifecycle label for the debug surface. Replaces the SAB status byte. */
30
+ status: string;
31
+ /**
32
+ * The commit epoch this connection has absorbed. Starts at -1: a worker
33
+ * opens the file — and reads page 1 — BEFORE it enters the pool, and a
34
+ * commit can land in between. At poolSize 2 that is the nominal startup
35
+ * ordering, not a rare race, so a new worker is always treated as behind and
36
+ * pays exactly one barrier statement in its lifetime.
37
+ */
38
+ seen: number;
39
+ /** The epoch captured when the current lease was granted. */
40
+ epochTarget: number;
41
+ query: <T extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[], options?: PoolWorkerQueryOptions) => AsyncGenerator<T[] | number>;
42
+ /**
43
+ * Ask the worker to stop. Also settles a `next()` already in flight, which
44
+ * is what lets the consumer's queued `return()` reach the generator's
45
+ * finally instead of waiting behind a chunk that may be minutes away.
46
+ */
47
+ interrupt: () => void;
48
+ /** Resolves when no query is in flight on this worker. */
49
+ quiesce: () => Promise<void>;
50
+ /** Posts `close`, awaits the `closed` reply, then the caller must terminate. */
51
+ close: () => Promise<void>;
52
+ };
53
+ /**
54
+ * Returns a SQLiteError('BUSY', …) when data carries a lock-conflict result
55
+ * code (5 or 6), else undefined. Shared by both the query-error and
56
+ * open-error paths so the BUSY_CODES decision lives in exactly one place.
57
+ */
58
+ export declare const busyFromCode: (data: {
59
+ message: string;
60
+ cause?: unknown;
61
+ sqliteCode?: number;
62
+ }) => SQLiteError | undefined;
63
+ /**
64
+ * The single `new Worker(new URL(…))` expression in this package.
65
+ *
66
+ * It must stay one literal, in one place: bundlers find the worker by static
67
+ * analysis of exactly this shape, and a second copy would have them emit a
68
+ * second, untransformed worker bundle. `pool.ts:191` records what that cost
69
+ * when the expression was written a second time for an error message.
70
+ */
71
+ export declare const spawnWorker: (name: string) => Worker;
72
+ /**
73
+ * Creates a new pool worker and registers it in the pool array.
74
+ * Sets up message routing via callId for query responses.
75
+ *
76
+ * Moved verbatim from `createWorker` in client.ts, with three changes:
77
+ * 1. Closure variables become explicit `deps` parameters.
78
+ * 2. Both `available` assignments are deleted (availability lives in the Scheduler).
79
+ * 3. `worker.available = false/true` in the `query` generator are deleted.
80
+ */
81
+ export declare const createPoolWorker: (deps: {
82
+ index: number;
83
+ pool: (PoolWorker | undefined)[];
84
+ clientPrefix: string;
85
+ file: string;
86
+ vfs: SQLiteVFS;
87
+ build: SQLiteBuild;
88
+ /** Already resolved and absolute; relayed to the worker, never read here. */
89
+ wasm?: WasmLocation | undefined;
90
+ pragmas?: Record<string, string> | undefined;
91
+ statementCacheSize?: number | undefined;
92
+ onDeath?: (index: number, error: SQLiteError) => void;
93
+ onServed?: (index: number) => void;
94
+ drainTimeout: number;
95
+ createWorkerDebugState?: ((index: number, name: string) => any) | undefined;
96
+ createQueryDebugState?: ((index: number, sql: string, params?: unknown[]) => any) | undefined;
97
+ logger: Logger;
98
+ }) => Promise<PoolWorker>;
@@ -0,0 +1,36 @@
1
+ import type { OptionsWithSignal, SQLiteChunkOptions } from './api';
2
+ import type { PoolWorker } from './pool';
3
+ /**
4
+ * Wires an AbortSignal into a promise that rejects the instant the signal
5
+ * fires, and returns a teardown that removes the listener. The rejection sink
6
+ * (`aborted?.catch`) suppresses the unhandled-rejection when the query ends
7
+ * normally and nobody is racing the promise any more.
8
+ *
9
+ * This is the only place in the module that reads an AbortSignal; both
10
+ * `chunk()` and `writeWorker()` delegate here.
11
+ */
12
+ export declare const makeAbortRace: (signal: AbortSignal | undefined) => {
13
+ aborted: Promise<never> | undefined;
14
+ teardown: () => void;
15
+ };
16
+ /**
17
+ * The single query primitive. Every other read path is a thin derivation, and
18
+ * abort is implemented here exactly once.
19
+ */
20
+ export declare const chunk: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteChunkOptions & {
21
+ credits?: number;
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>;
24
+ export declare const readWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: SQLiteChunkOptions) => Promise<T[]>;
25
+ /**
26
+ * First row, then stop. This BREAKS rather than aborting: a break triggers the
27
+ * generator's return path, which runs chunk()'s finally and the transport's
28
+ * stop-and-drain — the same worker-stop routine, reached without an exception.
29
+ * That is why there is no internal AbortController here and no need to tell an
30
+ * internal abort from the caller's.
31
+ */
32
+ export declare const firstWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<T | undefined>;
33
+ export declare const writeWorker: <T extends Record<string, unknown> = Record<string, unknown>>(worker: PoolWorker, sql: string, params?: unknown[], options?: OptionsWithSignal) => Promise<{
34
+ result: T[];
35
+ affected: number;
36
+ }>;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Pure worker scheduling: availability, wait queues, writer designation.
3
+ *
4
+ * This module is deliberately free of `Worker` and DOM imports so
5
+ * it can be exercised by fast Node tests. B1 survived for months because the
6
+ * scheduler was only reachable through slow browser tests.
7
+ */
8
+ import type { CreateSQLiteClientOptions } from './client';
9
+ /**
10
+ * A borrowed worker. `release()` is the only way back into the pool and is
11
+ * idempotent — a second call is a no-op, not an error.
12
+ */
13
+ export type Lease<W> = {
14
+ readonly worker: W;
15
+ release: () => void;
16
+ };
17
+ /**
18
+ * Decides whether a worker index may hold the write designation. The default
19
+ * accepts every index, so production behaviour is exactly what it was.
20
+ */
21
+ export type WriterPolicy = (index: number) => boolean;
22
+ /**
23
+ * TEST-ONLY, UNSUPPORTED, removable without notice.
24
+ *
25
+ * The barrier's browser test needs the failing configuration — writer not on
26
+ * the worker that serves the read — to be deterministic; at startup chance it
27
+ * occurs ~3 runs in 10. This type is declared here, and NOT in `client.ts`,
28
+ * because `src/index.ts` re-exports only `./client` and `./errors`: keeping it
29
+ * out of that path keeps it out of the published `.d.ts` and out of every
30
+ * consumer's autocompletion. `CreateSQLiteClientOptions` is pulled in with
31
+ * `import type`, which is erased at build time and creates no runtime cycle.
32
+ *
33
+ * A predicate that refuses every index leaves writes queued forever — use it
34
+ * with `poolSize >= 2`.
35
+ */
36
+ export type InternalSQLiteClientOptions = CreateSQLiteClientOptions & {
37
+ __unsafeTestWriterPolicy?: WriterPolicy;
38
+ };
39
+ export type Scheduler<W> = {
40
+ add: (worker: W) => void;
41
+ /**
42
+ * Leases a worker, queueing when none is free.
43
+ *
44
+ * `signal` aborts the WAIT, and only the wait: it rejects with
45
+ * `signal.reason` while the request is still queued, and is ignored once a
46
+ * lease has been granted — from that point the caller owns the worker and
47
+ * owes a `release()`. Without it an abort could not land at all while the
48
+ * pool had nothing to lend, which is the state a VFS rotating one exclusive
49
+ * OPFS handle can stay in indefinitely.
50
+ */
51
+ acquire: (kind: 'read' | 'write', signal?: AbortSignal) => Promise<Lease<W>>;
52
+ /**
53
+ * Takes a worker out of the pool for good. A lease already outstanding on
54
+ * that index becomes inert: its `release()` neither hands the worker back nor
55
+ * counts towards `shutdown()`'s wait.
56
+ */
57
+ remove: (index: number) => void;
58
+ /**
59
+ * Closes the front door. Queued waiters reject with `reason`, later
60
+ * acquisitions reject the same way, and the returned promise settles when the
61
+ * last outstanding lease has come back.
62
+ */
63
+ shutdown: (reason: Error) => Promise<void>;
64
+ /**
65
+ * Read-only counters for the debug subsystem. The scheduler stays pure: it
66
+ * exposes numbers and knows nothing about debug (spec §3.2).
67
+ */
68
+ stats: () => {
69
+ read: number;
70
+ write: number;
71
+ available: number;
72
+ leased: number;
73
+ /**
74
+ * Callers suspended on the readiness gate. They are in NEITHER wait queue —
75
+ * the gate is awaited before `takeAvailable` is ever reached — so `read`
76
+ * and `write` cannot see them, and without this the debug surface reports
77
+ * an idle pool for the whole startup window.
78
+ *
79
+ * Waiting for the pool to *exist* is a different wait from waiting for a
80
+ * free worker, which is why this is its own counter and not folded in.
81
+ */
82
+ gated: number;
83
+ };
84
+ /**
85
+ * Removes a slot from the settled-set so that its next `add()` or `remove()`
86
+ * call counts again toward opening the readiness gate. Only effective while
87
+ * the gate is still closed; a no-op once the gate has opened.
88
+ *
89
+ * Used by the startup retry round: the client re-arms the failed slots so
90
+ * the gate stays closed until the retry slots have settled.
91
+ */
92
+ rearmSlot: (index: number) => void;
93
+ };
94
+ /**
95
+ * Creates a scheduler over workers identified by a numeric `index`.
96
+ *
97
+ * @param opts.onIdle - Called when a released worker returns to the available
98
+ * set with nothing queued behind it. The scheduler itself knows nothing about
99
+ * worker state.
100
+ */
101
+ export declare const createScheduler: <W extends {
102
+ index: number;
103
+ }>(opts?: {
104
+ onIdle?: (worker: W) => void;
105
+ canDesignateWriter?: WriterPolicy;
106
+ /**
107
+ * Total number of worker slots the pool will spawn. Once every slot has
108
+ * settled (via `add` when it becomes ready, or via `remove` when it dies or
109
+ * fails to open), a one-shot gate is lifted and `acquire()` may proceed.
110
+ * Omit or pass 0 for an immediately-open gate (tests and single-shot use).
111
+ */
112
+ poolSize?: number;
113
+ /**
114
+ * Called exactly once when every slot in [0, poolSize) has settled for the
115
+ * first time. Fires before the gate opens so the callback can call
116
+ * `rearmSlot()` to extend the wait for a retry round.
117
+ *
118
+ * `openedCount` — slots that settled via `add()` (became ready).
119
+ * `failedIndices` — slots that settled via `remove()` (died / timed out).
120
+ */
121
+ onFirstSettle?: (result: {
122
+ openedCount: number;
123
+ failedIndices: number[];
124
+ }) => void;
125
+ /**
126
+ * Called when the readiness gate resolves (opens). Not called when the gate
127
+ * is rejected via `shutdown()`. Use this to clear any startup-pending flag
128
+ * after the retry round (if any) has fully settled.
129
+ */
130
+ onGateOpen?: () => void;
131
+ }) => Scheduler<W>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Pure restart policy for worker slots.
3
+ *
4
+ * Deliberately free of `Worker` and DOM imports so Node tests can
5
+ * drive it in milliseconds — the same reason `scheduler.ts` is pure. B1 lived
6
+ * for months because the only way to reach the pool's decisions was a browser.
7
+ *
8
+ * The caller reports facts; this module returns a decision and never acts.
9
+ */
10
+ export type SupervisorDecision = 'restart' | 'lost' | 'fail-client';
11
+ export type Supervisor = {
12
+ report: (index: number, event: 'spawned' | 'ready' | 'served' | 'died' | 'lost') => SupervisorDecision | undefined;
13
+ };
14
+ export declare const createSupervisor: (options: {
15
+ size: number;
16
+ maxWorkerRestarts?: number | undefined;
17
+ }) => Supervisor;
@@ -0,0 +1,38 @@
1
+ import type { SQLiteQueryAPI, SQLiteTransactionDB, SQLiteTransactionOptions } from './api';
2
+ import type { ReadFn, TransactionFn, WriteFn } from './bulk';
3
+ import { SQLiteError } from './errors';
4
+ import type { PoolWorker } from './pool';
5
+ import type { Scheduler } from './scheduler';
6
+ /**
7
+ * Returns the `transaction()` method for a SQLiteDB instance.
8
+ *
9
+ * The returned function acquires exactly one lease for the full lifetime of
10
+ * the transaction. All SQLiteTransactionDB methods call worker-bound derivations
11
+ * directly — never the public API — so no secondary lease acquisition can
12
+ * occur during the callback.
13
+ */
14
+ export declare const createTransaction: (deps: {
15
+ scheduler: Scheduler<PoolWorker>;
16
+ afterWrite: (worker: PoolWorker) => void;
17
+ /**
18
+ * Called when a connection may still hold an open transaction. The worker
19
+ * is lost rather than repaired: a "dirty worker" state is one more
20
+ * state the barrier would have to reason about, while a respawned
21
+ * connection is transaction-free by construction.
22
+ */
23
+ onPoisoned: (index: number, error: SQLiteError) => void;
24
+ /**
25
+ * The client's bulk factory. Called per transaction with the transaction's
26
+ * own read/write and a pass-through `transaction`, so output()'s swap runs
27
+ * on the caller's transaction instead of opening a BEGIN SQLite does not
28
+ * allow.
29
+ */
30
+ bulkFor: (target: {
31
+ read: ReadFn;
32
+ write: WriteFn;
33
+ transaction: TransactionFn;
34
+ }) => {
35
+ bulkWrite: SQLiteQueryAPI['bulkWrite'];
36
+ output: SQLiteQueryAPI['output'];
37
+ };
38
+ }) => <T = void>(callback: (db: SQLiteTransactionDB) => Promise<T>, options?: SQLiteTransactionOptions) => Promise<T>;