From 7b0626c9ee59d74c36f7e27bd36785deb3ba7455 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Nov 2025 23:23:10 +0000 Subject: [PATCH 1/6] feat: add better error handling --- package/cpp/NitroSQLiteException.hpp | 4 ++- package/src/NitroSQLiteError.ts | 46 ++++++++++++++++++++++++++ package/src/operations/execute.ts | 36 ++++++++++++-------- package/src/operations/executeBatch.ts | 21 ++++++++---- package/src/operations/session.ts | 34 ++++++++++--------- package/src/operations/transaction.ts | 30 +++++++++++------ 6 files changed, 122 insertions(+), 49 deletions(-) create mode 100644 package/src/NitroSQLiteError.ts diff --git a/package/cpp/NitroSQLiteException.hpp b/package/cpp/NitroSQLiteException.hpp index 636d9163..5fad007c 100644 --- a/package/cpp/NitroSQLiteException.hpp +++ b/package/cpp/NitroSQLiteException.hpp @@ -4,6 +4,8 @@ #include #include +const std::string NITRO_SQLITE_EXCEPTION_PREFIX = "[NitroSQLiteException]"; + enum NitroSQLiteExceptionType { UnknownError, DatabaseCannotBeOpened, @@ -33,7 +35,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..a193ecb3 --- /dev/null +++ b/package/src/NitroSQLiteError.ts @@ -0,0 +1,46 @@ +const NITRO_SQLITE_ERROR_NAME = 'NitroSQLiteError' as const +const NITRO_SQLITE_ERROR_PREFIX = '[NitroSQLite] ' 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 + this.message = NITRO_SQLITE_ERROR_PREFIX + message + + // 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..037da38e 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,10 @@ export const transaction = ( params?: SQLiteQueryParams, ): QueryResult => { if (isFinalized) { - throw Error( - `Nitro SQLite Error: Cannot execute query on finalized transaction: ${dbName}`, + throw NitroSQLiteError.fromError( + new NitroSQLiteError( + `Cannot execute query on finalized transaction: ${dbName}`, + ), ) } return execute(dbName, query, params) @@ -47,8 +50,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 +59,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 +70,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') @@ -108,7 +111,11 @@ export const transaction = ( return new Promise((resolve, reject) => { const tx: PendingTransaction = { start: () => { - run().then(resolve).catch(reject) + try { + run().then(resolve) + } catch (error) { + reject(NitroSQLiteError.fromError(error)) + } }, } @@ -118,7 +125,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 From 8df57b9dec13cf54050c9c0321c745200dc38658 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Nov 2025 23:29:58 +0000 Subject: [PATCH 2/6] feat: forward native exception --- package/src/NitroSQLiteError.ts | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/package/src/NitroSQLiteError.ts b/package/src/NitroSQLiteError.ts index a193ecb3..d2de7f25 100644 --- a/package/src/NitroSQLiteError.ts +++ b/package/src/NitroSQLiteError.ts @@ -1,15 +1,25 @@ +const NATIVE_NITRO_SQLITE_EXCEPTION_PREFIX = '[NitroSQLiteException] ' as const const NITRO_SQLITE_ERROR_NAME = 'NitroSQLiteError' as const -const NITRO_SQLITE_ERROR_PREFIX = '[NitroSQLite] ' as const +const getNitroSQLiteErrorPrefix = (isNativeNitroSQLiteException: boolean) => + isNativeNitroSQLiteException ? '[NitroSQLite (Native/C++)]' : '[NitroSQLite] ' + +type NitroSQLiteErrorOptions = ErrorOptions & { + isNativeNitroSQLiteException?: boolean +} /** * 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) + constructor(message: string, options?: NitroSQLiteErrorOptions) { + const { isNativeNitroSQLiteException = false, ...restOptions } = + options ?? {} + + super(message, restOptions) this.name = NITRO_SQLITE_ERROR_NAME - this.message = NITRO_SQLITE_ERROR_PREFIX + message + this.message = + getNitroSQLiteErrorPrefix(isNativeNitroSQLiteException) + message // Maintains proper prototype chain for instanceof checks Object.setPrototypeOf(this, NitroSQLiteError.prototype) @@ -25,6 +35,13 @@ export default class NitroSQLiteError extends Error { } if (error instanceof Error) { + if (error.message.includes(NATIVE_NITRO_SQLITE_EXCEPTION_PREFIX)) { + return new NitroSQLiteError(error.message, { + cause: error.cause, + isNativeNitroSQLiteException: true, + }) + } + const nitroSQLiteError = new NitroSQLiteError(error.message, { cause: error.cause, }) @@ -36,7 +53,11 @@ export default class NitroSQLiteError extends Error { } if (typeof error === 'string') { - return new NitroSQLiteError(error) + return new NitroSQLiteError(error, { + isNativeNitroSQLiteException: error.includes( + NATIVE_NITRO_SQLITE_EXCEPTION_PREFIX, + ), + }) } return new NitroSQLiteError('Unknown error occurred', { From b0e572c7c8bbc8b7cb5a012933884981fc21667c Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Nov 2025 23:47:24 +0000 Subject: [PATCH 3/6] fix: unnecessary duplicate error parsing --- package/src/operations/transaction.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package/src/operations/transaction.ts b/package/src/operations/transaction.ts index 037da38e..46057297 100644 --- a/package/src/operations/transaction.ts +++ b/package/src/operations/transaction.ts @@ -36,10 +36,8 @@ export const transaction = ( params?: SQLiteQueryParams, ): QueryResult => { if (isFinalized) { - throw NitroSQLiteError.fromError( - new NitroSQLiteError( - `Cannot execute query on finalized transaction: ${dbName}`, - ), + throw new NitroSQLiteError( + `Cannot execute query on finalized transaction: ${dbName}`, ) } return execute(dbName, query, params) From fad1dd3b39636712cdcca9aca917d717edb27529 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Nov 2025 23:47:44 +0000 Subject: [PATCH 4/6] update native exception prefix --- package/cpp/NitroSQLiteException.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/cpp/NitroSQLiteException.hpp b/package/cpp/NitroSQLiteException.hpp index 5fad007c..41e48ab8 100644 --- a/package/cpp/NitroSQLiteException.hpp +++ b/package/cpp/NitroSQLiteException.hpp @@ -3,8 +3,9 @@ #include #include #include +#include -const std::string NITRO_SQLITE_EXCEPTION_PREFIX = "[NitroSQLiteException]"; +const std::string NITRO_SQLITE_EXCEPTION_PREFIX = "[NativeNitroSQLiteException]"; enum NitroSQLiteExceptionType { UnknownError, @@ -35,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(NITRO_SQLITE_EXCEPTION_PREFIX + " [" + typeToString(type) + "] " + message) {} + : _exceptionString(NITRO_SQLITE_EXCEPTION_PREFIX + "[" + typeToString(type) + "] " + message) {} private: const std::string _exceptionString; From 7bfaf8c1d5a4aaa88d7411649d24f5a68b460ef5 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Nov 2025 23:49:30 +0000 Subject: [PATCH 5/6] fix: invalid promise resolution in transaction --- package/src/operations/transaction.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/src/operations/transaction.ts b/package/src/operations/transaction.ts index 46057297..d57f2907 100644 --- a/package/src/operations/transaction.ts +++ b/package/src/operations/transaction.ts @@ -108,9 +108,10 @@ export const transaction = ( return new Promise((resolve, reject) => { const tx: PendingTransaction = { - start: () => { + start: async () => { try { - run().then(resolve) + const result = await run() + resolve(result) } catch (error) { reject(NitroSQLiteError.fromError(error)) } From 51495eaceba775a76d5d6a2237c7a32db9255ffe Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 5 Nov 2025 23:53:58 +0000 Subject: [PATCH 6/6] simplify native exception handling --- package/src/NitroSQLiteError.ts | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/package/src/NitroSQLiteError.ts b/package/src/NitroSQLiteError.ts index d2de7f25..8456eedd 100644 --- a/package/src/NitroSQLiteError.ts +++ b/package/src/NitroSQLiteError.ts @@ -1,25 +1,13 @@ -const NATIVE_NITRO_SQLITE_EXCEPTION_PREFIX = '[NitroSQLiteException] ' as const const NITRO_SQLITE_ERROR_NAME = 'NitroSQLiteError' as const -const getNitroSQLiteErrorPrefix = (isNativeNitroSQLiteException: boolean) => - isNativeNitroSQLiteException ? '[NitroSQLite (Native/C++)]' : '[NitroSQLite] ' - -type NitroSQLiteErrorOptions = ErrorOptions & { - isNativeNitroSQLiteException?: boolean -} /** * 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?: NitroSQLiteErrorOptions) { - const { isNativeNitroSQLiteException = false, ...restOptions } = - options ?? {} - - super(message, restOptions) + constructor(message: string, options?: ErrorOptions) { + super(message, options) this.name = NITRO_SQLITE_ERROR_NAME - this.message = - getNitroSQLiteErrorPrefix(isNativeNitroSQLiteException) + message // Maintains proper prototype chain for instanceof checks Object.setPrototypeOf(this, NitroSQLiteError.prototype) @@ -35,16 +23,10 @@ export default class NitroSQLiteError extends Error { } if (error instanceof Error) { - if (error.message.includes(NATIVE_NITRO_SQLITE_EXCEPTION_PREFIX)) { - return new NitroSQLiteError(error.message, { - cause: error.cause, - isNativeNitroSQLiteException: true, - }) - } - const nitroSQLiteError = new NitroSQLiteError(error.message, { cause: error.cause, }) + // Preserve original stack trace if available if (error.stack) { nitroSQLiteError.stack = error.stack @@ -53,11 +35,7 @@ export default class NitroSQLiteError extends Error { } if (typeof error === 'string') { - return new NitroSQLiteError(error, { - isNativeNitroSQLiteException: error.includes( - NATIVE_NITRO_SQLITE_EXCEPTION_PREFIX, - ), - }) + return new NitroSQLiteError(error) } return new NitroSQLiteError('Unknown error occurred', {