diff --git a/package/cpp/NitroSQLiteException.hpp b/package/cpp/NitroSQLiteException.hpp index 636d9163..41e48ab8 100644 --- a/package/cpp/NitroSQLiteException.hpp +++ b/package/cpp/NitroSQLiteException.hpp @@ -3,6 +3,9 @@ #include #include #include +#include + +const std::string NITRO_SQLITE_EXCEPTION_PREFIX = "[NativeNitroSQLiteException]"; enum NitroSQLiteExceptionType { UnknownError, @@ -33,7 +36,7 @@ class NitroSQLiteException : public std::exception { explicit NitroSQLiteException(const std::string& message) : NitroSQLiteException(NitroSQLiteExceptionType::UnknownError, message) {} NitroSQLiteException(const NitroSQLiteExceptionType& type, const char* message) : NitroSQLiteException(type, std::string(message)) {} NitroSQLiteException(const NitroSQLiteExceptionType& type, const std::string& message) - : _exceptionString("[" + typeToString(type) + "] " + message) {} + : _exceptionString(NITRO_SQLITE_EXCEPTION_PREFIX + "[" + typeToString(type) + "] " + message) {} private: const std::string _exceptionString; diff --git a/package/src/NitroSQLiteError.ts b/package/src/NitroSQLiteError.ts new file mode 100644 index 00000000..8456eedd --- /dev/null +++ b/package/src/NitroSQLiteError.ts @@ -0,0 +1,45 @@ +const NITRO_SQLITE_ERROR_NAME = 'NitroSQLiteError' as const + +/** + * Custom error class for NitroSQLite operations + * Extends the native Error class with proper prototype chain and error handling + */ +export default class NitroSQLiteError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options) + this.name = NITRO_SQLITE_ERROR_NAME + + // Maintains proper prototype chain for instanceof checks + Object.setPrototypeOf(this, NitroSQLiteError.prototype) + } + + /** + * Converts an unknown error to a NitroSQLiteError + * Preserves stack traces and error causes when available + */ + static fromError(error: unknown): NitroSQLiteError { + if (error instanceof NitroSQLiteError) { + return error + } + + if (error instanceof Error) { + const nitroSQLiteError = new NitroSQLiteError(error.message, { + cause: error.cause, + }) + + // Preserve original stack trace if available + if (error.stack) { + nitroSQLiteError.stack = error.stack + } + return nitroSQLiteError + } + + if (typeof error === 'string') { + return new NitroSQLiteError(error) + } + + return new NitroSQLiteError('Unknown error occurred', { + cause: error, + }) + } +} diff --git a/package/src/operations/execute.ts b/package/src/operations/execute.ts index 0b8e2b92..ce2d629c 100644 --- a/package/src/operations/execute.ts +++ b/package/src/operations/execute.ts @@ -8,6 +8,7 @@ import type { SQLiteQueryParams, QueryResultRow, } from '../types' +import NitroSQLiteError from '../NitroSQLiteError' export function execute( dbName: string, @@ -18,13 +19,17 @@ export function execute( ? toNativeQueryParams(params) : (params as NativeSQLiteQueryParams) - const nativeResult = HybridNitroSQLite.execute( - dbName, - query, - transformedParams, - ) - const result = buildJsQueryResult(nativeResult) - return result + try { + const nativeResult = HybridNitroSQLite.execute( + dbName, + query, + transformedParams, + ) + + return buildJsQueryResult(nativeResult) + } catch (error) { + throw NitroSQLiteError.fromError(error) + } } export async function executeAsync( @@ -36,13 +41,16 @@ export async function executeAsync( ? toNativeQueryParams(params) : (params as NativeSQLiteQueryParams) - const nativeResult = await HybridNitroSQLite.executeAsync( - dbName, - query, - transformedParams, - ) - const result = buildJsQueryResult(nativeResult) - return result + try { + const nativeResult = await HybridNitroSQLite.executeAsync( + dbName, + query, + transformedParams, + ) + return buildJsQueryResult(nativeResult) + } catch (error) { + throw NitroSQLiteError.fromError(error) + } } function toNativeQueryParams( diff --git a/package/src/operations/executeBatch.ts b/package/src/operations/executeBatch.ts index 48cd683d..ea30a624 100644 --- a/package/src/operations/executeBatch.ts +++ b/package/src/operations/executeBatch.ts @@ -9,6 +9,7 @@ import type { BatchQueryCommand, NativeBatchQueryCommand, } from '../types' +import NitroSQLiteError from '../NitroSQLiteError' export function executeBatch( dbName: string, @@ -18,8 +19,11 @@ export function executeBatch( ? toNativeBatchQueryCommands(commands) : (commands as NativeBatchQueryCommand[]) - const result = HybridNitroSQLite.executeBatch(dbName, transformedCommands) - return result + try { + return HybridNitroSQLite.executeBatch(dbName, transformedCommands) + } catch (error) { + throw NitroSQLiteError.fromError(error) + } } export async function executeBatchAsync( @@ -30,11 +34,14 @@ export async function executeBatchAsync( ? toNativeBatchQueryCommands(commands) : (commands as NativeBatchQueryCommand[]) - const result = await HybridNitroSQLite.executeBatchAsync( - dbName, - transformedCommands, - ) - return result + try { + return await HybridNitroSQLite.executeBatchAsync( + dbName, + transformedCommands, + ) + } catch (error) { + throw NitroSQLiteError.fromError(error) + } } function toNativeBatchQueryCommands( diff --git a/package/src/operations/session.ts b/package/src/operations/session.ts index 7d8ac8ec..f6fb51dd 100644 --- a/package/src/operations/session.ts +++ b/package/src/operations/session.ts @@ -11,14 +11,30 @@ import type { } from '../types' import { execute, executeAsync } from './execute' import { executeBatch, executeBatchAsync } from './executeBatch' +import NitroSQLiteError from '../NitroSQLiteError' export function open( options: NitroSQLiteConnectionOptions, ): NitroSQLiteConnection { - openDb(options.name, options.location) + try { + HybridNitroSQLite.open(options.name, options.location) + locks[options.name] = { + queue: [], + inProgress: false, + } + } catch (error) { + throw NitroSQLiteError.fromError(error) + } return { - close: () => close(options.name), + close: () => { + try { + HybridNitroSQLite.close(options.name) + delete locks[options.name] + } catch (error) { + throw NitroSQLiteError.fromError(error) + } + }, delete: () => HybridNitroSQLite.drop(options.name, options.location), attach: (dbNameToAttach: string, alias: string, location?: string) => HybridNitroSQLite.attach(options.name, dbNameToAttach, alias, location), @@ -43,17 +59,3 @@ export function open( HybridNitroSQLite.loadFileAsync(options.name, location), } } - -export function openDb(dbName: string, location?: string) { - HybridNitroSQLite.open(dbName, location) - - locks[dbName] = { - queue: [], - inProgress: false, - } -} - -export function close(dbName: string) { - HybridNitroSQLite.close(dbName) - delete locks[dbName] -} diff --git a/package/src/operations/transaction.ts b/package/src/operations/transaction.ts index be1a3f04..d57f2907 100644 --- a/package/src/operations/transaction.ts +++ b/package/src/operations/transaction.ts @@ -1,4 +1,5 @@ import { locks, HybridNitroSQLite } from '../nitro' +import NitroSQLiteError from '../NitroSQLiteError' import type { QueryResult, Transaction, @@ -25,7 +26,7 @@ export const transaction = ( fn: (tx: Transaction) => Promise | void, ): Promise => { if (locks[dbName] == null) - throw Error(`Nitro SQLite Error: No lock found on db: ${dbName}`) + throw new NitroSQLiteError(`No lock found on db: ${dbName}`) let isFinalized = false @@ -35,8 +36,8 @@ export const transaction = ( params?: SQLiteQueryParams, ): QueryResult => { if (isFinalized) { - throw Error( - `Nitro SQLite Error: Cannot execute query on finalized transaction: ${dbName}`, + throw new NitroSQLiteError( + `Cannot execute query on finalized transaction: ${dbName}`, ) } return execute(dbName, query, params) @@ -47,8 +48,8 @@ export const transaction = ( params?: SQLiteQueryParams, ): Promise> => { if (isFinalized) { - throw Error( - `Nitro SQLite Error: Cannot execute query on finalized transaction: ${dbName}`, + throw new NitroSQLiteError( + `Cannot execute query on finalized transaction: ${dbName}`, ) } return executeAsync(dbName, query, params) @@ -56,8 +57,8 @@ export const transaction = ( const commit = () => { if (isFinalized) { - throw Error( - `Nitro SQLite Error: Cannot execute commit on finalized transaction: ${dbName}`, + throw new NitroSQLiteError( + `Cannot execute commit on finalized transaction: ${dbName}`, ) } const result = HybridNitroSQLite.execute(dbName, 'COMMIT') @@ -67,8 +68,8 @@ export const transaction = ( const rollback = () => { if (isFinalized) { - throw Error( - `Nitro SQLite Error: Cannot execute rollback on finalized transaction: ${dbName}`, + throw new NitroSQLiteError( + `Cannot execute rollback on finalized transaction: ${dbName}`, ) } const result = HybridNitroSQLite.execute(dbName, 'ROLLBACK') @@ -107,8 +108,13 @@ export const transaction = ( return new Promise((resolve, reject) => { const tx: PendingTransaction = { - start: () => { - run().then(resolve).catch(reject) + start: async () => { + try { + const result = await run() + resolve(result) + } catch (error) { + reject(NitroSQLiteError.fromError(error)) + } }, } @@ -118,7 +124,8 @@ export const transaction = ( } function startNextTransaction(dbName: string) { - if (locks[dbName] == null) throw Error(`Lock not found for db: ${dbName}`) + if (locks[dbName] == null) + throw new NitroSQLiteError(`Lock not found for db: ${dbName}`) if (locks[dbName].inProgress) { // Transaction is already in process bail out