experiments in a post-browser web
at main 125 lines 2.6 kB view raw
1/** 2 * better-sqlite3 Adapter Implementation 3 * 4 * Wraps the better-sqlite3 library to conform to the SqlAdapter interface. 5 * Provides statement caching for improved performance. 6 */ 7 8const Database = require("better-sqlite3"); 9 10/** 11 * @typedef {import('./types').SqlAdapter} SqlAdapter 12 * @typedef {import('./types').SqlAdapterFactory} SqlAdapterFactory 13 * @typedef {import('./types').RunResult} RunResult 14 */ 15 16/** 17 * @implements {SqlAdapter} 18 */ 19class BetterSqlite3Adapter { 20 /** 21 * @param {import('better-sqlite3').Database} db 22 */ 23 constructor(db) { 24 /** @private */ 25 this.db = db; 26 /** @private @type {Map<string, import('better-sqlite3').Statement>} */ 27 this.stmtCache = new Map(); 28 } 29 30 /** 31 * @param {string} sql 32 */ 33 exec(sql) { 34 this.db.exec(sql); 35 } 36 37 /** 38 * @param {string} sql 39 * @param {unknown[]} [params=[]] 40 * @returns {RunResult} 41 */ 42 run(sql, params = []) { 43 const stmt = this.getOrPrepare(sql); 44 const result = stmt.run(...params); 45 return { 46 changes: result.changes, 47 lastInsertRowid: Number(result.lastInsertRowid), 48 }; 49 } 50 51 /** 52 * @template T 53 * @param {string} sql 54 * @param {unknown[]} [params=[]] 55 * @returns {T|null} 56 */ 57 get(sql, params = []) { 58 const stmt = this.getOrPrepare(sql); 59 return stmt.get(...params) ?? null; 60 } 61 62 /** 63 * @template T 64 * @param {string} sql 65 * @param {unknown[]} [params=[]] 66 * @returns {T[]} 67 */ 68 all(sql, params = []) { 69 const stmt = this.getOrPrepare(sql); 70 return stmt.all(...params); 71 } 72 73 /** 74 * @template T 75 * @param {function(): T} fn 76 * @returns {T} 77 */ 78 transaction(fn) { 79 return this.db.transaction(fn)(); 80 } 81 82 close() { 83 this.stmtCache.clear(); 84 this.db.close(); 85 } 86 87 /** 88 * @private 89 * @param {string} sql 90 * @returns {import('better-sqlite3').Statement} 91 */ 92 getOrPrepare(sql) { 93 let stmt = this.stmtCache.get(sql); 94 if (!stmt) { 95 stmt = this.db.prepare(sql); 96 this.stmtCache.set(sql, stmt); 97 } 98 return stmt; 99 } 100} 101 102/** 103 * @type {SqlAdapterFactory} 104 */ 105const factory = { 106 /** 107 * @param {string} path 108 * @param {{ readonly?: boolean }} [options] 109 * @returns {SqlAdapter} 110 */ 111 open(path, options) { 112 const dbOptions = options?.readonly ? { readonly: true } : {}; 113 const db = new Database(path, dbOptions); 114 return new BetterSqlite3Adapter(db); 115 }, 116 117 /** 118 * @param {SqlAdapter} adapter 119 */ 120 init(adapter) { 121 adapter.exec("PRAGMA journal_mode = WAL"); 122 }, 123}; 124 125module.exports = { factory, BetterSqlite3Adapter };