Skip to content

Commit aaca4e5

Browse files
authored
feat: Add better error handling (#239)
* feat: add better error handling * feat: forward native exception * fix: unnecessary duplicate error parsing * update native exception prefix * fix: invalid promise resolution in transaction * simplify native exception handling
1 parent fba906b commit aaca4e5

File tree

6 files changed

+122
-50
lines changed

6 files changed

+122
-50
lines changed

package/cpp/NitroSQLiteException.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include <exception>
44
#include <iostream>
55
#include <string>
6+
#include <optional>
7+
8+
const std::string NITRO_SQLITE_EXCEPTION_PREFIX = "[NativeNitroSQLiteException]";
69

710
enum NitroSQLiteExceptionType {
811
UnknownError,
@@ -33,7 +36,7 @@ class NitroSQLiteException : public std::exception {
3336
explicit NitroSQLiteException(const std::string& message) : NitroSQLiteException(NitroSQLiteExceptionType::UnknownError, message) {}
3437
NitroSQLiteException(const NitroSQLiteExceptionType& type, const char* message) : NitroSQLiteException(type, std::string(message)) {}
3538
NitroSQLiteException(const NitroSQLiteExceptionType& type, const std::string& message)
36-
: _exceptionString("[" + typeToString(type) + "] " + message) {}
39+
: _exceptionString(NITRO_SQLITE_EXCEPTION_PREFIX + "[" + typeToString(type) + "] " + message) {}
3740

3841
private:
3942
const std::string _exceptionString;

package/src/NitroSQLiteError.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const NITRO_SQLITE_ERROR_NAME = 'NitroSQLiteError' as const
2+
3+
/**
4+
* Custom error class for NitroSQLite operations
5+
* Extends the native Error class with proper prototype chain and error handling
6+
*/
7+
export default class NitroSQLiteError extends Error {
8+
constructor(message: string, options?: ErrorOptions) {
9+
super(message, options)
10+
this.name = NITRO_SQLITE_ERROR_NAME
11+
12+
// Maintains proper prototype chain for instanceof checks
13+
Object.setPrototypeOf(this, NitroSQLiteError.prototype)
14+
}
15+
16+
/**
17+
* Converts an unknown error to a NitroSQLiteError
18+
* Preserves stack traces and error causes when available
19+
*/
20+
static fromError(error: unknown): NitroSQLiteError {
21+
if (error instanceof NitroSQLiteError) {
22+
return error
23+
}
24+
25+
if (error instanceof Error) {
26+
const nitroSQLiteError = new NitroSQLiteError(error.message, {
27+
cause: error.cause,
28+
})
29+
30+
// Preserve original stack trace if available
31+
if (error.stack) {
32+
nitroSQLiteError.stack = error.stack
33+
}
34+
return nitroSQLiteError
35+
}
36+
37+
if (typeof error === 'string') {
38+
return new NitroSQLiteError(error)
39+
}
40+
41+
return new NitroSQLiteError('Unknown error occurred', {
42+
cause: error,
43+
})
44+
}
45+
}

package/src/operations/execute.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
SQLiteQueryParams,
99
QueryResultRow,
1010
} from '../types'
11+
import NitroSQLiteError from '../NitroSQLiteError'
1112

1213
export function execute<Row extends QueryResultRow = never>(
1314
dbName: string,
@@ -18,13 +19,17 @@ export function execute<Row extends QueryResultRow = never>(
1819
? toNativeQueryParams(params)
1920
: (params as NativeSQLiteQueryParams)
2021

21-
const nativeResult = HybridNitroSQLite.execute(
22-
dbName,
23-
query,
24-
transformedParams,
25-
)
26-
const result = buildJsQueryResult<Row>(nativeResult)
27-
return result
22+
try {
23+
const nativeResult = HybridNitroSQLite.execute(
24+
dbName,
25+
query,
26+
transformedParams,
27+
)
28+
29+
return buildJsQueryResult<Row>(nativeResult)
30+
} catch (error) {
31+
throw NitroSQLiteError.fromError(error)
32+
}
2833
}
2934

3035
export async function executeAsync<Row extends QueryResultRow = never>(
@@ -36,13 +41,16 @@ export async function executeAsync<Row extends QueryResultRow = never>(
3641
? toNativeQueryParams(params)
3742
: (params as NativeSQLiteQueryParams)
3843

39-
const nativeResult = await HybridNitroSQLite.executeAsync(
40-
dbName,
41-
query,
42-
transformedParams,
43-
)
44-
const result = buildJsQueryResult<Row>(nativeResult)
45-
return result
44+
try {
45+
const nativeResult = await HybridNitroSQLite.executeAsync(
46+
dbName,
47+
query,
48+
transformedParams,
49+
)
50+
return buildJsQueryResult<Row>(nativeResult)
51+
} catch (error) {
52+
throw NitroSQLiteError.fromError(error)
53+
}
4654
}
4755

4856
function toNativeQueryParams(

package/src/operations/executeBatch.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
BatchQueryCommand,
1010
NativeBatchQueryCommand,
1111
} from '../types'
12+
import NitroSQLiteError from '../NitroSQLiteError'
1213

1314
export function executeBatch(
1415
dbName: string,
@@ -18,8 +19,11 @@ export function executeBatch(
1819
? toNativeBatchQueryCommands(commands)
1920
: (commands as NativeBatchQueryCommand[])
2021

21-
const result = HybridNitroSQLite.executeBatch(dbName, transformedCommands)
22-
return result
22+
try {
23+
return HybridNitroSQLite.executeBatch(dbName, transformedCommands)
24+
} catch (error) {
25+
throw NitroSQLiteError.fromError(error)
26+
}
2327
}
2428

2529
export async function executeBatchAsync(
@@ -30,11 +34,14 @@ export async function executeBatchAsync(
3034
? toNativeBatchQueryCommands(commands)
3135
: (commands as NativeBatchQueryCommand[])
3236

33-
const result = await HybridNitroSQLite.executeBatchAsync(
34-
dbName,
35-
transformedCommands,
36-
)
37-
return result
37+
try {
38+
return await HybridNitroSQLite.executeBatchAsync(
39+
dbName,
40+
transformedCommands,
41+
)
42+
} catch (error) {
43+
throw NitroSQLiteError.fromError(error)
44+
}
3845
}
3946

4047
function toNativeBatchQueryCommands(

package/src/operations/session.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,30 @@ import type {
1111
} from '../types'
1212
import { execute, executeAsync } from './execute'
1313
import { executeBatch, executeBatchAsync } from './executeBatch'
14+
import NitroSQLiteError from '../NitroSQLiteError'
1415

1516
export function open(
1617
options: NitroSQLiteConnectionOptions,
1718
): NitroSQLiteConnection {
18-
openDb(options.name, options.location)
19+
try {
20+
HybridNitroSQLite.open(options.name, options.location)
21+
locks[options.name] = {
22+
queue: [],
23+
inProgress: false,
24+
}
25+
} catch (error) {
26+
throw NitroSQLiteError.fromError(error)
27+
}
1928

2029
return {
21-
close: () => close(options.name),
30+
close: () => {
31+
try {
32+
HybridNitroSQLite.close(options.name)
33+
delete locks[options.name]
34+
} catch (error) {
35+
throw NitroSQLiteError.fromError(error)
36+
}
37+
},
2238
delete: () => HybridNitroSQLite.drop(options.name, options.location),
2339
attach: (dbNameToAttach: string, alias: string, location?: string) =>
2440
HybridNitroSQLite.attach(options.name, dbNameToAttach, alias, location),
@@ -43,17 +59,3 @@ export function open(
4359
HybridNitroSQLite.loadFileAsync(options.name, location),
4460
}
4561
}
46-
47-
export function openDb(dbName: string, location?: string) {
48-
HybridNitroSQLite.open(dbName, location)
49-
50-
locks[dbName] = {
51-
queue: [],
52-
inProgress: false,
53-
}
54-
}
55-
56-
export function close(dbName: string) {
57-
HybridNitroSQLite.close(dbName)
58-
delete locks[dbName]
59-
}

package/src/operations/transaction.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { locks, HybridNitroSQLite } from '../nitro'
2+
import NitroSQLiteError from '../NitroSQLiteError'
23
import type {
34
QueryResult,
45
Transaction,
@@ -25,7 +26,7 @@ export const transaction = (
2526
fn: (tx: Transaction) => Promise<void> | void,
2627
): Promise<void> => {
2728
if (locks[dbName] == null)
28-
throw Error(`Nitro SQLite Error: No lock found on db: ${dbName}`)
29+
throw new NitroSQLiteError(`No lock found on db: ${dbName}`)
2930

3031
let isFinalized = false
3132

@@ -35,8 +36,8 @@ export const transaction = (
3536
params?: SQLiteQueryParams,
3637
): QueryResult<Data> => {
3738
if (isFinalized) {
38-
throw Error(
39-
`Nitro SQLite Error: Cannot execute query on finalized transaction: ${dbName}`,
39+
throw new NitroSQLiteError(
40+
`Cannot execute query on finalized transaction: ${dbName}`,
4041
)
4142
}
4243
return execute(dbName, query, params)
@@ -47,17 +48,17 @@ export const transaction = (
4748
params?: SQLiteQueryParams,
4849
): Promise<QueryResult<Data>> => {
4950
if (isFinalized) {
50-
throw Error(
51-
`Nitro SQLite Error: Cannot execute query on finalized transaction: ${dbName}`,
51+
throw new NitroSQLiteError(
52+
`Cannot execute query on finalized transaction: ${dbName}`,
5253
)
5354
}
5455
return executeAsync(dbName, query, params)
5556
}
5657

5758
const commit = () => {
5859
if (isFinalized) {
59-
throw Error(
60-
`Nitro SQLite Error: Cannot execute commit on finalized transaction: ${dbName}`,
60+
throw new NitroSQLiteError(
61+
`Cannot execute commit on finalized transaction: ${dbName}`,
6162
)
6263
}
6364
const result = HybridNitroSQLite.execute(dbName, 'COMMIT')
@@ -67,8 +68,8 @@ export const transaction = (
6768

6869
const rollback = () => {
6970
if (isFinalized) {
70-
throw Error(
71-
`Nitro SQLite Error: Cannot execute rollback on finalized transaction: ${dbName}`,
71+
throw new NitroSQLiteError(
72+
`Cannot execute rollback on finalized transaction: ${dbName}`,
7273
)
7374
}
7475
const result = HybridNitroSQLite.execute(dbName, 'ROLLBACK')
@@ -107,8 +108,13 @@ export const transaction = (
107108

108109
return new Promise((resolve, reject) => {
109110
const tx: PendingTransaction = {
110-
start: () => {
111-
run().then(resolve).catch(reject)
111+
start: async () => {
112+
try {
113+
const result = await run()
114+
resolve(result)
115+
} catch (error) {
116+
reject(NitroSQLiteError.fromError(error))
117+
}
112118
},
113119
}
114120

@@ -118,7 +124,8 @@ export const transaction = (
118124
}
119125

120126
function startNextTransaction(dbName: string) {
121-
if (locks[dbName] == null) throw Error(`Lock not found for db: ${dbName}`)
127+
if (locks[dbName] == null)
128+
throw new NitroSQLiteError(`Lock not found for db: ${dbName}`)
122129

123130
if (locks[dbName].inProgress) {
124131
// Transaction is already in process bail out

0 commit comments

Comments
 (0)