@bombillazo/error-x 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +479 -195
- package/dist/index.cjs +563 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1067 -429
- package/dist/index.d.ts +1067 -429
- package/dist/index.js +561 -233
- package/dist/index.js.map +1 -1
- package/package.json +2 -15
package/README.md
CHANGED
|
@@ -4,19 +4,24 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@bombillazo/error-x)
|
|
5
5
|
[](https://github.com/bombillazo/error-x/blob/master/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
🚨❌
|
|
8
|
+
|
|
9
|
+
A smart, isomorphic, and type-safe error library for TypeScript applications. Provides excellent DX with intelligent error conversion, stack trace preservation, serialization support, error chaining, flexible custom error classes, and more.
|
|
8
10
|
|
|
9
11
|
## Features
|
|
10
12
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
13
|
+
- **Built in TypeScript** for full Type-safe error handling support and generics
|
|
14
|
+
- **Isomorphic** - works in Node.js and browsers
|
|
15
|
+
- **Smart error conversion** from different sources (API responses, strings, Error objects)
|
|
16
|
+
- **Error chaining** for full error sequence
|
|
17
|
+
- **Factory method** `.create()` for preset-based error creation
|
|
18
|
+
- **Custom metadata** with type-safe generics for additional context
|
|
19
|
+
- **Global configuration** for stack cleaning and defaults
|
|
20
|
+
- **Serialization/deserialization** for network transfer and storage
|
|
21
|
+
- **Custom ErrorX class** examples:
|
|
22
|
+
- `HTTPErrorX` - HTTP status code presets (400-511)
|
|
23
|
+
- `DBErrorX` - Database error presets (connection, query, constraints)
|
|
24
|
+
- `ValidationErrorX` - Validation errors with Zod integration
|
|
20
25
|
|
|
21
26
|
## Installation
|
|
22
27
|
|
|
@@ -31,275 +36,554 @@ yarn add @bombillazo/error-x
|
|
|
31
36
|
### Requirements
|
|
32
37
|
|
|
33
38
|
- **Node.js**: 18 or higher
|
|
34
|
-
- **TypeScript**: 5.0 or higher
|
|
39
|
+
- **TypeScript**: 5.0 or higher
|
|
35
40
|
- **Target Environment**: ES2022+
|
|
36
41
|
|
|
37
42
|
This library uses modern JavaScript features and ES2022 APIs. For browser compatibility, ensure your build tool (e.g., Vite, webpack, esbuild) is configured to target ES2022 or transpile accordingly.
|
|
38
43
|
|
|
39
44
|
> [!WARNING]
|
|
40
45
|
>
|
|
41
|
-
> This library is currently in pre-v1.0 development.
|
|
46
|
+
> This library is currently in pre-v1.0 development. Breaking changes may occur. We recommend pinning to specific versions and reviewing release notes when updating.
|
|
42
47
|
>
|
|
43
48
|
> Once we reach version 1.0, we plan to minimize API changes and follow semantic versioning.
|
|
44
49
|
|
|
45
|
-
## Documentation
|
|
46
|
-
|
|
47
|
-
[Documentation](docs/index.md)
|
|
48
|
-
|
|
49
50
|
## Quick Start
|
|
50
51
|
|
|
51
52
|
```typescript
|
|
52
|
-
import { ErrorX,
|
|
53
|
+
import { ErrorX, HTTPErrorX, DBErrorX } from "@bombillazo/error-x";
|
|
53
54
|
|
|
54
55
|
// Simple usage
|
|
55
|
-
throw new ErrorX(
|
|
56
|
+
throw new ErrorX("Something went wrong");
|
|
56
57
|
|
|
57
|
-
//
|
|
58
|
+
// A fully defined error
|
|
58
59
|
throw new ErrorX({
|
|
59
|
-
message:
|
|
60
|
-
name:
|
|
61
|
-
code:
|
|
62
|
-
uiMessage:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
// Using
|
|
68
|
-
throw
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
message: 'Session expired',
|
|
74
|
-
metadata: { userId: 123 }
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
// Smart conversion from unknown errors
|
|
78
|
-
try {
|
|
79
|
-
await someOperation()
|
|
80
|
-
} catch (error) {
|
|
81
|
-
const errorX = ErrorX.from(error)
|
|
82
|
-
throw errorX.withMetadata({ context: 'additional info' })
|
|
83
|
-
}
|
|
60
|
+
message: "User authentication failed",
|
|
61
|
+
name: "AuthError",
|
|
62
|
+
code: "AUTH_FAILED",
|
|
63
|
+
uiMessage: "Please check your credentials",
|
|
64
|
+
httpStatus: 401,
|
|
65
|
+
metadata: { userId: 123 },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Using specialized error classes
|
|
69
|
+
throw HTTPErrorX.create(404);
|
|
70
|
+
throw DBErrorX.create("CONNECTION_FAILED");
|
|
71
|
+
|
|
72
|
+
// Convert unknown errors
|
|
73
|
+
const errorX = ErrorX.from(unknownError);
|
|
84
74
|
```
|
|
85
75
|
|
|
86
76
|
## Documentation
|
|
87
77
|
|
|
88
|
-
|
|
78
|
+
[Full Documentation](docs/index.md)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## API Reference
|
|
83
|
+
|
|
84
|
+
### ErrorX Class
|
|
85
|
+
|
|
86
|
+
The base error class that extends the native `Error` with enhanced capabilities.
|
|
87
|
+
|
|
88
|
+
#### Properties
|
|
89
|
+
|
|
90
|
+
| Property | Type | Description |
|
|
91
|
+
| ------------ | -------------------------- | -------------------------------------------------------------- |
|
|
92
|
+
| `message` | `string` | Technical error message |
|
|
93
|
+
| `name` | `string` | Error type/name |
|
|
94
|
+
| `code` | `string` | Error identifier code (auto-generated from name if not set) |
|
|
95
|
+
| `uiMessage` | `string \| undefined` | User-friendly message for UI display |
|
|
96
|
+
| `httpStatus` | `number \| undefined` | HTTP status code associated with this error |
|
|
97
|
+
| `metadata` | `TMetadata \| undefined` | Additional context (type-safe with generics) |
|
|
98
|
+
| `timestamp` | `number` | Unix epoch timestamp (ms) when error was created |
|
|
99
|
+
| `stack` | `string \| undefined` | Stack trace (inherited from Error) |
|
|
100
|
+
| `chain` | `readonly ErrorX[]` | Full error sequence: `[this, parent, grandparent, ..., root]` |
|
|
101
|
+
| `root` | `ErrorX \| undefined` | Error that started the whole error chain |
|
|
102
|
+
| `parent` | `ErrorX \| undefined` | Error that immediately precedes this error in the chain |
|
|
103
|
+
| `original` | `ErrorXCause \| undefined` | Stores the original non-ErrorX error used to create this error |
|
|
104
|
+
|
|
105
|
+
#### Static Methods
|
|
106
|
+
|
|
107
|
+
| Method | Description |
|
|
108
|
+
| --------------------- | ------------------------------------------------------------------- |
|
|
109
|
+
| `from(value, opts?)` | Convert any value to ErrorX with intelligent property extraction |
|
|
110
|
+
| `fromJSON(json)` | Deserialize JSON back to ErrorX instance |
|
|
111
|
+
| `create(key?, opts?)` | Factory method for preset-based error creation (used by subclasses) |
|
|
112
|
+
| `isErrorX(value)` | Type guard to check if value is an ErrorX instance |
|
|
113
|
+
| `isErrorXOptions(v)` | Check if value is a valid ErrorXOptions object |
|
|
114
|
+
| `configure(config)` | Set global configuration (stack cleaning, defaults) |
|
|
115
|
+
| `getConfig()` | Get current global configuration |
|
|
116
|
+
| `resetConfig()` | Reset global configuration to defaults |
|
|
117
|
+
| `cleanStack(stack)` | Clean internal frames from stack trace |
|
|
118
|
+
|
|
119
|
+
#### Instance Methods
|
|
120
|
+
|
|
121
|
+
| Method | Description |
|
|
122
|
+
| -------------------- | ------------------------------------------------- |
|
|
123
|
+
| `withMetadata(meta)` | Create new ErrorX with additional metadata merged |
|
|
124
|
+
| `toJSON()` | Serialize to JSON-compatible object |
|
|
125
|
+
| `toString()` | Detailed string representation with metadata |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Constructor
|
|
89
130
|
|
|
90
131
|
```typescript
|
|
91
132
|
new ErrorX(input?: string | ErrorXOptions)
|
|
92
133
|
```
|
|
93
134
|
|
|
94
|
-
All parameters are optional
|
|
135
|
+
Create a new ErrorX instance. All parameters are optional with sensible defaults.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// String message
|
|
139
|
+
new ErrorX("Database connection failed");
|
|
140
|
+
|
|
141
|
+
// Options object
|
|
142
|
+
new ErrorX({
|
|
143
|
+
message: "User not found",
|
|
144
|
+
name: "NotFoundError",
|
|
145
|
+
code: "USER_NOT_FOUND",
|
|
146
|
+
uiMessage: "The requested user does not exist",
|
|
147
|
+
httpStatus: 404,
|
|
148
|
+
metadata: { userId: 123 },
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// With type-safe metadata
|
|
152
|
+
type UserMeta = { userId: number; action: string };
|
|
153
|
+
new ErrorX<UserMeta>({
|
|
154
|
+
message: "Action failed",
|
|
155
|
+
metadata: { userId: 123, action: "delete" },
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### ErrorXOptions
|
|
160
|
+
|
|
161
|
+
| Property | Type | Default | Description |
|
|
162
|
+
| ---------- | --------------------------------- | --------------------- | ----------------------------------------- |
|
|
163
|
+
| message | `string` | `'An error occurred'` | Technical error message |
|
|
164
|
+
| name | `string` | `'Error'` | Error type/name |
|
|
165
|
+
| code | `string \| number` | Auto-generated | Error identifier (UPPER_SNAKE_CASE) |
|
|
166
|
+
| uiMessage | `string` | `undefined` | User-friendly message |
|
|
167
|
+
| httpStatus | `number` | `undefined` | HTTP status code |
|
|
168
|
+
| metadata | `TMetadata` | `undefined` | Additional context |
|
|
169
|
+
| cause | `ErrorXCause \| Error \| unknown` | `undefined` | Error that caused this (builds the chain) |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Common Methods
|
|
95
174
|
|
|
96
|
-
|
|
97
|
-
| --------- | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
|
|
98
|
-
| message | `string` | `'An error occurred'` | Technical error message (pass-through, no auto-formatting) |
|
|
99
|
-
| name | `string` | `'Error'` | Error type/title |
|
|
100
|
-
| code | `string \| number` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
|
|
101
|
-
| uiMessage | `string \| undefined` | `undefined` | User-friendly message for display |
|
|
102
|
-
| cause | `ErrorXCause \| Error \| unknown` | `undefined` | Original error that caused this (preserves full error chain) |
|
|
103
|
-
| metadata | `Record<string, unknown> \| undefined` | `undefined` | Additional context and data (flexible storage for any extra info) |
|
|
104
|
-
| type | `string \| undefined` | `undefined` | Error type for categorization (e.g., 'http', 'validation') |
|
|
105
|
-
| docsUrl | `string \| undefined` | `undefined` or auto-generated | Documentation URL for this specific error |
|
|
106
|
-
| source | `string \| undefined` | `undefined` or from config | Where the error originated (service name, module, component) |
|
|
107
|
-
| timestamp | `number` | `Date.now()` | Unix epoch timestamp in milliseconds when error was created |
|
|
108
|
-
| stack | `string` | Auto-generated | Stack trace with preservation and cleaning (inherited from Error) |
|
|
175
|
+
### ErrorX.from()
|
|
109
176
|
|
|
110
|
-
|
|
177
|
+
Convert any value into an ErrorX instance with intelligent property extraction.
|
|
111
178
|
|
|
112
|
-
|
|
179
|
+
```typescript
|
|
180
|
+
static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Handles strings, Error objects, API responses, and unknown values. Extracts common properties like `message`, `code`, `status`, `statusCode`, and `metadata`.
|
|
113
184
|
|
|
114
185
|
```typescript
|
|
115
|
-
|
|
186
|
+
// Convert string
|
|
187
|
+
ErrorX.from("Something went wrong");
|
|
188
|
+
|
|
189
|
+
// Convert Error
|
|
190
|
+
ErrorX.from(new Error("Connection failed"));
|
|
191
|
+
|
|
192
|
+
// Convert API response
|
|
193
|
+
ErrorX.from({
|
|
194
|
+
message: "User not found",
|
|
195
|
+
code: "USER_404",
|
|
196
|
+
status: 404,
|
|
197
|
+
metadata: { userId: 123 },
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// With overrides (deep merged)
|
|
201
|
+
ErrorX.from(error, {
|
|
202
|
+
httpStatus: 500,
|
|
203
|
+
metadata: { context: "db-layer" },
|
|
204
|
+
});
|
|
205
|
+
```
|
|
116
206
|
|
|
117
|
-
|
|
118
|
-
throw new ErrorX(http[404])
|
|
119
|
-
// Result: 404 error with message "Not found.", code "NOT_FOUND", etc.
|
|
207
|
+
### ErrorX.isErrorX()
|
|
120
208
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
})
|
|
209
|
+
Type guard to check if a value is an ErrorX instance.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
static isErrorX<T>(value: unknown): value is ErrorX<T>
|
|
213
|
+
```
|
|
127
214
|
|
|
128
|
-
|
|
215
|
+
```typescript
|
|
129
216
|
try {
|
|
130
|
-
|
|
131
|
-
} catch (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
metadata: { operation: 'database-query' }
|
|
136
|
-
})
|
|
217
|
+
await riskyOperation();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (ErrorX.isErrorX(error)) {
|
|
220
|
+
console.log(error.code, error.metadata);
|
|
221
|
+
}
|
|
137
222
|
}
|
|
138
223
|
```
|
|
139
224
|
|
|
140
|
-
|
|
225
|
+
### withMetadata()
|
|
226
|
+
|
|
227
|
+
Create a new ErrorX with additional metadata merged with existing metadata.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>
|
|
231
|
+
```
|
|
141
232
|
|
|
142
|
-
|
|
233
|
+
```typescript
|
|
234
|
+
const error = new ErrorX({
|
|
235
|
+
message: "Request failed",
|
|
236
|
+
metadata: { endpoint: "/api/users" },
|
|
237
|
+
});
|
|
143
238
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
- `uiMessage`: User-friendly message
|
|
148
|
-
- `metadata`: Contains `{ status: <number> }` with the HTTP status code
|
|
239
|
+
const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
|
|
240
|
+
// metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }
|
|
241
|
+
```
|
|
149
242
|
|
|
150
|
-
|
|
243
|
+
### Serialization
|
|
151
244
|
|
|
152
|
-
|
|
245
|
+
Serialize ErrorX instances for network transfer or storage.
|
|
153
246
|
|
|
154
247
|
```typescript
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
export const dbErrors = {
|
|
159
|
-
connectionFailed: {
|
|
160
|
-
name: 'DatabaseError',
|
|
161
|
-
code: 'DB_CONNECTION_FAILED',
|
|
162
|
-
message: 'Database connection failed.',
|
|
163
|
-
uiMessage: 'Unable to connect to database. Please try again later.',
|
|
164
|
-
type: 'database',
|
|
165
|
-
},
|
|
166
|
-
queryTimeout: {
|
|
167
|
-
name: 'DatabaseError',
|
|
168
|
-
code: 'DB_QUERY_TIMEOUT',
|
|
169
|
-
message: 'Database query timeout.',
|
|
170
|
-
uiMessage: 'The operation took too long. Please try again.',
|
|
171
|
-
type: 'database',
|
|
172
|
-
},
|
|
173
|
-
} satisfies Record<string, ErrorXOptions>;
|
|
174
|
-
|
|
175
|
-
export const authErrors = {
|
|
176
|
-
invalidToken: {
|
|
177
|
-
name: 'AuthenticationError',
|
|
178
|
-
code: 'AUTH_INVALID_TOKEN',
|
|
179
|
-
message: 'Invalid authentication token.',
|
|
180
|
-
uiMessage: 'Your session has expired. Please log in again.',
|
|
181
|
-
metadata: { status: 401 },
|
|
182
|
-
type: 'authentication',
|
|
183
|
-
},
|
|
184
|
-
insufficientPermissions: {
|
|
185
|
-
name: 'AuthorizationError',
|
|
186
|
-
code: 'AUTH_INSUFFICIENT_PERMISSIONS',
|
|
187
|
-
message: 'Insufficient permissions.',
|
|
188
|
-
uiMessage: 'You do not have permission to perform this action.',
|
|
189
|
-
metadata: { status: 403 },
|
|
190
|
-
type: 'authorization',
|
|
191
|
-
},
|
|
192
|
-
} satisfies Record<string, ErrorXOptions>;
|
|
248
|
+
// Serialize
|
|
249
|
+
const json = error.toJSON();
|
|
250
|
+
// { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
|
|
193
251
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
throw new ErrorX({ ...authErrors.invalidToken, metadata: { userId: 123 } });
|
|
252
|
+
// Deserialize
|
|
253
|
+
const restored = ErrorX.fromJSON(json);
|
|
197
254
|
```
|
|
198
255
|
|
|
199
|
-
|
|
256
|
+
### Error Chaining
|
|
200
257
|
|
|
258
|
+
Build error timelines by passing `cause` to preserve the full error history.
|
|
201
259
|
|
|
202
|
-
|
|
260
|
+
```typescript
|
|
261
|
+
// Build an error chain
|
|
262
|
+
const dbError = ErrorX.from(new Error("ECONNREFUSED"));
|
|
263
|
+
const repoError = new ErrorX({ message: "Query failed", cause: dbError });
|
|
264
|
+
const serviceError = new ErrorX({
|
|
265
|
+
message: "User fetch failed",
|
|
266
|
+
cause: repoError,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Access chain information
|
|
270
|
+
serviceError.chain.length; // 3: [serviceError, repoError, dbError]
|
|
271
|
+
serviceError.parent; // repoError
|
|
272
|
+
serviceError.root; // dbError
|
|
273
|
+
dbError.original; // { message: 'ECONNREFUSED', name: 'Error', stack: '...' }
|
|
274
|
+
```
|
|
203
275
|
|
|
204
|
-
|
|
276
|
+
```typescript
|
|
277
|
+
// Practical example
|
|
278
|
+
try {
|
|
279
|
+
await database.query(sql);
|
|
280
|
+
} catch (dbError) {
|
|
281
|
+
throw DBErrorX.create("QUERY_FAILED", {
|
|
282
|
+
cause: dbError,
|
|
283
|
+
metadata: { query: sql, table: "users" },
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Later, inspect the chain
|
|
288
|
+
if (ErrorX.isErrorX(error)) {
|
|
289
|
+
console.log(
|
|
290
|
+
"Error chain:",
|
|
291
|
+
error.chain.map((e) => e.name)
|
|
292
|
+
);
|
|
293
|
+
console.log("Root cause:", error.root?.original);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Custom Error Classes
|
|
300
|
+
|
|
301
|
+
error-x includes several custom error classes out of the box:
|
|
302
|
+
|
|
303
|
+
- **Ready-to-use** - Practical error classes for common scenarios (HTTP, database, validation)
|
|
304
|
+
- **Educational** - Demonstrate how to use presets, defaults, and transforms
|
|
305
|
+
- **Extensible** - Serve as templates and inspiration for your own domain-specific error classes
|
|
306
|
+
|
|
307
|
+
### HTTPErrorX
|
|
308
|
+
|
|
309
|
+
HTTP errors with presets for all standard status codes (400-511).
|
|
205
310
|
|
|
206
311
|
```typescript
|
|
207
|
-
import {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
312
|
+
import { HTTPErrorX } from "@bombillazo/error-x";
|
|
313
|
+
|
|
314
|
+
// Create by status code
|
|
315
|
+
HTTPErrorX.create(404);
|
|
316
|
+
// → code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
|
|
317
|
+
|
|
318
|
+
HTTPErrorX.create(401);
|
|
319
|
+
// → code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
|
|
320
|
+
|
|
321
|
+
// With overrides
|
|
322
|
+
HTTPErrorX.create(404, {
|
|
323
|
+
message: "User not found",
|
|
324
|
+
metadata: { userId: 123 },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// With error chaining
|
|
328
|
+
HTTPErrorX.create(500, { cause: originalError });
|
|
329
|
+
|
|
330
|
+
// instanceof checks
|
|
331
|
+
if (error instanceof HTTPErrorX) {
|
|
332
|
+
console.log(error.httpStatus);
|
|
219
333
|
}
|
|
220
334
|
```
|
|
221
335
|
|
|
222
|
-
|
|
336
|
+
**Available Presets:** All 4xx client errors (400-451) and 5xx server errors (500-511).
|
|
337
|
+
|
|
338
|
+
### DBErrorX
|
|
339
|
+
|
|
340
|
+
Database errors with presets for common database scenarios. All codes are automatically prefixed with `DB_`.
|
|
223
341
|
|
|
224
342
|
```typescript
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
343
|
+
import { DBErrorX } from "@bombillazo/error-x";
|
|
344
|
+
|
|
345
|
+
// Connection errors
|
|
346
|
+
DBErrorX.create("CONNECTION_FAILED"); // → code: 'DB_CONNECTION_FAILED'
|
|
347
|
+
DBErrorX.create("CONNECTION_TIMEOUT");
|
|
348
|
+
DBErrorX.create("CONNECTION_REFUSED");
|
|
349
|
+
DBErrorX.create("CONNECTION_LOST");
|
|
350
|
+
|
|
351
|
+
// Query errors
|
|
352
|
+
DBErrorX.create("QUERY_FAILED");
|
|
353
|
+
DBErrorX.create("QUERY_TIMEOUT");
|
|
354
|
+
DBErrorX.create("SYNTAX_ERROR");
|
|
355
|
+
|
|
356
|
+
// Constraint errors (with appropriate httpStatus)
|
|
357
|
+
DBErrorX.create("UNIQUE_VIOLATION"); // httpStatus: 409
|
|
358
|
+
DBErrorX.create("FOREIGN_KEY_VIOLATION"); // httpStatus: 400
|
|
359
|
+
DBErrorX.create("NOT_NULL_VIOLATION"); // httpStatus: 400
|
|
360
|
+
DBErrorX.create("CHECK_VIOLATION"); // httpStatus: 400
|
|
361
|
+
|
|
362
|
+
// Transaction errors
|
|
363
|
+
DBErrorX.create("TRANSACTION_FAILED");
|
|
364
|
+
DBErrorX.create("DEADLOCK"); // httpStatus: 409
|
|
365
|
+
|
|
366
|
+
// Record errors
|
|
367
|
+
DBErrorX.create("NOT_FOUND"); // httpStatus: 404
|
|
368
|
+
|
|
369
|
+
// With metadata
|
|
370
|
+
DBErrorX.create("QUERY_FAILED", {
|
|
371
|
+
message: "Failed to fetch user",
|
|
372
|
+
metadata: {
|
|
373
|
+
query: "SELECT * FROM users WHERE id = ?",
|
|
374
|
+
table: "users",
|
|
375
|
+
operation: "SELECT",
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// instanceof checks
|
|
380
|
+
if (error instanceof DBErrorX) {
|
|
381
|
+
console.log(error.metadata?.table);
|
|
239
382
|
}
|
|
240
383
|
```
|
|
241
384
|
|
|
242
|
-
###
|
|
385
|
+
### ValidationErrorX
|
|
386
|
+
|
|
387
|
+
Validation errors with Zod integration. All codes are prefixed with `VALIDATION_`.
|
|
243
388
|
|
|
244
389
|
```typescript
|
|
390
|
+
import { z } from "zod";
|
|
391
|
+
import { ValidationErrorX } from "@bombillazo/error-x";
|
|
392
|
+
|
|
393
|
+
// From Zod errors
|
|
394
|
+
const schema = z.object({
|
|
395
|
+
email: z.string().email(),
|
|
396
|
+
age: z.number().min(18),
|
|
397
|
+
});
|
|
398
|
+
|
|
245
399
|
try {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
400
|
+
schema.parse({ email: "invalid", age: 15 });
|
|
401
|
+
} catch (err) {
|
|
402
|
+
if (err instanceof z.ZodError) {
|
|
403
|
+
throw ValidationErrorX.fromZodError(err);
|
|
404
|
+
// → code: 'VALIDATION_INVALID_STRING'
|
|
405
|
+
// → metadata.field: 'email'
|
|
406
|
+
// → metadata.zodCode: 'invalid_string'
|
|
407
|
+
// → httpStatus: 400
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// With overrides
|
|
412
|
+
ValidationErrorX.fromZodError(zodError, {
|
|
413
|
+
uiMessage: "Please check your input",
|
|
414
|
+
httpStatus: 422,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Field-specific errors (without Zod)
|
|
418
|
+
ValidationErrorX.forField("email", "Invalid email format");
|
|
419
|
+
ValidationErrorX.forField("age", "Must be 18 or older", { code: "TOO_YOUNG" });
|
|
420
|
+
|
|
421
|
+
// Direct creation
|
|
422
|
+
ValidationErrorX.create({
|
|
423
|
+
message: "Invalid input",
|
|
424
|
+
code: "INVALID_INPUT",
|
|
425
|
+
metadata: { field: "email" },
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// instanceof checks
|
|
429
|
+
if (error instanceof ValidationErrorX) {
|
|
430
|
+
console.log(error.metadata?.field);
|
|
261
431
|
}
|
|
262
432
|
```
|
|
263
433
|
|
|
264
|
-
|
|
434
|
+
### Creating Custom Error Classes
|
|
265
435
|
|
|
266
|
-
|
|
436
|
+
Extend `ErrorX` to create domain-specific error classes with presets, defaults, and transforms.
|
|
267
437
|
|
|
268
438
|
```typescript
|
|
269
|
-
|
|
270
|
-
|
|
439
|
+
import {
|
|
440
|
+
ErrorX,
|
|
441
|
+
type ErrorXOptions,
|
|
442
|
+
type ErrorXTransform,
|
|
443
|
+
} from "@bombillazo/error-x";
|
|
444
|
+
|
|
445
|
+
// 1. Define your metadata type
|
|
446
|
+
type PaymentMetadata = {
|
|
447
|
+
transactionId?: string;
|
|
448
|
+
amount?: number;
|
|
449
|
+
currency?: string;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// 2. Define presets outside the class for type inference
|
|
453
|
+
const paymentPresets = {
|
|
454
|
+
INSUFFICIENT_FUNDS: {
|
|
455
|
+
code: "INSUFFICIENT_FUNDS",
|
|
456
|
+
name: "PaymentError",
|
|
457
|
+
message: "Insufficient funds.",
|
|
458
|
+
uiMessage: "Your payment method has insufficient funds.",
|
|
459
|
+
httpStatus: 402,
|
|
460
|
+
},
|
|
461
|
+
CARD_DECLINED: {
|
|
462
|
+
code: "CARD_DECLINED",
|
|
463
|
+
name: "PaymentError",
|
|
464
|
+
message: "Card declined.",
|
|
465
|
+
uiMessage: "Your card was declined. Please try another payment method.",
|
|
466
|
+
httpStatus: 402,
|
|
467
|
+
},
|
|
468
|
+
EXPIRED_CARD: {
|
|
469
|
+
code: "EXPIRED_CARD",
|
|
470
|
+
name: "PaymentError",
|
|
471
|
+
message: "Card expired.",
|
|
472
|
+
uiMessage: "Your card has expired. Please update your payment method.",
|
|
473
|
+
httpStatus: 402,
|
|
474
|
+
},
|
|
475
|
+
} as const satisfies Record<string, ErrorXOptions>;
|
|
476
|
+
|
|
477
|
+
// 3. Derive preset key type
|
|
478
|
+
type PaymentPresetKey = keyof typeof paymentPresets | (string & {});
|
|
479
|
+
|
|
480
|
+
// 4. Create the class
|
|
481
|
+
export class PaymentErrorX extends ErrorX<PaymentMetadata> {
|
|
482
|
+
static presets = paymentPresets;
|
|
483
|
+
static defaultPreset = "CARD_DECLINED";
|
|
484
|
+
static defaults = { httpStatus: 402 };
|
|
485
|
+
|
|
486
|
+
// Optional: transform to prefix codes
|
|
487
|
+
static transform: ErrorXTransform<PaymentMetadata> = (opts) => ({
|
|
488
|
+
...opts,
|
|
489
|
+
code: `PAYMENT_${opts.code}`,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Override create for proper typing
|
|
493
|
+
static override create(
|
|
494
|
+
presetKey?: PaymentPresetKey,
|
|
495
|
+
overrides?: Partial<ErrorXOptions<PaymentMetadata>>
|
|
496
|
+
): PaymentErrorX {
|
|
497
|
+
return ErrorX.create.call(
|
|
498
|
+
PaymentErrorX,
|
|
499
|
+
presetKey,
|
|
500
|
+
overrides
|
|
501
|
+
) as PaymentErrorX;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Usage
|
|
506
|
+
throw PaymentErrorX.create("INSUFFICIENT_FUNDS");
|
|
507
|
+
throw PaymentErrorX.create("CARD_DECLINED", {
|
|
508
|
+
metadata: { transactionId: "tx_123", amount: 99.99, currency: "USD" },
|
|
509
|
+
});
|
|
271
510
|
|
|
272
|
-
|
|
273
|
-
|
|
511
|
+
// instanceof works
|
|
512
|
+
if (error instanceof PaymentErrorX) {
|
|
513
|
+
console.log(error.metadata?.transactionId);
|
|
514
|
+
}
|
|
274
515
|
```
|
|
275
516
|
|
|
276
|
-
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Other Usage
|
|
520
|
+
|
|
521
|
+
### Global Configuration
|
|
522
|
+
|
|
523
|
+
Configure stack trace cleaning and other global settings.
|
|
277
524
|
|
|
278
525
|
```typescript
|
|
279
|
-
|
|
280
|
-
|
|
526
|
+
import { ErrorX } from "@bombillazo/error-x";
|
|
527
|
+
|
|
528
|
+
// Enable stack cleaning with custom delimiter
|
|
529
|
+
ErrorX.configure({
|
|
530
|
+
cleanStack: true,
|
|
531
|
+
cleanStackDelimiter: "my-app-entry",
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// Custom patterns to remove from stack traces
|
|
535
|
+
ErrorX.configure({
|
|
536
|
+
cleanStack: ["node_modules", "internal/"],
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// Disable stack cleaning
|
|
540
|
+
ErrorX.configure({ cleanStack: false });
|
|
281
541
|
|
|
282
|
-
|
|
283
|
-
|
|
542
|
+
// Get current config
|
|
543
|
+
const config = ErrorX.getConfig();
|
|
544
|
+
|
|
545
|
+
// Reset to defaults
|
|
546
|
+
ErrorX.resetConfig();
|
|
284
547
|
```
|
|
285
548
|
|
|
286
|
-
|
|
549
|
+
### Auto Code Generation
|
|
550
|
+
|
|
551
|
+
Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
new ErrorX({ name: "DatabaseError" });
|
|
555
|
+
// → code: 'DATABASE_ERROR'
|
|
556
|
+
|
|
557
|
+
new ErrorX({ name: "userAuthError" });
|
|
558
|
+
// → code: 'USER_AUTH_ERROR'
|
|
559
|
+
|
|
560
|
+
new ErrorX({ name: "API Timeout" });
|
|
561
|
+
// → code: 'API_TIMEOUT'
|
|
562
|
+
```
|
|
287
563
|
|
|
288
|
-
|
|
564
|
+
### Message Formatting
|
|
289
565
|
|
|
290
|
-
|
|
566
|
+
ErrorX does NOT auto-format messages. Messages are passed through as-is:
|
|
291
567
|
|
|
292
568
|
```typescript
|
|
293
|
-
new ErrorX({ message:
|
|
294
|
-
//
|
|
569
|
+
new ErrorX({ message: "test error" });
|
|
570
|
+
// → message: 'test error'
|
|
295
571
|
|
|
296
|
-
new ErrorX({ message:
|
|
297
|
-
//
|
|
572
|
+
new ErrorX({ message: "Test error." });
|
|
573
|
+
// → message: 'Test error.'
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Empty or whitespace-only messages default to `'An error occurred'`:
|
|
298
577
|
|
|
299
|
-
|
|
300
|
-
|
|
578
|
+
```typescript
|
|
579
|
+
new ErrorX({ message: "" });
|
|
580
|
+
// → message: 'An error occurred'
|
|
301
581
|
```
|
|
302
582
|
|
|
583
|
+
Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
303
587
|
## License
|
|
304
588
|
|
|
305
589
|
MIT
|