import { queueOperationAsync, throwIfDatabaseIsNotOpen } from '../DatabaseQueue' import type { Transaction, SQLiteQueryParams, QueryResult, QueryResultRow, } from '../types' import { execute, executeAsync } from './execute' import NitroSQLiteError from '../NitroSQLiteError' export const transaction = async ( dbName: string, transactionCallback: (tx: Transaction) => Promise, isExclusive = false, ) => { throwIfDatabaseIsNotOpen(dbName) let isFinished = false const executeOnTransaction = ( query: string, params?: SQLiteQueryParams, ): QueryResult => { if (isFinished) { throw new NitroSQLiteError( `Cannot execute query on finalized transaction: ${dbName}`, ) } return execute(dbName, query, params) } const executeAsyncOnTransaction = ( query: string, params?: SQLiteQueryParams, ): Promise> => { if (isFinished) { throw new NitroSQLiteError( `Cannot execute query on finalized transaction: ${dbName}`, ) } return executeAsync(dbName, query, params) } const commit = () => { if (isFinished) { throw new NitroSQLiteError( `Cannot execute commit on finalized transaction: ${dbName}`, ) } isFinished = true return execute(dbName, 'COMMIT') } const rollback = () => { if (isFinished) { throw new NitroSQLiteError( `Cannot execute rollback on finalized transaction: ${dbName}`, ) } isFinished = true return execute(dbName, 'ROLLBACK') } return await queueOperationAsync(dbName, async () => { try { await executeAsync( dbName, isExclusive ? 'BEGIN EXCLUSIVE TRANSACTION' : 'BEGIN TRANSACTION', ) const result = await transactionCallback({ commit, execute: executeOnTransaction, executeAsync: executeAsyncOnTransaction, rollback, }) if (!isFinished) commit() return result } catch (executionError) { if (!isFinished) { try { rollback() } catch (rollbackError) { throw NitroSQLiteError.fromError(rollbackError) } } throw NitroSQLiteError.fromError(executionError) } }) }