auth

Main authentication module for my projects
git clone git://git.finwo.net/app/auth
Log | Files | Refs

sqlite.js (3102B)


      1 // src/lib/db/sqlite.js - sqlite driver built on node:sqlite.
      2 //
      3 // node:sqlite is synchronous. The methods are async so callers use one
      4 // interface for every driver.
      5 
      6 import { DatabaseSync } from 'node:sqlite';
      7 import { compileCached, bindParams } from './query.js';
      8 
      9 export function openSqlite(cfg) {
     10   const db = new DatabaseSync(cfg.database.path);
     11 
     12   // WAL keeps readers from blocking the writer.
     13   db.exec('PRAGMA journal_mode = WAL');
     14   db.exec('PRAGMA foreign_keys = ON');
     15   db.exec('PRAGMA busy_timeout = 5000');
     16   db.exec('PRAGMA synchronous = NORMAL');
     17 
     18   const statements = new Map();
     19 
     20   function prepare(sql) {
     21     const compiled = compileCached(sql, 'sqlite');
     22     let stmt = statements.get(compiled.sql);
     23     if (!stmt) {
     24       stmt = db.prepare(compiled.sql);
     25       statements.set(compiled.sql, stmt);
     26     }
     27     return { stmt, keys: compiled.keys };
     28   }
     29 
     30   function fail(e, sql) {
     31     const err = new Error(`sqlite: ${e.message}\n  sql: ${sql.trim().split('\n')[0]}`);
     32     err.cause = e;
     33     err.code = e.code;
     34     throw err;
     35   }
     36 
     37   // node:sqlite returns null-prototype objects.
     38   const toPlain = (row) => (row === undefined ? undefined : { ...row });
     39 
     40   const api = {
     41     dialect: 'sqlite',
     42 
     43     async all(sql, params = {}) {
     44       const { stmt, keys } = prepare(sql);
     45       const values = bindParams(keys, params, sql);
     46       try {
     47         return stmt.all(...values).map(toPlain);
     48       } catch (e) {
     49         fail(e, sql);
     50       }
     51     },
     52 
     53     async get(sql, params = {}) {
     54       const { stmt, keys } = prepare(sql);
     55       const values = bindParams(keys, params, sql);
     56       try {
     57         return toPlain(stmt.get(...values));
     58       } catch (e) {
     59         fail(e, sql);
     60       }
     61     },
     62 
     63     async run(sql, params = {}) {
     64       const { stmt, keys } = prepare(sql);
     65       const values = bindParams(keys, params, sql);
     66       try {
     67         const r = stmt.run(...values);
     68         return {
     69           changes: Number(r.changes),
     70           lastInsertId: r.lastInsertRowid === undefined ? null : Number(r.lastInsertRowid),
     71         };
     72       } catch (e) {
     73         fail(e, sql);
     74       }
     75     },
     76 
     77     // Raw DDL, no bind markers are interpreted.
     78     async exec(sql) {
     79       try {
     80         db.exec(sql);
     81       } catch (e) {
     82         fail(e, sql);
     83       }
     84     },
     85 
     86     async close() {
     87       statements.clear();
     88       db.close();
     89     },
     90   };
     91 
     92   // Nesting uses savepoints so that a helper which opens a transaction can be
     93   // composed inside a larger one.
     94   let depth = 0;
     95   api.transaction = async function transaction(fn) {
     96     const name = `sp_${depth}`;
     97     if (depth === 0) db.exec('BEGIN IMMEDIATE');
     98     else db.exec(`SAVEPOINT ${name}`);
     99     depth += 1;
    100     try {
    101       const result = await fn(api);
    102       depth -= 1;
    103       if (depth === 0) db.exec('COMMIT');
    104       else db.exec(`RELEASE ${name}`);
    105       return result;
    106     } catch (e) {
    107       depth -= 1;
    108       try {
    109         if (depth === 0) db.exec('ROLLBACK');
    110         else db.exec(`ROLLBACK TO ${name}`);
    111       } catch {
    112         // The original error is more useful than a rollback failure.
    113       }
    114       throw e;
    115     }
    116   };
    117 
    118   return api;
    119 }