@bombillazo/error-x 0.4.5 → 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 +445 -278
- package/dist/index.cjs +566 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1072 -431
- package/dist/index.d.ts +1072 -431
- package/dist/index.js +564 -234
- package/dist/index.js.map +1 -1
- package/package.json +3 -16
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,392 +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
50
|
## Quick Start
|
|
46
51
|
|
|
47
52
|
```typescript
|
|
48
|
-
import { ErrorX,
|
|
53
|
+
import { ErrorX, HTTPErrorX, DBErrorX } from "@bombillazo/error-x";
|
|
49
54
|
|
|
50
55
|
// Simple usage
|
|
51
|
-
throw new ErrorX(
|
|
56
|
+
throw new ErrorX("Something went wrong");
|
|
52
57
|
|
|
53
|
-
//
|
|
54
|
-
throw new ErrorX({
|
|
55
|
-
message: 'User authentication failed',
|
|
56
|
-
name: 'AuthError',
|
|
57
|
-
code: 'AUTH_FAILED',
|
|
58
|
-
uiMessage: 'Please check your credentials and try again',
|
|
59
|
-
metadata: { userId: 123, loginAttempt: 3 },
|
|
60
|
-
sourceUrl: 'https://api.example.com/auth',
|
|
61
|
-
source: 'auth-service',
|
|
62
|
-
httpStatus: 401
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// Using HTTP presets
|
|
66
|
-
throw new ErrorX(http[404])
|
|
67
|
-
|
|
68
|
-
// Customizing presets
|
|
58
|
+
// A fully defined error
|
|
69
59
|
throw new ErrorX({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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);
|
|
82
74
|
```
|
|
83
75
|
|
|
84
76
|
## Documentation
|
|
85
77
|
|
|
86
|
-
|
|
78
|
+
[Full Documentation](docs/index.md)
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
new ErrorX(input?: string | ErrorXOptions)
|
|
90
|
-
```
|
|
80
|
+
---
|
|
91
81
|
|
|
92
|
-
|
|
82
|
+
## API Reference
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
| --------- | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
|
|
96
|
-
| message | `string` | `'An error occurred'` | Technical error message (pass-through, no auto-formatting) |
|
|
97
|
-
| name | `string` | `'Error'` | Error type/title |
|
|
98
|
-
| code | `string \| number` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
|
|
99
|
-
| uiMessage | `string \| undefined` | `undefined` | User-friendly message for display |
|
|
100
|
-
| cause | `Error \| unknown` | `undefined` | Original error that caused this (preserves full error chain) |
|
|
101
|
-
| metadata | `Record<string, unknown> \| undefined` | `undefined` | Additional context and data |
|
|
102
|
-
| httpStatus | `number \| undefined` | `undefined` | HTTP status code (100-599) for HTTP-related errors |
|
|
103
|
-
| type | `string \| undefined` | `undefined` | Error type for categorization (e.g., 'http', 'validation') |
|
|
104
|
-
| sourceUrl | `string \| undefined` | `undefined` | URL related to the error (API endpoint, page URL, resource URL) |
|
|
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) |
|
|
84
|
+
### ErrorX Class
|
|
109
85
|
|
|
110
|
-
|
|
86
|
+
The base error class that extends the native `Error` with enhanced capabilities.
|
|
111
87
|
|
|
112
|
-
|
|
88
|
+
#### Properties
|
|
113
89
|
|
|
114
|
-
|
|
115
|
-
|
|
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 |
|
|
116
104
|
|
|
117
|
-
|
|
118
|
-
throw new ErrorX(http[404])
|
|
119
|
-
// Result: 404 error with message "Not found.", code "NOT_FOUND", etc.
|
|
105
|
+
#### Static Methods
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 |
|
|
127
118
|
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
// some operation
|
|
131
|
-
} catch (originalError) {
|
|
132
|
-
throw new ErrorX({
|
|
133
|
-
...http[500],
|
|
134
|
-
cause: originalError,
|
|
135
|
-
metadata: { operation: 'database-query' }
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
```
|
|
119
|
+
#### Instance Methods
|
|
139
120
|
|
|
140
|
-
|
|
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 |
|
|
141
126
|
|
|
142
|
-
|
|
143
|
-
- `httpStatus`: HTTP status code
|
|
144
|
-
- `code`: Error code in UPPER_SNAKE_CASE
|
|
145
|
-
- `name`: Descriptive error name
|
|
146
|
-
- `message`: Technical message with proper sentence casing and period
|
|
147
|
-
- `uiMessage`: User-friendly message
|
|
148
|
-
- `type`: Set to `'http'` for all HTTP presets
|
|
127
|
+
---
|
|
149
128
|
|
|
150
|
-
|
|
129
|
+
## Constructor
|
|
151
130
|
|
|
152
|
-
|
|
131
|
+
```typescript
|
|
132
|
+
new ErrorX(input?: string | ErrorXOptions)
|
|
133
|
+
```
|
|
153
134
|
|
|
154
|
-
|
|
155
|
-
`500`, `501`, `502`, `503`, `504`, `505`, `506`, `507`, `508`, `510`, `511`
|
|
135
|
+
Create a new ErrorX instance. All parameters are optional with sensible defaults.
|
|
156
136
|
|
|
157
|
-
|
|
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
158
|
|
|
159
|
-
|
|
159
|
+
### ErrorXOptions
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
uiMessage: 'Unable to connect to database. Please try again later.',
|
|
171
|
-
type: 'database',
|
|
172
|
-
},
|
|
173
|
-
queryTimeout: {
|
|
174
|
-
name: 'DatabaseError',
|
|
175
|
-
code: 'DB_QUERY_TIMEOUT',
|
|
176
|
-
message: 'Database query timeout.',
|
|
177
|
-
uiMessage: 'The operation took too long. Please try again.',
|
|
178
|
-
type: 'database',
|
|
179
|
-
},
|
|
180
|
-
} satisfies Record<string, ErrorXOptions>;
|
|
181
|
-
|
|
182
|
-
export const authErrors = {
|
|
183
|
-
invalidToken: {
|
|
184
|
-
name: 'AuthenticationError',
|
|
185
|
-
code: 'AUTH_INVALID_TOKEN',
|
|
186
|
-
message: 'Invalid authentication token.',
|
|
187
|
-
uiMessage: 'Your session has expired. Please log in again.',
|
|
188
|
-
httpStatus: 401,
|
|
189
|
-
type: 'authentication',
|
|
190
|
-
},
|
|
191
|
-
insufficientPermissions: {
|
|
192
|
-
name: 'AuthorizationError',
|
|
193
|
-
code: 'AUTH_INSUFFICIENT_PERMISSIONS',
|
|
194
|
-
message: 'Insufficient permissions.',
|
|
195
|
-
uiMessage: 'You do not have permission to perform this action.',
|
|
196
|
-
httpStatus: 403,
|
|
197
|
-
type: 'authorization',
|
|
198
|
-
},
|
|
199
|
-
} satisfies Record<string, ErrorXOptions>;
|
|
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) |
|
|
200
170
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
```
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Common Methods
|
|
205
174
|
|
|
206
|
-
|
|
175
|
+
### ErrorX.from()
|
|
207
176
|
|
|
208
|
-
|
|
177
|
+
Convert any value into an ErrorX instance with intelligent property extraction.
|
|
209
178
|
|
|
210
|
-
|
|
179
|
+
```typescript
|
|
180
|
+
static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>
|
|
181
|
+
```
|
|
211
182
|
|
|
212
|
-
|
|
183
|
+
Handles strings, Error objects, API responses, and unknown values. Extracts common properties like `message`, `code`, `status`, `statusCode`, and `metadata`.
|
|
213
184
|
|
|
214
185
|
```typescript
|
|
215
|
-
// Convert
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
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
|
+
});
|
|
232
205
|
```
|
|
233
206
|
|
|
234
|
-
|
|
207
|
+
### ErrorX.isErrorX()
|
|
235
208
|
|
|
236
|
-
Type guard to check if a value is an ErrorX instance
|
|
209
|
+
Type guard to check if a value is an ErrorX instance.
|
|
237
210
|
|
|
238
211
|
```typescript
|
|
239
|
-
|
|
240
|
-
|
|
212
|
+
static isErrorX<T>(value: unknown): value is ErrorX<T>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
try {
|
|
217
|
+
await riskyOperation();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (ErrorX.isErrorX(error)) {
|
|
220
|
+
console.log(error.code, error.metadata);
|
|
221
|
+
}
|
|
241
222
|
}
|
|
242
223
|
```
|
|
243
224
|
|
|
244
|
-
|
|
225
|
+
### withMetadata()
|
|
245
226
|
|
|
246
|
-
|
|
227
|
+
Create a new ErrorX with additional metadata merged with existing metadata.
|
|
247
228
|
|
|
248
229
|
```typescript
|
|
249
|
-
ErrorX
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
})
|
|
230
|
+
withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const error = new ErrorX({
|
|
235
|
+
message: "Request failed",
|
|
236
|
+
metadata: { endpoint: "/api/users" },
|
|
237
|
+
});
|
|
257
238
|
|
|
258
|
-
|
|
259
|
-
//
|
|
260
|
-
// - docsUrl: auto-generated from docsBaseURL + docsMap[code]
|
|
239
|
+
const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
|
|
240
|
+
// metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }
|
|
261
241
|
```
|
|
262
242
|
|
|
263
|
-
|
|
243
|
+
### Serialization
|
|
264
244
|
|
|
265
|
-
|
|
245
|
+
Serialize ErrorX instances for network transfer or storage.
|
|
266
246
|
|
|
267
247
|
```typescript
|
|
268
|
-
|
|
269
|
-
|
|
248
|
+
// Serialize
|
|
249
|
+
const json = error.toJSON();
|
|
250
|
+
// { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
|
|
251
|
+
|
|
252
|
+
// Deserialize
|
|
253
|
+
const restored = ErrorX.fromJSON(json);
|
|
270
254
|
```
|
|
271
255
|
|
|
272
|
-
###
|
|
256
|
+
### Error Chaining
|
|
273
257
|
|
|
274
|
-
|
|
258
|
+
Build error timelines by passing `cause` to preserve the full error history.
|
|
275
259
|
|
|
276
|
-
|
|
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
|
+
```
|
|
277
275
|
|
|
278
276
|
```typescript
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
+
}
|
|
282
295
|
```
|
|
283
296
|
|
|
284
|
-
|
|
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
|
|
285
306
|
|
|
286
|
-
|
|
307
|
+
### HTTPErrorX
|
|
308
|
+
|
|
309
|
+
HTTP errors with presets for all standard status codes (400-511).
|
|
287
310
|
|
|
288
311
|
```typescript
|
|
289
|
-
|
|
312
|
+
import { HTTPErrorX } from "@bombillazo/error-x";
|
|
290
313
|
|
|
291
|
-
//
|
|
292
|
-
|
|
314
|
+
// Create by status code
|
|
315
|
+
HTTPErrorX.create(404);
|
|
316
|
+
// → code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
|
|
293
317
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// Returns stack trace starting after the line containing 'my-app-boundary'
|
|
297
|
-
```
|
|
318
|
+
HTTPErrorX.create(401);
|
|
319
|
+
// → code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
|
|
298
320
|
|
|
299
|
-
|
|
321
|
+
// With overrides
|
|
322
|
+
HTTPErrorX.create(404, {
|
|
323
|
+
message: "User not found",
|
|
324
|
+
metadata: { userId: 123 },
|
|
325
|
+
});
|
|
300
326
|
|
|
301
|
-
|
|
327
|
+
// With error chaining
|
|
328
|
+
HTTPErrorX.create(500, { cause: originalError });
|
|
302
329
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
330
|
+
// instanceof checks
|
|
331
|
+
if (error instanceof HTTPErrorX) {
|
|
332
|
+
console.log(error.httpStatus);
|
|
333
|
+
}
|
|
307
334
|
```
|
|
308
335
|
|
|
309
|
-
|
|
336
|
+
**Available Presets:** All 4xx client errors (400-451) and 5xx server errors (500-511).
|
|
337
|
+
|
|
338
|
+
### DBErrorX
|
|
310
339
|
|
|
311
|
-
|
|
340
|
+
Database errors with presets for common database scenarios. All codes are automatically prefixed with `DB_`.
|
|
312
341
|
|
|
313
342
|
```typescript
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
//
|
|
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);
|
|
382
|
+
}
|
|
317
383
|
```
|
|
318
384
|
|
|
319
|
-
|
|
385
|
+
### ValidationErrorX
|
|
320
386
|
|
|
321
|
-
|
|
387
|
+
Validation errors with Zod integration. All codes are prefixed with `VALIDATION_`.
|
|
322
388
|
|
|
323
389
|
```typescript
|
|
324
|
-
import {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
+
|
|
399
|
+
try {
|
|
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
|
|
335
408
|
}
|
|
336
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);
|
|
431
|
+
}
|
|
337
432
|
```
|
|
338
433
|
|
|
339
|
-
###
|
|
434
|
+
### Creating Custom Error Classes
|
|
435
|
+
|
|
436
|
+
Extend `ErrorX` to create domain-specific error classes with presets, defaults, and transforms.
|
|
340
437
|
|
|
341
438
|
```typescript
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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;
|
|
355
502
|
}
|
|
356
503
|
}
|
|
357
|
-
```
|
|
358
504
|
|
|
359
|
-
|
|
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
|
+
});
|
|
360
510
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
await tx.users.create(userData)
|
|
365
|
-
})
|
|
366
|
-
} catch (dbError) {
|
|
367
|
-
throw new ErrorX({
|
|
368
|
-
message: 'User creation failed',
|
|
369
|
-
name: 'UserCreationError',
|
|
370
|
-
code: 'USER_CREATE_FAILED',
|
|
371
|
-
uiMessage: 'Unable to create user account',
|
|
372
|
-
cause: dbError, // Preserves original stack trace
|
|
373
|
-
metadata: {
|
|
374
|
-
operation: 'userRegistration',
|
|
375
|
-
email: userData.email
|
|
376
|
-
}
|
|
377
|
-
})
|
|
511
|
+
// instanceof works
|
|
512
|
+
if (error instanceof PaymentErrorX) {
|
|
513
|
+
console.log(error.metadata?.transactionId);
|
|
378
514
|
}
|
|
379
515
|
```
|
|
380
516
|
|
|
381
|
-
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Other Usage
|
|
382
520
|
|
|
383
|
-
|
|
521
|
+
### Global Configuration
|
|
522
|
+
|
|
523
|
+
Configure stack trace cleaning and other global settings.
|
|
384
524
|
|
|
385
525
|
```typescript
|
|
386
|
-
|
|
387
|
-
|
|
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 });
|
|
388
541
|
|
|
389
|
-
|
|
390
|
-
|
|
542
|
+
// Get current config
|
|
543
|
+
const config = ErrorX.getConfig();
|
|
544
|
+
|
|
545
|
+
// Reset to defaults
|
|
546
|
+
ErrorX.resetConfig();
|
|
391
547
|
```
|
|
392
548
|
|
|
393
|
-
|
|
549
|
+
### Auto Code Generation
|
|
550
|
+
|
|
551
|
+
Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
|
|
394
552
|
|
|
395
553
|
```typescript
|
|
396
|
-
new ErrorX({
|
|
397
|
-
//
|
|
554
|
+
new ErrorX({ name: "DatabaseError" });
|
|
555
|
+
// → code: 'DATABASE_ERROR'
|
|
398
556
|
|
|
399
|
-
new ErrorX({
|
|
400
|
-
//
|
|
401
|
-
```
|
|
557
|
+
new ErrorX({ name: "userAuthError" });
|
|
558
|
+
// → code: 'USER_AUTH_ERROR'
|
|
402
559
|
|
|
403
|
-
|
|
560
|
+
new ErrorX({ name: "API Timeout" });
|
|
561
|
+
// → code: 'API_TIMEOUT'
|
|
562
|
+
```
|
|
404
563
|
|
|
405
|
-
|
|
564
|
+
### Message Formatting
|
|
406
565
|
|
|
407
|
-
|
|
566
|
+
ErrorX does NOT auto-format messages. Messages are passed through as-is:
|
|
408
567
|
|
|
409
568
|
```typescript
|
|
410
|
-
new ErrorX({ message:
|
|
411
|
-
//
|
|
569
|
+
new ErrorX({ message: "test error" });
|
|
570
|
+
// → message: 'test error'
|
|
412
571
|
|
|
413
|
-
new ErrorX({ message:
|
|
414
|
-
//
|
|
572
|
+
new ErrorX({ message: "Test error." });
|
|
573
|
+
// → message: 'Test error.'
|
|
574
|
+
```
|
|
415
575
|
|
|
416
|
-
|
|
417
|
-
|
|
576
|
+
Empty or whitespace-only messages default to `'An error occurred'`:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
new ErrorX({ message: "" });
|
|
580
|
+
// → message: 'An error occurred'
|
|
418
581
|
```
|
|
419
582
|
|
|
583
|
+
Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
420
587
|
## License
|
|
421
588
|
|
|
422
589
|
MIT
|