providerrefactor part1
This commit is contained in:
134
src/providers/sqlite3/index.js
Normal file
134
src/providers/sqlite3/index.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Provider for a caller-owned `better-sqlite3` Database instance.
|
||||
*
|
||||
* The driver is synchronous, but this adapter exposes the asynchronous
|
||||
* provider contract used by LibDashStore.
|
||||
*/
|
||||
|
||||
function bindParams(params) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [
|
||||
key.replace(/^[@:$]/, ''),
|
||||
value === undefined ? null : value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeInsertId(value) {
|
||||
if (value === undefined || value === null) return null;
|
||||
if (typeof value !== 'bigint') return value;
|
||||
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
|
||||
}
|
||||
|
||||
export class BetterSqlite3Executor {
|
||||
/** @param {import('better-sqlite3').Database} db */
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('../provider.js').QueryResult>} */
|
||||
async query(sql, params = {}) {
|
||||
const statement = this.db.prepare(sql);
|
||||
const bindings = bindParams(params);
|
||||
|
||||
if (statement.reader) {
|
||||
const rows = statement.all(bindings);
|
||||
return {
|
||||
rows,
|
||||
rowsAffected: rows.length,
|
||||
insertId: normalizeInsertId(rows[0]?.id),
|
||||
};
|
||||
}
|
||||
|
||||
const result = statement.run(bindings);
|
||||
return {
|
||||
rows: [],
|
||||
rowsAffected: result.changes,
|
||||
insertId: normalizeInsertId(result.lastInsertRowid),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class BetterSqlite3Provider extends BetterSqlite3Executor {
|
||||
/**
|
||||
* @param {import('better-sqlite3').Database} db An open, caller-owned database.
|
||||
*/
|
||||
constructor(db) {
|
||||
if (
|
||||
!db ||
|
||||
typeof db.prepare !== 'function' ||
|
||||
typeof db.exec !== 'function' ||
|
||||
typeof db.pragma !== 'function'
|
||||
) {
|
||||
throw new TypeError(
|
||||
'BetterSqlite3Provider requires an open better-sqlite3 Database',
|
||||
);
|
||||
}
|
||||
|
||||
super(db);
|
||||
this.dialect = 'sqlite3';
|
||||
db.pragma('foreign_keys = ON');
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('busy_timeout = 5000');
|
||||
}
|
||||
|
||||
#transactionTail = Promise.resolve();
|
||||
|
||||
/**
|
||||
* Runs `fn(runner)` in an immediate write transaction. Transactions are
|
||||
* serialized because better-sqlite3 has one synchronous connection.
|
||||
*
|
||||
* @template T
|
||||
* @param {(runner: BetterSqlite3Executor) => Promise<T> | T} fn
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
transaction(fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
return Promise.reject(new TypeError('transaction requires a callback'));
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
this.db.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
const result = await fn(new BetterSqlite3Executor(this.db));
|
||||
this.db.exec('COMMIT');
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (this.db.inTransaction) this.db.exec('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const pending = this.#transactionTail.then(run, run);
|
||||
this.#transactionTail = pending.catch(() => undefined);
|
||||
return pending;
|
||||
}
|
||||
|
||||
async exec() {
|
||||
throw new Error('Stored procedures are not supported by better-sqlite3');
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
// SQLite migrations are provided by the SQLite schema layer.
|
||||
}
|
||||
|
||||
async purge() {
|
||||
// SQLite schema teardown is provided by the SQLite schema layer.
|
||||
}
|
||||
|
||||
/** Explicitly closes the caller-owned database. It is never closed implicitly. */
|
||||
close() {
|
||||
this.db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export function isUniqueConstraintError(err) {
|
||||
return (
|
||||
err?.code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
||||
err?.code === 'SQLITE_CONSTRAINT_PRIMARYKEY'
|
||||
);
|
||||
}
|
||||
|
||||
export function isForeignKeyConstraintError(err) {
|
||||
return err?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY';
|
||||
}
|
||||
Reference in New Issue
Block a user