/** * 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; /** * runs a query on the writer, resolving the first row (or undefined). */ get: (sql: string, params?: import('./native.js').BindParams) => Promise; /** * runs a statement inside the transaction, resolving `{lastID, changes, lastIDBigInt}`. */ write: (sql: string, params?: import('./native.js').BindParams) => Promise; /** * runs raw SQL (DDL, pragmas) inside the transaction. */ exec: (sql: string) => Promise; }; /** * 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} the opened pool. */ static create(filename: string, options: { readers: number; walMode: boolean; busyTimeout: number; integerMode: ('number' | 'bigint' | 'mixed') | undefined; }): Promise; /** * 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} 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; /** * 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} 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; /** * 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} 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; /** * 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} 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; /** * 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} 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} 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(fn: (tx: PoolTransaction) => T | Promise, options?: { mode?: 'deferred' | 'immediate' | 'exclusive'; }): Promise; /** * 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} resolves once every worker has exited. * @since 9.0.0 * @example * await pool.close(); */ close(): Promise; /** * `await using pool` — disposes via {@link SqlitePool#close}. * * @returns {Promise} resolves once the pool is closed. * @since 9.0.0 */ [Symbol.asyncDispose](): Promise; } /** * 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} 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; export { pool, SqlitePool };