import { Database , Options as DatabaseOptions } from 'better-sqlite3' ; import { DatabaseConnection , QueryResult , } from '../../driver/database-connection.js' ; import { Driver } from '../../driver/driver.js' ; import { CompiledQuery } from '../../query-compiler/compiled-query.js' ; import { freeze , isBoolean , isFunction , isNumber , } from '../../util/object-utils.js' ; import { SqliteDialectConfig } from './sqlite-dialect-config.js' ; export class SqliteDriver implements Driver { readonly #config : SqliteDialectConfig ; readonly #connectionMutex = new ConnectionMutex ( ) ; #db ? : Database ; #connection ? : DatabaseConnection ; constructor ( config ) { this . #config = freeze ( { ... config } ) ; } async init ( ) { const DatabaseConstructor = await importBetterSqlite3Database ( ) ; const options = { } ; if ( isBoolean ( this . #config . readonly ) ) { options . readonly = this . #config . readonly ; } if ( isBoolean ( this . #config . fileMustExist ) ) { options . fileMustExist = this . #config . fileMustExist ; } if ( isNumber ( this . #config . timeout ) ) { options . timeout = this . #config . timeout ; } if ( isFunction ( this . #config . verbose ) ) { options . verbose = this . #config . verbose ; } this . #db = new DatabaseConstructor ( this . #config . databasePath , options ) ; this . #connection = new SqliteConnection ( this . #db ) ; if ( this . #config . onCreateConnection ) { await this . #config . onCreateConnection ( this . #connection ) ; } } async acquireConnection ( ) { await this . #connectionMutex . lock ( ) ; return this . #connection ! ; } async beginTransaction ( connection ) { await connection . executeQuery ( CompiledQuery . raw ( 'begin' ) ) ; } async commitTransaction ( connection ) { await connection . executeQuery ( CompiledQuery . raw ( 'commit' ) ) ; } async rollbackTransaction ( connection ) { await connection . executeQuery ( CompiledQuery . raw ( 'rollback' ) ) ; } async releaseConnection ( ) { this . #connectionMutex . unlock ( ) ; } async destroy ( ) { this . #db ?. close ( ) ; } } type DatabaseConstructor = new ( fileName , options ) => Database ; async function importBetterSqlite3Database ( ) { try { const sqliteModule = await import ( 'better-sqlite3' ) ; if ( isFunction ( sqliteModule ) ) { return sqliteModule ; } else { return sqliteModule . default ; } } catch ( error ) { throw new Error ( 'SQLite client not installed. Please run `npm install better-sqlite3`' ) ; } } class SqliteConnection implements DatabaseConnection { readonly #db : Database ; constructor ( db ) { this . #db = db ; } executeQuery < O > ( compiledQuery ) { const { sql , parameters } = compiledQuery ; const stmt = this . #db . prepare ( sql ) ; if ( stmt . reader ) { return Promise . resolve ( { rows : stmt . all ( parameters ) , } ) ; } else { const { changes , lastInsertRowid } = stmt . run ( parameters ) ; return Promise . resolve ( { numUpdatedOrDeletedRows : changes !== undefined && changes !== null ? BigInt ( changes ) : undefined , insertId : lastInsertRowid !== undefined && lastInsertRowid !== null ? BigInt ( lastInsertRowid ) : undefined , rows : [ ] , } ) ; } } } class ConnectionMutex { #promise ? : Promise < void > ; #resolve ? : ( ) => void ; async lock ( ) { while ( this . #promise ) { await this . #promise ; } this . #promise = new Promise ( ( resolve ) => { this . #resolve = resolve ; } ) ; } unlock ( ) { const resolve = this . #resolve ; this . #promise = undefined ; this . #resolve = undefined ; resolve ?. ( ) ; } }