@bombillazo/error-x 0.4.6 → 0.5.1
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 +473 -195
- package/dist/index.cjs +597 -246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1074 -436
- package/dist/index.d.ts +1074 -436
- package/dist/index.js +595 -246
- 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,273 +36,546 @@ 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` | `ErrorXSnapshot \| 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.
|
|
95
136
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
109
160
|
|
|
110
|
-
|
|
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 | `unknown` | `undefined` | Error that caused this (builds the chain) |
|
|
111
170
|
|
|
112
|
-
|
|
171
|
+
## Global Configuration
|
|
172
|
+
|
|
173
|
+
Configure stack trace cleaning and other global settings.
|
|
113
174
|
|
|
114
175
|
```typescript
|
|
115
|
-
import { ErrorX
|
|
176
|
+
import { ErrorX } from "@bombillazo/error-x";
|
|
116
177
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
178
|
+
// Enable stack cleaning with custom delimiter
|
|
179
|
+
ErrorX.configure({
|
|
180
|
+
cleanStack: true,
|
|
181
|
+
cleanStackDelimiter: "my-app-entry",
|
|
182
|
+
});
|
|
120
183
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
metadata: { userId: 123 }
|
|
126
|
-
})
|
|
184
|
+
// Custom patterns to remove from stack traces
|
|
185
|
+
ErrorX.configure({
|
|
186
|
+
cleanStack: ["node_modules", "internal/"],
|
|
187
|
+
});
|
|
127
188
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
137
|
-
}
|
|
189
|
+
// Disable stack cleaning
|
|
190
|
+
ErrorX.configure({ cleanStack: false });
|
|
191
|
+
|
|
192
|
+
// Get current config
|
|
193
|
+
const config = ErrorX.getConfig();
|
|
194
|
+
|
|
195
|
+
// Reset to defaults
|
|
196
|
+
ErrorX.resetConfig();
|
|
138
197
|
```
|
|
139
198
|
|
|
140
|
-
|
|
199
|
+
### Auto Code Generation
|
|
141
200
|
|
|
142
|
-
|
|
201
|
+
Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
|
|
143
202
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
203
|
+
```typescript
|
|
204
|
+
new ErrorX({ name: "DatabaseError" });
|
|
205
|
+
// → code: 'DATABASE_ERROR'
|
|
206
|
+
|
|
207
|
+
new ErrorX({ name: "userAuthError" });
|
|
208
|
+
// → code: 'USER_AUTH_ERROR'
|
|
209
|
+
|
|
210
|
+
new ErrorX({ name: "API Timeout" });
|
|
211
|
+
// → code: 'API_TIMEOUT'
|
|
212
|
+
```
|
|
149
213
|
|
|
150
|
-
|
|
214
|
+
### Message Formatting
|
|
151
215
|
|
|
152
|
-
|
|
216
|
+
ErrorX does NOT auto-format messages. Messages are passed through as-is:
|
|
153
217
|
|
|
154
218
|
```typescript
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// Define your application-specific presets
|
|
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>;
|
|
219
|
+
new ErrorX({ message: "test error" });
|
|
220
|
+
// → message: 'test error'
|
|
193
221
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
throw new ErrorX({ ...authErrors.invalidToken, metadata: { userId: 123 } });
|
|
222
|
+
new ErrorX({ message: "Test error." });
|
|
223
|
+
// → message: 'Test error.'
|
|
197
224
|
```
|
|
198
225
|
|
|
199
|
-
|
|
226
|
+
Empty or whitespace-only messages default to `'An error occurred'`:
|
|
200
227
|
|
|
228
|
+
```typescript
|
|
229
|
+
new ErrorX({ message: "" });
|
|
230
|
+
// → message: 'An error occurred'
|
|
231
|
+
```
|
|
201
232
|
|
|
202
|
-
|
|
233
|
+
Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
|
|
203
234
|
|
|
204
|
-
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Common Methods
|
|
238
|
+
|
|
239
|
+
### ErrorX.from()
|
|
240
|
+
|
|
241
|
+
Convert any value into an ErrorX instance with intelligent property extraction.
|
|
205
242
|
|
|
206
243
|
```typescript
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
function validateUser(user: unknown) {
|
|
210
|
-
if (!user) {
|
|
211
|
-
throw new ErrorX({
|
|
212
|
-
message: 'User validation failed: user is required',
|
|
213
|
-
name: 'ValidationError',
|
|
214
|
-
code: 'USER_REQUIRED',
|
|
215
|
-
uiMessage: 'Please provide user information',
|
|
216
|
-
metadata: { field: 'user', received: user }
|
|
217
|
-
})
|
|
218
|
-
}
|
|
219
|
-
}
|
|
244
|
+
static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>
|
|
220
245
|
```
|
|
221
246
|
|
|
222
|
-
|
|
247
|
+
Handles strings, Error objects, API responses, and unknown values. Extracts common properties like `message`, `code`, `status`, `statusCode`, and `metadata`.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Convert string
|
|
251
|
+
ErrorX.from("Something went wrong");
|
|
252
|
+
|
|
253
|
+
// Convert Error
|
|
254
|
+
ErrorX.from(new Error("Connection failed"));
|
|
255
|
+
|
|
256
|
+
// Convert API response
|
|
257
|
+
ErrorX.from({
|
|
258
|
+
message: "User not found",
|
|
259
|
+
code: "USER_404",
|
|
260
|
+
status: 404,
|
|
261
|
+
metadata: { userId: 123 },
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// With overrides (deep merged)
|
|
265
|
+
ErrorX.from(error, {
|
|
266
|
+
httpStatus: 500,
|
|
267
|
+
metadata: { context: "db-layer" },
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### ErrorX.isErrorX()
|
|
272
|
+
|
|
273
|
+
Type guard to check if a value is an ErrorX instance.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
static isErrorX<T>(value: unknown): value is ErrorX<T>
|
|
277
|
+
```
|
|
223
278
|
|
|
224
279
|
```typescript
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
...http[response.status === 404 ? 404 : 500],
|
|
231
|
-
metadata: { status: response.status, statusText: response.statusText }
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
return response.json()
|
|
235
|
-
} catch (error) {
|
|
236
|
-
const errorX = ErrorX.from(error)
|
|
237
|
-
throw errorX.withMetadata({ userId: id, operation: 'fetchUser' })
|
|
280
|
+
try {
|
|
281
|
+
await riskyOperation();
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (ErrorX.isErrorX(error)) {
|
|
284
|
+
console.log(error.code, error.metadata);
|
|
238
285
|
}
|
|
239
286
|
}
|
|
240
287
|
```
|
|
241
288
|
|
|
289
|
+
### withMetadata()
|
|
290
|
+
|
|
291
|
+
Create a new ErrorX with additional metadata merged with existing metadata.
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const error = new ErrorX({
|
|
299
|
+
message: "Request failed",
|
|
300
|
+
metadata: { endpoint: "/api/users" },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
|
|
304
|
+
// metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Serialization
|
|
308
|
+
|
|
309
|
+
Serialize ErrorX instances for network transfer or storage.
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Serialize
|
|
313
|
+
const json = error.toJSON();
|
|
314
|
+
// { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
|
|
315
|
+
|
|
316
|
+
// Deserialize
|
|
317
|
+
const restored = ErrorX.fromJSON(json);
|
|
318
|
+
```
|
|
319
|
+
|
|
242
320
|
### Error Chaining
|
|
243
321
|
|
|
322
|
+
Build error timelines by passing `cause` to preserve the full error history.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// Build an error chain
|
|
326
|
+
const dbError = ErrorX.from(new Error("ECONNREFUSED"));
|
|
327
|
+
const repoError = new ErrorX({ message: "Query failed", cause: dbError });
|
|
328
|
+
const serviceError = new ErrorX({
|
|
329
|
+
message: "User fetch failed",
|
|
330
|
+
cause: repoError,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Access chain information
|
|
334
|
+
serviceError.chain.length; // 3: [serviceError, repoError, dbError]
|
|
335
|
+
serviceError.parent; // repoError
|
|
336
|
+
serviceError.root; // dbError
|
|
337
|
+
dbError.original; // { message: 'ECONNREFUSED', name: 'Error', stack: '...' }
|
|
338
|
+
```
|
|
339
|
+
|
|
244
340
|
```typescript
|
|
341
|
+
// Practical example
|
|
245
342
|
try {
|
|
246
|
-
await database.
|
|
247
|
-
await tx.users.create(userData)
|
|
248
|
-
})
|
|
343
|
+
await database.query(sql);
|
|
249
344
|
} catch (dbError) {
|
|
250
|
-
throw
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
345
|
+
throw DBErrorX.create("QUERY_FAILED", {
|
|
346
|
+
cause: dbError,
|
|
347
|
+
metadata: { query: sql, table: "users" },
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Later, inspect the chain
|
|
352
|
+
if (ErrorX.isErrorX(error)) {
|
|
353
|
+
console.log(
|
|
354
|
+
"Error chain:",
|
|
355
|
+
error.chain.map((e) => e.name),
|
|
356
|
+
);
|
|
357
|
+
console.log("Root cause:", error.root?.original);
|
|
261
358
|
}
|
|
262
359
|
```
|
|
263
360
|
|
|
264
|
-
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Custom Error Classes
|
|
364
|
+
|
|
365
|
+
error-x includes several custom error classes out of the box:
|
|
366
|
+
|
|
367
|
+
- **Ready-to-use** - Practical error classes for common scenarios (HTTP, database, validation)
|
|
368
|
+
- **Educational** - Demonstrate how to use presets, defaults, and transforms
|
|
369
|
+
- **Extensible** - Serve as templates and inspiration for your own domain-specific error classes
|
|
370
|
+
|
|
371
|
+
### HTTPErrorX
|
|
265
372
|
|
|
266
|
-
|
|
373
|
+
HTTP errors with presets for all standard status codes (400-511).
|
|
267
374
|
|
|
268
375
|
```typescript
|
|
269
|
-
|
|
270
|
-
// message: 'test error' (exactly as provided)
|
|
376
|
+
import { HTTPErrorX } from "@bombillazo/error-x";
|
|
271
377
|
|
|
272
|
-
|
|
273
|
-
|
|
378
|
+
// Create by status code
|
|
379
|
+
HTTPErrorX.create(404);
|
|
380
|
+
// → code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
|
|
381
|
+
|
|
382
|
+
HTTPErrorX.create(401);
|
|
383
|
+
// → code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
|
|
384
|
+
|
|
385
|
+
// With overrides
|
|
386
|
+
HTTPErrorX.create(404, {
|
|
387
|
+
message: "User not found",
|
|
388
|
+
metadata: { userId: 123 },
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// With error chaining
|
|
392
|
+
HTTPErrorX.create(500, { cause: originalError });
|
|
393
|
+
|
|
394
|
+
// instanceof checks
|
|
395
|
+
if (error instanceof HTTPErrorX) {
|
|
396
|
+
console.log(error.httpStatus);
|
|
397
|
+
}
|
|
274
398
|
```
|
|
275
399
|
|
|
276
|
-
|
|
400
|
+
**Available Presets:** All 4xx client errors (400-451) and 5xx server errors (500-511).
|
|
401
|
+
|
|
402
|
+
### DBErrorX
|
|
403
|
+
|
|
404
|
+
Database errors with presets for common database scenarios. All codes are automatically prefixed with `DB_`.
|
|
277
405
|
|
|
278
406
|
```typescript
|
|
279
|
-
|
|
280
|
-
|
|
407
|
+
import { DBErrorX } from "@bombillazo/error-x";
|
|
408
|
+
|
|
409
|
+
// Connection errors
|
|
410
|
+
DBErrorX.create("CONNECTION_FAILED"); // → code: 'DB_CONNECTION_FAILED'
|
|
411
|
+
DBErrorX.create("CONNECTION_TIMEOUT");
|
|
412
|
+
DBErrorX.create("CONNECTION_REFUSED");
|
|
413
|
+
DBErrorX.create("CONNECTION_LOST");
|
|
414
|
+
|
|
415
|
+
// Query errors
|
|
416
|
+
DBErrorX.create("QUERY_FAILED");
|
|
417
|
+
DBErrorX.create("QUERY_TIMEOUT");
|
|
418
|
+
DBErrorX.create("SYNTAX_ERROR");
|
|
419
|
+
|
|
420
|
+
// Constraint errors (with appropriate httpStatus)
|
|
421
|
+
DBErrorX.create("UNIQUE_VIOLATION"); // httpStatus: 409
|
|
422
|
+
DBErrorX.create("FOREIGN_KEY_VIOLATION"); // httpStatus: 400
|
|
423
|
+
DBErrorX.create("NOT_NULL_VIOLATION"); // httpStatus: 400
|
|
424
|
+
DBErrorX.create("CHECK_VIOLATION"); // httpStatus: 400
|
|
425
|
+
|
|
426
|
+
// Transaction errors
|
|
427
|
+
DBErrorX.create("TRANSACTION_FAILED");
|
|
428
|
+
DBErrorX.create("DEADLOCK"); // httpStatus: 409
|
|
429
|
+
|
|
430
|
+
// Record errors
|
|
431
|
+
DBErrorX.create("NOT_FOUND"); // httpStatus: 404
|
|
432
|
+
|
|
433
|
+
// With metadata
|
|
434
|
+
DBErrorX.create("QUERY_FAILED", {
|
|
435
|
+
message: "Failed to fetch user",
|
|
436
|
+
metadata: {
|
|
437
|
+
query: "SELECT * FROM users WHERE id = ?",
|
|
438
|
+
table: "users",
|
|
439
|
+
operation: "SELECT",
|
|
440
|
+
},
|
|
441
|
+
});
|
|
281
442
|
|
|
282
|
-
|
|
283
|
-
|
|
443
|
+
// instanceof checks
|
|
444
|
+
if (error instanceof DBErrorX) {
|
|
445
|
+
console.log(error.metadata?.table);
|
|
446
|
+
}
|
|
284
447
|
```
|
|
285
448
|
|
|
286
|
-
|
|
449
|
+
### ValidationErrorX
|
|
287
450
|
|
|
288
|
-
|
|
451
|
+
Validation errors with Zod integration. All codes are prefixed with `VALIDATION_`.
|
|
289
452
|
|
|
290
|
-
|
|
453
|
+
```typescript
|
|
454
|
+
import { z } from "zod";
|
|
455
|
+
import { ValidationErrorX } from "@bombillazo/error-x";
|
|
456
|
+
|
|
457
|
+
// From Zod errors
|
|
458
|
+
const schema = z.object({
|
|
459
|
+
email: z.string().email(),
|
|
460
|
+
age: z.number().min(18),
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
schema.parse({ email: "invalid", age: 15 });
|
|
465
|
+
} catch (err) {
|
|
466
|
+
if (err instanceof z.ZodError) {
|
|
467
|
+
throw ValidationErrorX.fromZodError(err);
|
|
468
|
+
// → code: 'VALIDATION_INVALID_STRING'
|
|
469
|
+
// → metadata.field: 'email'
|
|
470
|
+
// → metadata.zodCode: 'invalid_string'
|
|
471
|
+
// → httpStatus: 400
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// With overrides
|
|
476
|
+
ValidationErrorX.fromZodError(zodError, {
|
|
477
|
+
uiMessage: "Please check your input",
|
|
478
|
+
httpStatus: 422,
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Field-specific errors (without Zod)
|
|
482
|
+
ValidationErrorX.forField("email", "Invalid email format");
|
|
483
|
+
ValidationErrorX.forField("age", "Must be 18 or older", { code: "TOO_YOUNG" });
|
|
484
|
+
|
|
485
|
+
// Direct creation
|
|
486
|
+
ValidationErrorX.create({
|
|
487
|
+
message: "Invalid input",
|
|
488
|
+
code: "INVALID_INPUT",
|
|
489
|
+
metadata: { field: "email" },
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// instanceof checks
|
|
493
|
+
if (error instanceof ValidationErrorX) {
|
|
494
|
+
console.log(error.metadata?.field);
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Creating Custom Error Classes
|
|
499
|
+
|
|
500
|
+
Extend `ErrorX` to create domain-specific error classes with presets, defaults, and transforms.
|
|
291
501
|
|
|
292
502
|
```typescript
|
|
293
|
-
|
|
294
|
-
|
|
503
|
+
import {
|
|
504
|
+
ErrorX,
|
|
505
|
+
type ErrorXOptions,
|
|
506
|
+
type ErrorXTransform,
|
|
507
|
+
} from "@bombillazo/error-x";
|
|
508
|
+
|
|
509
|
+
// 1. Define your metadata type
|
|
510
|
+
type PaymentMetadata = {
|
|
511
|
+
transactionId?: string;
|
|
512
|
+
amount?: number;
|
|
513
|
+
currency?: string;
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// 2. Define presets outside the class for type inference
|
|
517
|
+
const paymentPresets = {
|
|
518
|
+
INSUFFICIENT_FUNDS: {
|
|
519
|
+
code: "INSUFFICIENT_FUNDS",
|
|
520
|
+
name: "PaymentError",
|
|
521
|
+
message: "Insufficient funds.",
|
|
522
|
+
uiMessage: "Your payment method has insufficient funds.",
|
|
523
|
+
httpStatus: 402,
|
|
524
|
+
},
|
|
525
|
+
CARD_DECLINED: {
|
|
526
|
+
code: "CARD_DECLINED",
|
|
527
|
+
name: "PaymentError",
|
|
528
|
+
message: "Card declined.",
|
|
529
|
+
uiMessage: "Your card was declined. Please try another payment method.",
|
|
530
|
+
httpStatus: 402,
|
|
531
|
+
},
|
|
532
|
+
EXPIRED_CARD: {
|
|
533
|
+
code: "EXPIRED_CARD",
|
|
534
|
+
name: "PaymentError",
|
|
535
|
+
message: "Card expired.",
|
|
536
|
+
uiMessage: "Your card has expired. Please update your payment method.",
|
|
537
|
+
httpStatus: 402,
|
|
538
|
+
},
|
|
539
|
+
} as const satisfies Record<string, ErrorXOptions>;
|
|
540
|
+
|
|
541
|
+
// 3. Derive preset key type
|
|
542
|
+
type PaymentPresetKey = keyof typeof paymentPresets | (string & {});
|
|
543
|
+
|
|
544
|
+
// 4. Create the class
|
|
545
|
+
export class PaymentErrorX extends ErrorX<PaymentMetadata> {
|
|
546
|
+
static presets = paymentPresets;
|
|
547
|
+
static defaultPreset = "CARD_DECLINED";
|
|
548
|
+
static defaults = { httpStatus: 402 };
|
|
549
|
+
|
|
550
|
+
// Optional: transform to prefix codes
|
|
551
|
+
static transform: ErrorXTransform<PaymentMetadata> = (opts) => ({
|
|
552
|
+
...opts,
|
|
553
|
+
code: `PAYMENT_${opts.code}`,
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Override create for proper typing
|
|
557
|
+
static override create(
|
|
558
|
+
presetKey?: PaymentPresetKey,
|
|
559
|
+
overrides?: Partial<ErrorXOptions<PaymentMetadata>>,
|
|
560
|
+
): PaymentErrorX {
|
|
561
|
+
return ErrorX.create.call(
|
|
562
|
+
PaymentErrorX,
|
|
563
|
+
presetKey,
|
|
564
|
+
overrides,
|
|
565
|
+
) as PaymentErrorX;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
295
568
|
|
|
296
|
-
|
|
297
|
-
|
|
569
|
+
// Usage
|
|
570
|
+
throw PaymentErrorX.create("INSUFFICIENT_FUNDS");
|
|
571
|
+
throw PaymentErrorX.create("CARD_DECLINED", {
|
|
572
|
+
metadata: { transactionId: "tx_123", amount: 99.99, currency: "USD" },
|
|
573
|
+
});
|
|
298
574
|
|
|
299
|
-
|
|
300
|
-
|
|
575
|
+
// instanceof works
|
|
576
|
+
if (error instanceof PaymentErrorX) {
|
|
577
|
+
console.log(error.metadata?.transactionId);
|
|
578
|
+
}
|
|
301
579
|
```
|
|
302
580
|
|
|
303
581
|
## License
|