320 lines
13 KiB
Plaintext
320 lines
13 KiB
Plaintext
/**
|
|
* Options for {@link pool}.
|
|
*/
|
|
export type PoolOptions = {
|
|
/**
|
|
* how many read-only worker connections to
|
|
* open (default 4). 0 routes reads to the writer, serializing them
|
|
* with writes — the right shape for a write-heavy file or a
|
|
* `:memory:` database.
|
|
*/
|
|
readers?: number;
|
|
/**
|
|
* set `PRAGMA journal_mode = WAL` on the
|
|
* writer before readers connect (default true). WAL is what lets
|
|
* readers and the writer proceed concurrently; with `false` (or on a
|
|
* filesystem that refuses WAL) readers can block on the writer and
|
|
* rely on `busyTimeout`.
|
|
*/
|
|
walMode?: boolean;
|
|
/**
|
|
* `PRAGMA busy_timeout` in milliseconds
|
|
* for every connection (default 5000).
|
|
*/
|
|
busyTimeout?: number;
|
|
/**
|
|
* the integer
|
|
* conversion mode for every connection (see
|
|
* `configure('integerMode', …)`); the driver default (`'number'`)
|
|
* applies when omitted.
|
|
*/
|
|
integerMode?: 'number' | 'bigint' | 'mixed';
|
|
};
|
|
/**
|
|
* Options for a pool query.
|
|
*/
|
|
export type PoolQueryOptions = {
|
|
/**
|
|
* aborts the query: the connection
|
|
* running it is interrupted through a shared-memory flag, and the
|
|
* promise rejects with the signal's reason. As with the
|
|
* single-connection API, an abort that loses the race with a
|
|
* completing query still rejects and drops the result.
|
|
*/
|
|
signal?: AbortSignal;
|
|
};
|
|
/**
|
|
* The query surface inside {@link SqlitePool#transaction}, pinned to the
|
|
* writer connection: reads see the transaction's own uncommitted writes,
|
|
* writes join it.
|
|
*/
|
|
export type PoolTransaction = {
|
|
/**
|
|
* runs a query on the writer, resolving every row.
|
|
*/
|
|
read: (sql: string, params?: import('./native.js').BindParams) => Promise<import('./native.js').Row[]>;
|
|
/**
|
|
* runs a query on the writer, resolving the first row (or undefined).
|
|
*/
|
|
get: (sql: string, params?: import('./native.js').BindParams) => Promise<import('./native.js').Row | undefined>;
|
|
/**
|
|
* runs a statement inside the transaction, resolving `{lastID, changes, lastIDBigInt}`.
|
|
*/
|
|
write: (sql: string, params?: import('./native.js').BindParams) => Promise<import('./promises.js').PromiseRunResult>;
|
|
/**
|
|
* runs raw SQL (DDL, pragmas) inside the transaction.
|
|
*/
|
|
exec: (sql: string) => Promise<void>;
|
|
};
|
|
/**
|
|
* A worker-thread pool over one database file: a single writer
|
|
* connection plus read-only reader connections, each on its own worker.
|
|
*
|
|
* Created with {@link pool}; not a constructor users call. Every method
|
|
* is promise-only — there is no callback form, and none of the objects
|
|
* from the worker connections (`Database`, `Statement`, …) ever cross
|
|
* the boundary.
|
|
*
|
|
* Rows are structured-cloned across `postMessage`, so blob columns come
|
|
* back as plain `Uint8Array` (not `Buffer`), and large result sets pay a
|
|
* copy — the pool is shaped for many small queries, not for
|
|
* `SELECT *` over a million rows (see docs/concurrency.md).
|
|
*
|
|
* @since 9.0.0
|
|
*/
|
|
declare class SqlitePool {
|
|
#private;
|
|
/**
|
|
* @param {string} filename the database file.
|
|
* @param {{ readers: number, walMode: boolean, busyTimeout: number,
|
|
* integerMode: ('number' | 'bigint' | 'mixed') | undefined }} options
|
|
* validated options.
|
|
*/
|
|
constructor(filename: string, options: {
|
|
readers: number;
|
|
walMode: boolean;
|
|
busyTimeout: number;
|
|
integerMode: ('number' | 'bigint' | 'mixed') | undefined;
|
|
});
|
|
/**
|
|
* Spawns the workers and opens every connection.
|
|
*
|
|
* The writer connects first (it may create the file and switch it to
|
|
* WAL); the readers follow in parallel so a missing file cannot fail
|
|
* their read-only opens. On any failure every spawned worker is
|
|
* terminated and the error surfaces.
|
|
*
|
|
* @param {string} filename the database file.
|
|
* @param {{ readers: number, walMode: boolean, busyTimeout: number,
|
|
* integerMode: ('number' | 'bigint' | 'mixed') | undefined }} options
|
|
* the validated options.
|
|
* @returns {Promise<SqlitePool>} the opened pool.
|
|
*/
|
|
static create(filename: string, options: {
|
|
readers: number;
|
|
walMode: boolean;
|
|
busyTimeout: number;
|
|
integerMode: ('number' | 'bigint' | 'mixed') | undefined;
|
|
}): Promise<SqlitePool>;
|
|
/**
|
|
* The database filename the pool was created with.
|
|
*
|
|
* @returns {string} the filename.
|
|
* @since 9.0.0
|
|
*/
|
|
get filename(): string;
|
|
/**
|
|
* How many read-only connections the pool was created with.
|
|
*
|
|
* @returns {number} the reader count.
|
|
* @since 9.0.0
|
|
*/
|
|
get readers(): number;
|
|
/**
|
|
* True once close() has started (the pool refuses new work).
|
|
*
|
|
* @returns {boolean} whether close() has started.
|
|
* @since 9.0.0
|
|
*/
|
|
get closed(): boolean;
|
|
/**
|
|
* Runs a query on a reader connection, resolving every row. With
|
|
* `readers: 0` the query runs on the writer, serialized with writes
|
|
* (and is refused inside a transaction body — use the tx handle).
|
|
*
|
|
* Reads on reader connections see committed data only — including,
|
|
* under WAL, data a running transaction on the writer has written
|
|
* but not committed.
|
|
*
|
|
* @param {...any} args the SQL query, then optionally bind
|
|
* parameters (one array or named-parameter object), then
|
|
* optionally a trailing `{ signal }` options object.
|
|
* @returns {Promise<import('./native.js').Row[]>} every result row.
|
|
* @throws {TypeError} when the SQL is not a non-empty string or the
|
|
* arguments are malformed.
|
|
* @since 9.0.0
|
|
* @example
|
|
* const rows = await pool.read('SELECT id FROM users WHERE name = ?', ['alice']);
|
|
*/
|
|
read(...args: any[]): Promise<import('./native.js').Row[]>;
|
|
/**
|
|
* Runs a query on a reader connection, resolving the first row (or
|
|
* `undefined`). Same routing and visibility rules as
|
|
* {@link SqlitePool#read}.
|
|
*
|
|
* @param {...any} args the SQL query, then optionally bind
|
|
* parameters (one array or named-parameter object), then
|
|
* optionally a trailing `{ signal }` options object.
|
|
* @returns {Promise<import('./native.js').Row | undefined>} the first row, or undefined.
|
|
* @throws {TypeError} when the SQL is not a non-empty string or the
|
|
* arguments are malformed.
|
|
* @since 9.0.0
|
|
* @example
|
|
* const user = await pool.get('SELECT id FROM users WHERE name = ?', ['alice']);
|
|
*/
|
|
get(...args: any[]): Promise<import('./native.js').Row | undefined>;
|
|
/**
|
|
* Runs a statement on the writer connection, resolving
|
|
* `{lastID, changes, lastIDBigInt}`. Writes serialize: concurrent
|
|
* calls queue on the single writer instead of racing each other to
|
|
* SQLITE_BUSY.
|
|
*
|
|
* @param {...any} args the SQL statement, then optionally bind
|
|
* parameters (one array or named-parameter object), then
|
|
* optionally a trailing `{ signal }` options object.
|
|
* @returns {Promise<import('./promises.js').PromiseRunResult>} the run result.
|
|
* @throws {TypeError} when the SQL is not a non-empty string or the
|
|
* arguments are malformed.
|
|
* @since 9.0.0
|
|
* @example
|
|
* const result = await pool.write('INSERT INTO users (name) VALUES (?)', ['alice']);
|
|
*/
|
|
write(...args: any[]): Promise<import('./promises.js').PromiseRunResult>;
|
|
/**
|
|
* Runs raw SQL on the writer connection: DDL, pragmas,
|
|
* multi-statement scripts. Resolves once every statement has run.
|
|
*
|
|
* @param {string} sql the SQL to execute.
|
|
* @returns {Promise<void>} resolves when the SQL has run.
|
|
* @throws {TypeError} when the SQL is not a non-empty string.
|
|
* @since 9.0.0
|
|
* @example
|
|
* await pool.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
*/
|
|
exec(sql: string): Promise<void>;
|
|
/**
|
|
* Runs `fn` as one transaction on the writer connection, with
|
|
* `ROLLBACK` on failure. The whole transaction is pinned to the
|
|
* writer: the {@link PoolTransaction} handle `fn` receives routes
|
|
* every query there, its reads see the transaction's own uncommitted
|
|
* writes, and no other write can interleave inside it.
|
|
*
|
|
* Transactions serialize — a second `transaction()` call waits for
|
|
* the first to commit or roll back. Reads issued as `pool.read()`
|
|
* inside the body still run on the readers and still see committed
|
|
* data only. Inside the body, use the tx handle rather than
|
|
* `pool.write()`/`pool.exec()` (they refuse, rather than wait on the
|
|
* transaction they would be joining).
|
|
*
|
|
* @template T
|
|
* @param {(tx: PoolTransaction) => T | Promise<T>} fn the transaction body.
|
|
* @param {{ mode?: 'deferred' | 'immediate' | 'exclusive' }} [options]
|
|
* `mode` selects the BEGIN form (default `'deferred'`; use
|
|
* `'immediate'` to take the write lock up front).
|
|
* @returns {Promise<T>} whatever the body resolves to.
|
|
* @throws {TypeError} when `fn` is not a function or the options are
|
|
* malformed.
|
|
* @throws {AggregateError} when the body and the ROLLBACK both fail.
|
|
* @since 9.0.0
|
|
* @example
|
|
* await pool.transaction(async (tx) => {
|
|
* const user = await tx.get('SELECT id FROM users WHERE name = ?', ['alice']);
|
|
* await tx.write('INSERT INTO logs (user_id) VALUES (?)', [user.id]);
|
|
* });
|
|
*/
|
|
transaction<T>(fn: (tx: PoolTransaction) => T | Promise<T>, options?: {
|
|
mode?: 'deferred' | 'immediate' | 'exclusive';
|
|
}): Promise<T>;
|
|
/**
|
|
* Closes the pool: refuses new work, waits for every in-flight
|
|
* operation (including running transactions) to settle, closes every
|
|
* connection on its worker, and waits for every worker to exit. No
|
|
* worker survives close().
|
|
*
|
|
* Idempotent — every call returns the same promise. Refused from
|
|
* inside a transaction body (it would wait on that transaction).
|
|
*
|
|
* @returns {Promise<void>} resolves once every worker has exited.
|
|
* @since 9.0.0
|
|
* @example
|
|
* await pool.close();
|
|
*/
|
|
close(): Promise<void>;
|
|
/**
|
|
* `await using pool` — disposes via {@link SqlitePool#close}.
|
|
*
|
|
* @returns {Promise<void>} resolves once the pool is closed.
|
|
* @since 9.0.0
|
|
*/
|
|
[Symbol.asyncDispose](): Promise<void>;
|
|
}
|
|
/**
|
|
* Creates a worker-thread pool over a database file: one writer
|
|
* connection plus `options.readers` read-only connections (default 4),
|
|
* each on its own worker. Writes serialize on the writer; reads fan out
|
|
* to the readers. All SQLite work happens off the calling thread.
|
|
*
|
|
* Requires a real file (or a `file:` URI): each connection is separate,
|
|
* so a plain `:memory:` database cannot be shared across the pool's
|
|
* workers — move in-memory data with `db.serializeToBytes()` +
|
|
* {@link sqlite3.deserializeFromBytes} in a worker instead (see
|
|
* docs/concurrency.md). With `readers: 0` a `:memory:` pool is fine:
|
|
* everything runs on the single writer.
|
|
*
|
|
* A `file:` URI is opened as a URI (the workers set `OPEN_URI` for it),
|
|
* so `mode`, `immutable` and `cache` are honoured. A URI that asserts
|
|
* read-only access (`mode=ro`, `immutable=1`) opens **every** connection
|
|
* read-only, the writer included: with `OPEN_CREATE` the writer would
|
|
* create the database the caller declared immutable, so a missing file
|
|
* fails with `SQLITE_CANTOPEN` instead of coming back as an empty one.
|
|
* Such a URI, and an in-memory database, also turn the WAL default off by
|
|
* themselves (a read-only connection cannot switch journal mode, and an
|
|
* in-memory database has no journal); asking for `walMode: true` there is
|
|
* refused up front rather than failing every worker at
|
|
* `PRAGMA journal_mode`.
|
|
*
|
|
* Pool queries are **not** visible to {@link sqlite3.subscribeQueries}:
|
|
* each worker loads its own instance of this module, so spans are
|
|
* published on the worker's channels and never reach a main-thread
|
|
* subscriber. See docs/concurrency.md.
|
|
*
|
|
* With `cache=shared` (the only way the workers can share an in-memory
|
|
* database) SQLite locks per table, and a read colliding with the
|
|
* writer's open transaction fails with `SQLITE_LOCKED_SHAREDCACHE`
|
|
* outside the busy handler's reach — such a **read** is retried within
|
|
* the `busyTimeout` budget (`busyTimeout: 0` fails fast); writes and
|
|
* `exec` are not, since re-running them is not safe.
|
|
*
|
|
* @param {string} filename the database file, or a `file:` URI.
|
|
* @param {PoolOptions} [options] the pool options.
|
|
* @returns {Promise<SqlitePool>} the opened pool.
|
|
* @throws {TypeError} when the filename is missing or malformed, or an
|
|
* option is unknown/invalid.
|
|
* @since 9.0.0
|
|
* @example
|
|
* const pool = await sqlite3.pool('app.db', {
|
|
* readers: 4,
|
|
* busyTimeout: 5000,
|
|
* });
|
|
* const rows = await pool.read('SELECT * FROM t WHERE a = ?', [1]);
|
|
* await pool.write('INSERT INTO t (a) VALUES (?)', [2]);
|
|
* await pool.close();
|
|
* @example
|
|
* // A read-only pool over a shipped database, canonical URI form:
|
|
* const ro = await sqlite3.pool('file:/data/vdb.sqlite?mode=ro', {
|
|
* readers: 2,
|
|
* });
|
|
*/
|
|
declare function pool(filename: string, options?: PoolOptions): Promise<SqlitePool>;
|
|
export { pool, SqlitePool };
|