@bombillazo/error-x 0.3.0 → 0.4.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 +235 -370
- package/dist/index.cjs +501 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +580 -675
- package/dist/index.d.ts +580 -675
- package/dist/index.js +501 -593
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@bombillazo/error-x)
|
|
5
5
|
[](https://github.com/bombillazo/error-x/blob/master/LICENSE)
|
|
6
6
|
|
|
7
|
-
A smart, isomorphic, and
|
|
7
|
+
A smart, isomorphic, and type-safe error library for TypeScript applications. Provides excellent DX with intelligent error conversion, stack trace preservation, serialization support, and HTTP error presets.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- 🎯 **Type-safe error handling**
|
|
11
|
+
- 🎯 **Type-safe error handling** with full TypeScript support
|
|
12
12
|
- 🔄 **Smart error conversion** from various formats (API responses, strings, Error objects)
|
|
13
|
-
- 📝 **Auto-formatted messages and error codes** with proper capitalization and punctuation
|
|
14
13
|
- 👤 **User-friendly messages** separate from technical messages
|
|
15
14
|
- 🔗 **Error chaining** with cause preservation and stack trace preservation
|
|
16
15
|
- 📊 **Flexible metadata** for additional context
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
16
|
+
- 📦 **Serialization/deserialization** for network transfer and storage
|
|
17
|
+
- 🎨 **HTTP error presets** for all status codes (400-511)
|
|
18
|
+
- 🌐 **Isomorphic** - works in Node.js and browsers
|
|
19
|
+
- ⚙️ **Global configuration** for defaults and documentation URLs
|
|
20
20
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
@@ -28,6 +28,15 @@ npm install @bombillazo/error-x
|
|
|
28
28
|
yarn add @bombillazo/error-x
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
### Requirements
|
|
32
|
+
|
|
33
|
+
- **Node.js**: 18 or higher
|
|
34
|
+
- **TypeScript**: 5.0 or higher (optional, but recommended)
|
|
35
|
+
- **Target Environment**: ES2022+
|
|
36
|
+
|
|
37
|
+
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
|
+
|
|
39
|
+
|
|
31
40
|
> [!WARNING]
|
|
32
41
|
>
|
|
33
42
|
> This library is currently in pre-v1.0 development. While we strive to minimize breaking changes, the API may evolve based on feedback and real-world usage. We recommend pinning to specific versions and reviewing release notes when updating.
|
|
@@ -37,405 +46,269 @@ yarn add @bombillazo/error-x
|
|
|
37
46
|
## Quick Start
|
|
38
47
|
|
|
39
48
|
```typescript
|
|
40
|
-
import { ErrorX,
|
|
49
|
+
import { ErrorX, http } from '@bombillazo/error-x'
|
|
41
50
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Simple string message
|
|
46
|
-
const error = new ErrorX('Database connection failed')
|
|
51
|
+
// Simple usage
|
|
52
|
+
throw new ErrorX('Database connection failed')
|
|
47
53
|
|
|
48
|
-
//
|
|
49
|
-
|
|
54
|
+
// With options
|
|
55
|
+
throw new ErrorX({
|
|
56
|
+
message: 'User authentication failed',
|
|
50
57
|
name: 'AuthError',
|
|
51
58
|
code: 'AUTH_FAILED',
|
|
52
59
|
uiMessage: 'Please check your credentials and try again',
|
|
53
|
-
metadata: { userId: 123, loginAttempt: 3 }
|
|
60
|
+
metadata: { userId: 123, loginAttempt: 3 },
|
|
61
|
+
sourceUrl: 'https://api.example.com/auth',
|
|
62
|
+
source: 'auth-service',
|
|
63
|
+
httpStatus: 401
|
|
54
64
|
})
|
|
55
65
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
metadata: {
|
|
64
|
-
userId: 123,
|
|
65
|
-
loginAttempt: 3,
|
|
66
|
-
},
|
|
67
|
-
actions: [
|
|
68
|
-
{ action: 'notify', payload: { targets: [HandlingTargets.TOAST, HandlingTargets.BANNER] } },
|
|
69
|
-
{ action: 'redirect', payload: { redirectURL: '/login', delay: 1000 } },
|
|
70
|
-
{ action: 'custom', payload: { type: 'analytics', event: 'auth_failed', userId: 123, category: 'errors', severity: 'high' } }
|
|
71
|
-
]
|
|
66
|
+
// Using HTTP presets
|
|
67
|
+
throw new ErrorX(http.notFound)
|
|
68
|
+
|
|
69
|
+
// Customizing presets
|
|
70
|
+
throw new ErrorX({
|
|
71
|
+
...http.unauthorized,
|
|
72
|
+
message: 'Session expired',
|
|
73
|
+
metadata: { userId: 123 }
|
|
72
74
|
})
|
|
73
75
|
|
|
74
76
|
// Smart conversion from unknown errors
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
try {
|
|
78
|
+
await someOperation()
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const errorX = ErrorX.from(error)
|
|
81
|
+
throw errorX.withMetadata({ context: 'additional info' })
|
|
82
|
+
}
|
|
77
83
|
```
|
|
78
84
|
|
|
79
85
|
## Documentation
|
|
80
86
|
|
|
81
|
-
### API Reference
|
|
82
|
-
|
|
83
|
-
For complete API documentation with detailed descriptions, examples, and type information, see:
|
|
84
|
-
|
|
85
|
-
- **[📖 Complete API Documentation](docs/api/error-x.md)** - Full API reference with examples
|
|
86
|
-
- **[🏗️ ErrorX Class](docs/api/error-x.errorx.md)** - Main ErrorX class documentation
|
|
87
|
-
- **[🔧 Types](docs/api/error-x.md#type-aliases)** - All available types and interfaces
|
|
88
|
-
|
|
89
87
|
### Constructor
|
|
90
88
|
|
|
91
89
|
```typescript
|
|
92
|
-
|
|
93
|
-
new ErrorX(message: string, options?: {
|
|
94
|
-
name?: string // Optional: Error type
|
|
95
|
-
code?: string | number // Optional: Error code (auto-generated from name if not provided)
|
|
96
|
-
uiMessage?: string // Optional: User-friendly message
|
|
97
|
-
cause?: Error | unknown // Optional: Original error that caused this (preserves stack traces)
|
|
98
|
-
metadata?: Record<string, any> // Optional: Additional context data
|
|
99
|
-
actions?: ErrorAction[] // Optional: Configuration for application actions to perform when error occurs
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
// Options object signature (backward compatible)
|
|
103
|
-
new ErrorX(options?: {
|
|
104
|
-
name?: string // Optional: Error type
|
|
105
|
-
message?: string // Optional: Technical error message (default: 'An error occurred')
|
|
106
|
-
code?: string | number // Optional: Error code (auto-generated from name if not provided)
|
|
107
|
-
uiMessage?: string // Optional: User-friendly message
|
|
108
|
-
cause?: Error | unknown // Optional: Original error that caused this (preserves stack traces)
|
|
109
|
-
metadata?: Record<string, any> // Optional: Additional context data
|
|
110
|
-
actions?: ErrorAction[] // Optional: Configuration for application actions to perform when error occurs
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// Smart conversion signature (converts any unknown input)
|
|
114
|
-
new ErrorX(input: unknown)
|
|
90
|
+
new ErrorX(message?: string | ErrorXOptions)
|
|
115
91
|
```
|
|
116
92
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
### Properties
|
|
93
|
+
All parameters are optional. ErrorX uses sensible defaults:
|
|
120
94
|
|
|
121
95
|
| Property | Type | Default Value | Description |
|
|
122
96
|
| --------- | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
|
|
97
|
+
| message | `string` | `'An error occurred'` | Technical error message (pass-through, no auto-formatting) |
|
|
123
98
|
| name | `string` | `'Error'` | Error type/title |
|
|
124
|
-
| code | `string`
|
|
125
|
-
| message | `string` | `'An error occurred'` | Auto-formatted technical error message |
|
|
99
|
+
| code | `string \| number` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
|
|
126
100
|
| uiMessage | `string \| undefined` | `undefined` | User-friendly message for display |
|
|
101
|
+
| cause | `Error \| unknown` | `undefined` | Original error that caused this (preserves full error chain) |
|
|
102
|
+
| metadata | `Record<string, unknown> \| undefined` | `undefined` | Additional context and data |
|
|
103
|
+
| httpStatus | `number \| undefined` | `undefined` | HTTP status code (100-599) for HTTP-related errors |
|
|
104
|
+
| type | `string \| undefined` | `undefined` | Error type for categorization (e.g., 'http', 'validation') |
|
|
105
|
+
| sourceUrl | `string \| undefined` | `undefined` | URL related to the error (API endpoint, page URL, resource URL) |
|
|
106
|
+
| docsUrl | `string \| undefined` | `undefined` or auto-generated | Documentation URL for this specific error |
|
|
107
|
+
| source | `string \| undefined` | `undefined` or from config | Where the error originated (service name, module, component) |
|
|
108
|
+
| timestamp | `Date` | `new Date()` | When the error was created (read-only) |
|
|
127
109
|
| stack | `string` | Auto-generated | Stack trace with preservation and cleaning (inherited from Error) |
|
|
128
|
-
| cause | `unknown` | `undefined` | Original error that caused this (preserves full error chain) |
|
|
129
|
-
| timestamp | `Date` | `new Date()` | When the error was created (readonly) |
|
|
130
|
-
| metadata | `Record<string, any> \| undefined` | `undefined` | Additional context and data |
|
|
131
|
-
| actions | `ErrorAction[] \| undefined` | `undefined` | Array of actions to perform when error occurs (readonly) |
|
|
132
|
-
|
|
133
|
-
### Actions System
|
|
134
110
|
|
|
135
|
-
|
|
111
|
+
### HTTP Error Presets
|
|
136
112
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
#### Action Types
|
|
140
|
-
|
|
141
|
-
| Action Type | Action Value | Required Payload | Description |
|
|
142
|
-
| ----------- | ------------ | ---------------- | ----------- |
|
|
143
|
-
| NotifyAction | `'notify'` | `{ targets: HandlingTarget[], ...any }` | Display notification in specified UI targets |
|
|
144
|
-
| LogoutAction | `'logout'` | `{ ...any }` (optional) | Log out the current user |
|
|
145
|
-
| RedirectAction | `'redirect'` | `{ redirectURL: string, ...any }` | Redirect to a specific URL |
|
|
146
|
-
| CustomAction | `'custom'` | `{ ...any }` (optional) | Application-specific actions with flexible payload structure |
|
|
113
|
+
ErrorX provides pre-configured error templates via the `http` export:
|
|
147
114
|
|
|
148
115
|
```typescript
|
|
149
|
-
import {
|
|
150
|
-
|
|
151
|
-
// Predefined actions with typed payloads
|
|
152
|
-
const error1 = new ErrorX({
|
|
153
|
-
message: 'Payment failed',
|
|
154
|
-
actions: [
|
|
155
|
-
{ action: 'notify', payload: { targets: [HandlingTargets.MODAL] } },
|
|
156
|
-
{ action: 'redirect', payload: { redirectURL: '/payment', delay: 2000 } }
|
|
157
|
-
]
|
|
158
|
-
})
|
|
116
|
+
import { ErrorX, http } from '@bombillazo/error-x'
|
|
159
117
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
actions: [
|
|
164
|
-
{ action: 'logout', payload: { clearStorage: true } },
|
|
165
|
-
{ action: 'notify', payload: { targets: [HandlingTargets.TOAST] } }
|
|
166
|
-
]
|
|
167
|
-
})
|
|
118
|
+
// Use preset directly
|
|
119
|
+
throw new ErrorX(http.notFound)
|
|
120
|
+
// Result: 404 error with message "Not found.", code "NOT_FOUND", etc.
|
|
168
121
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
action: 'custom',
|
|
175
|
-
payload: {
|
|
176
|
-
type: 'show-rate-limit-modal',
|
|
177
|
-
resetTime: Date.now() + 60000,
|
|
178
|
-
message: 'Too many requests. Please wait.'
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
action: 'custom',
|
|
183
|
-
payload: {
|
|
184
|
-
type: 'analytics-track',
|
|
185
|
-
event: 'rate_limit_hit',
|
|
186
|
-
severity: 'warning',
|
|
187
|
-
category: 'api'
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
action: 'custom',
|
|
192
|
-
payload: {
|
|
193
|
-
type: 'cache-request',
|
|
194
|
-
retryAfter: 60,
|
|
195
|
-
endpoint: '/api/users'
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
]
|
|
122
|
+
// Override specific fields
|
|
123
|
+
throw new ErrorX({
|
|
124
|
+
...http.notFound,
|
|
125
|
+
message: 'User not found',
|
|
126
|
+
metadata: { userId: 123 }
|
|
199
127
|
})
|
|
128
|
+
|
|
129
|
+
// Add error cause
|
|
130
|
+
try {
|
|
131
|
+
// some operation
|
|
132
|
+
} catch (originalError) {
|
|
133
|
+
throw new ErrorX({
|
|
134
|
+
...http.internalServerError,
|
|
135
|
+
cause: originalError,
|
|
136
|
+
metadata: { operation: 'database-query' }
|
|
137
|
+
})
|
|
138
|
+
}
|
|
200
139
|
```
|
|
201
140
|
|
|
202
|
-
|
|
141
|
+
#### Available Presets
|
|
203
142
|
|
|
204
|
-
|
|
143
|
+
All presets use **camelCase naming** and include:
|
|
144
|
+
- `httpStatus`: HTTP status code
|
|
145
|
+
- `code`: Error code in UPPER_SNAKE_CASE
|
|
146
|
+
- `name`: Descriptive error name
|
|
147
|
+
- `message`: Technical message with proper sentence casing and period
|
|
148
|
+
- `uiMessage`: User-friendly message
|
|
149
|
+
- `type`: Set to `'http'` for all HTTP presets
|
|
205
150
|
|
|
206
|
-
|
|
151
|
+
**4xx Client Errors:**
|
|
152
|
+
`badRequest`, `unauthorized`, `paymentRequired`, `forbidden`, `notFound`, `methodNotAllowed`, `notAcceptable`, `proxyAuthenticationRequired`, `requestTimeout`, `conflict`, `gone`, `lengthRequired`, `preconditionFailed`, `payloadTooLarge`, `uriTooLong`, `unsupportedMediaType`, `rangeNotSatisfiable`, `expectationFailed`, `imATeapot`, `unprocessableEntity`, `locked`, `failedDependency`, `tooEarly`, `upgradeRequired`, `preconditionRequired`, `tooManyRequests`, `requestHeaderFieldsTooLarge`, `unavailableForLegalReasons`
|
|
207
153
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
| MODAL | `'modal'` | Display in a modal dialog |
|
|
211
|
-
| TOAST | `'toast'` | Display as a toast notification |
|
|
212
|
-
| INLINE | `'inline'` | Display inline with content |
|
|
213
|
-
| BANNER | `'banner'` | Display as a banner/alert bar |
|
|
214
|
-
| CONSOLE | `'console'` | Log to browser/server console |
|
|
215
|
-
| LOGGER | `'logger'` | Send to logging service |
|
|
216
|
-
| NOTIFICATION | `'notification'` | System notification |
|
|
154
|
+
**5xx Server Errors:**
|
|
155
|
+
`internalServerError`, `notImplemented`, `badGateway`, `serviceUnavailable`, `gatewayTimeout`, `httpVersionNotSupported`, `variantAlsoNegotiates`, `insufficientStorage`, `loopDetected`, `notExtended`, `networkAuthenticationRequired`
|
|
217
156
|
|
|
218
|
-
|
|
219
|
-
import { HandlingTargets, type HandlingTarget } from 'error-x'
|
|
220
|
-
|
|
221
|
-
const error = new ErrorX({
|
|
222
|
-
message: 'Mixed error',
|
|
223
|
-
actions: [
|
|
224
|
-
{
|
|
225
|
-
action: 'notify',
|
|
226
|
-
payload: {
|
|
227
|
-
targets: [
|
|
228
|
-
HandlingTargets.CONSOLE, // Predefined
|
|
229
|
-
'my-custom-logger', // Custom
|
|
230
|
-
HandlingTargets.BANNER, // Predefined
|
|
231
|
-
'analytics-tracker' // Custom
|
|
232
|
-
]
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
]
|
|
236
|
-
})
|
|
237
|
-
```
|
|
157
|
+
#### Creating Your Own Presets
|
|
238
158
|
|
|
239
|
-
|
|
159
|
+
HTTP presets work well because HTTP status codes are universally standardized. For domain-specific errors (database, validation, authentication, business logic), create your own presets:
|
|
240
160
|
|
|
241
|
-
|
|
161
|
+
```typescript
|
|
162
|
+
import { type ErrorXOptions } from '@bombillazo/error-x'
|
|
163
|
+
|
|
164
|
+
// Define your application-specific presets
|
|
165
|
+
export const dbErrors = {
|
|
166
|
+
connectionFailed: {
|
|
167
|
+
name: 'DatabaseError',
|
|
168
|
+
code: 'DB_CONNECTION_FAILED',
|
|
169
|
+
message: 'Database connection failed.',
|
|
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>;
|
|
242
200
|
|
|
243
|
-
|
|
201
|
+
// Use them just like http presets
|
|
202
|
+
throw new ErrorX(dbErrors.connectionFailed);
|
|
203
|
+
throw new ErrorX({ ...authErrors.invalidToken, metadata: { userId: 123 } });
|
|
204
|
+
```
|
|
244
205
|
|
|
245
|
-
|
|
246
|
-
- ✅ **Type-safe** with full TypeScript support
|
|
247
|
-
- ✅ **Fully customizable** via destructuring and override pattern
|
|
248
|
-
- ✅ **Categorized by type** - all HTTP presets include `type: 'http'` for easy filtering
|
|
249
|
-
- ✅ **User-friendly messages** included for all presets
|
|
206
|
+
This approach keeps your error handling consistent while remaining flexible for your specific domain.
|
|
250
207
|
|
|
251
|
-
###
|
|
208
|
+
### Static Methods
|
|
252
209
|
|
|
253
|
-
####
|
|
210
|
+
#### `ErrorX.from(error: unknown): ErrorX`
|
|
254
211
|
|
|
255
|
-
|
|
212
|
+
Converts any error type to ErrorX with intelligent property extraction:
|
|
256
213
|
|
|
257
214
|
```typescript
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
215
|
+
// Convert Error instances
|
|
216
|
+
const error = new Error('Something failed')
|
|
217
|
+
const errorX = ErrorX.from(error)
|
|
218
|
+
// Preserves: name, message, cause, stack
|
|
219
|
+
|
|
220
|
+
// Convert API responses
|
|
221
|
+
const apiError = { status: 404, statusText: 'Not Found', error: 'User not found' }
|
|
222
|
+
const errorX = ErrorX.from(apiError)
|
|
223
|
+
// Extracts: message, httpStatus, stores original in metadata
|
|
224
|
+
|
|
225
|
+
// Convert strings
|
|
226
|
+
const errorX = ErrorX.from('Something went wrong')
|
|
227
|
+
// Creates ErrorX with string as message
|
|
228
|
+
|
|
229
|
+
// Already ErrorX? Returns as-is
|
|
230
|
+
const errorX = ErrorX.from(new ErrorX('test'))
|
|
231
|
+
// Returns the same instance
|
|
263
232
|
```
|
|
264
233
|
|
|
265
|
-
####
|
|
234
|
+
#### `ErrorX.isErrorX(value: unknown): value is ErrorX`
|
|
266
235
|
|
|
267
|
-
|
|
236
|
+
Type guard to check if a value is an ErrorX instance:
|
|
268
237
|
|
|
269
238
|
```typescript
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
metadata: { userId: 123 }
|
|
274
|
-
})
|
|
275
|
-
// Result: 404 error with custom message but keeps httpStatus, code, name, uiMessage, type
|
|
239
|
+
if (ErrorX.isErrorX(error)) {
|
|
240
|
+
console.log(error.code, error.uiMessage)
|
|
241
|
+
}
|
|
276
242
|
```
|
|
277
243
|
|
|
278
|
-
####
|
|
244
|
+
#### `ErrorX.configure(config: ErrorXConfig): void`
|
|
279
245
|
|
|
280
|
-
|
|
246
|
+
Set global defaults for all ErrorX instances:
|
|
281
247
|
|
|
282
248
|
```typescript
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
249
|
+
ErrorX.configure({
|
|
250
|
+
source: 'my-api-service',
|
|
251
|
+
docsBaseURL: 'https://docs.example.com',
|
|
252
|
+
docsMap: {
|
|
253
|
+
'AUTH_FAILED': 'errors/authentication',
|
|
254
|
+
'NOT_FOUND': 'errors/not-found'
|
|
255
|
+
}
|
|
290
256
|
})
|
|
257
|
+
|
|
258
|
+
// Now all errors automatically get:
|
|
259
|
+
// - source: 'my-api-service' (unless overridden)
|
|
260
|
+
// - docsUrl: auto-generated from docsBaseURL + docsMap[code]
|
|
291
261
|
```
|
|
292
262
|
|
|
293
|
-
####
|
|
263
|
+
#### `ErrorX.getConfig(): ErrorXConfig | null`
|
|
294
264
|
|
|
295
|
-
|
|
265
|
+
Get the current global configuration:
|
|
296
266
|
|
|
297
267
|
```typescript
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} catch (originalError) {
|
|
301
|
-
throw new ErrorX({
|
|
302
|
-
...ErrorX.HTTP.INTERNAL_SERVER_ERROR,
|
|
303
|
-
cause: originalError,
|
|
304
|
-
metadata: { operation: 'database-query' }
|
|
305
|
-
})
|
|
306
|
-
}
|
|
268
|
+
const config = ErrorX.getConfig()
|
|
269
|
+
console.log(config?.source) // 'my-api-service'
|
|
307
270
|
```
|
|
308
271
|
|
|
309
|
-
###
|
|
310
|
-
|
|
311
|
-
#### 4xx Client Errors
|
|
312
|
-
|
|
313
|
-
| Preset | Status | Description |
|
|
314
|
-
| ------ | ------ | ----------- |
|
|
315
|
-
| BAD_REQUEST | 400 | Invalid request data |
|
|
316
|
-
| UNAUTHORIZED | 401 | Authentication required |
|
|
317
|
-
| PAYMENT_REQUIRED | 402 | Payment required to access resource |
|
|
318
|
-
| FORBIDDEN | 403 | Insufficient permissions |
|
|
319
|
-
| NOT_FOUND | 404 | Resource not found |
|
|
320
|
-
| METHOD_NOT_ALLOWED | 405 | HTTP method not allowed |
|
|
321
|
-
| NOT_ACCEPTABLE | 406 | Requested format not supported |
|
|
322
|
-
| PROXY_AUTHENTICATION_REQUIRED | 407 | Proxy authentication required |
|
|
323
|
-
| REQUEST_TIMEOUT | 408 | Request took too long |
|
|
324
|
-
| CONFLICT | 409 | Resource conflict |
|
|
325
|
-
| GONE | 410 | Resource no longer available |
|
|
326
|
-
| LENGTH_REQUIRED | 411 | Missing length information |
|
|
327
|
-
| PRECONDITION_FAILED | 412 | Required condition not met |
|
|
328
|
-
| PAYLOAD_TOO_LARGE | 413 | Request payload too large |
|
|
329
|
-
| URI_TOO_LONG | 414 | Request URL too long |
|
|
330
|
-
| UNSUPPORTED_MEDIA_TYPE | 415 | File type not supported |
|
|
331
|
-
| RANGE_NOT_SATISFIABLE | 416 | Requested range cannot be satisfied |
|
|
332
|
-
| EXPECTATION_FAILED | 417 | Server cannot meet request requirements |
|
|
333
|
-
| IM_A_TEAPOT | 418 | I'm a teapot (RFC 2324) |
|
|
334
|
-
| UNPROCESSABLE_ENTITY | 422 | Validation failed |
|
|
335
|
-
| LOCKED | 423 | Resource is locked |
|
|
336
|
-
| FAILED_DEPENDENCY | 424 | Request failed due to dependency error |
|
|
337
|
-
| TOO_EARLY | 425 | Request sent too early |
|
|
338
|
-
| UPGRADE_REQUIRED | 426 | Upgrade required to continue |
|
|
339
|
-
| PRECONDITION_REQUIRED | 428 | Required conditions missing |
|
|
340
|
-
| TOO_MANY_REQUESTS | 429 | Rate limit exceeded |
|
|
341
|
-
| REQUEST_HEADER_FIELDS_TOO_LARGE | 431 | Request headers too large |
|
|
342
|
-
| UNAVAILABLE_FOR_LEGAL_REASONS | 451 | Content unavailable for legal reasons |
|
|
343
|
-
|
|
344
|
-
#### 5xx Server Errors
|
|
345
|
-
|
|
346
|
-
| Preset | Status | Description |
|
|
347
|
-
| ------ | ------ | ----------- |
|
|
348
|
-
| INTERNAL_SERVER_ERROR | 500 | Unexpected server error |
|
|
349
|
-
| NOT_IMPLEMENTED | 501 | Feature not implemented |
|
|
350
|
-
| BAD_GATEWAY | 502 | Upstream server error |
|
|
351
|
-
| SERVICE_UNAVAILABLE | 503 | Service temporarily down |
|
|
352
|
-
| GATEWAY_TIMEOUT | 504 | Upstream timeout |
|
|
353
|
-
| HTTP_VERSION_NOT_SUPPORTED | 505 | HTTP version not supported |
|
|
354
|
-
| VARIANT_ALSO_NEGOTIATES | 506 | Internal configuration error |
|
|
355
|
-
| INSUFFICIENT_STORAGE | 507 | Insufficient storage |
|
|
356
|
-
| LOOP_DETECTED | 508 | Infinite loop detected |
|
|
357
|
-
| NOT_EXTENDED | 510 | Additional extensions required |
|
|
358
|
-
| NETWORK_AUTHENTICATION_REQUIRED | 511 | Network authentication required |
|
|
359
|
-
|
|
360
|
-
### Real-World Examples
|
|
361
|
-
|
|
362
|
-
#### API Endpoint
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
import { ErrorX } from '@bombillazo/error-x'
|
|
366
|
-
|
|
367
|
-
app.get('/users/:id', async (req, res) => {
|
|
368
|
-
const user = await db.users.findById(req.params.id)
|
|
369
|
-
|
|
370
|
-
if (!user) {
|
|
371
|
-
throw new ErrorX({
|
|
372
|
-
...ErrorX.HTTP.NOT_FOUND,
|
|
373
|
-
message: 'User not found',
|
|
374
|
-
metadata: { userId: req.params.id }
|
|
375
|
-
})
|
|
376
|
-
}
|
|
272
|
+
### Instance Methods
|
|
377
273
|
|
|
378
|
-
|
|
379
|
-
})
|
|
380
|
-
```
|
|
274
|
+
#### `withMetadata(additionalMetadata: Record<string, unknown>): ErrorX`
|
|
381
275
|
|
|
382
|
-
|
|
276
|
+
Creates a new ErrorX instance with merged metadata:
|
|
383
277
|
|
|
384
278
|
```typescript
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
...ErrorX.HTTP.UNAUTHORIZED,
|
|
389
|
-
actions: [
|
|
390
|
-
{ action: 'redirect', payload: { redirectURL: '/login' } }
|
|
391
|
-
]
|
|
392
|
-
})
|
|
393
|
-
}
|
|
394
|
-
next()
|
|
395
|
-
}
|
|
279
|
+
const error = new ErrorX({ message: 'test', metadata: { a: 1 } })
|
|
280
|
+
const enriched = error.withMetadata({ b: 2 })
|
|
281
|
+
// enriched.metadata = { a: 1, b: 2 }
|
|
396
282
|
```
|
|
397
283
|
|
|
398
|
-
####
|
|
284
|
+
#### `cleanStackTrace(delimiter?: string): ErrorX`
|
|
285
|
+
|
|
286
|
+
Removes stack trace lines before a delimiter:
|
|
399
287
|
|
|
400
288
|
```typescript
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
metadata: {
|
|
405
|
-
ip: req.ip,
|
|
406
|
-
retryAfter: 60
|
|
407
|
-
}
|
|
408
|
-
})
|
|
409
|
-
}
|
|
289
|
+
const error = new ErrorX('test')
|
|
290
|
+
const cleaned = error.cleanStackTrace('my-app-boundary')
|
|
291
|
+
// Stack trace only includes lines after 'my-app-boundary'
|
|
410
292
|
```
|
|
411
293
|
|
|
412
|
-
|
|
294
|
+
#### `toJSON(): ErrorXSerialized`
|
|
413
295
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
Error codes are automatically generated from the error name:
|
|
296
|
+
Serializes the error for network transfer:
|
|
417
297
|
|
|
418
298
|
```typescript
|
|
419
|
-
new ErrorX({ message: '
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
new ErrorX({ message: 'Failed', name: 'userAuthError' })
|
|
423
|
-
// code: 'USER_AUTH_ERROR'
|
|
424
|
-
|
|
425
|
-
new ErrorX({ message: 'Failed', name: 'API Timeout' })
|
|
426
|
-
// code: 'API_TIMEOUT'
|
|
299
|
+
const error = new ErrorX({ message: 'test', code: 'TEST' })
|
|
300
|
+
const json = error.toJSON()
|
|
301
|
+
// Returns plain object with all error properties
|
|
427
302
|
```
|
|
428
303
|
|
|
429
|
-
|
|
304
|
+
#### `fromJSON(serialized: ErrorXSerialized): ErrorX`
|
|
430
305
|
|
|
431
|
-
|
|
306
|
+
Deserializes a JSON object back to ErrorX:
|
|
432
307
|
|
|
433
308
|
```typescript
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
new ErrorX({ message: 'user not found. please check credentials' })
|
|
438
|
-
// message: 'User not found. Please check credentials.'
|
|
309
|
+
const json = { name: 'TestError', message: 'test', code: 'TEST', ... }
|
|
310
|
+
const error = ErrorX.fromJSON(json)
|
|
311
|
+
// Returns fully reconstructed ErrorX instance
|
|
439
312
|
```
|
|
440
313
|
|
|
441
314
|
## Usage Examples
|
|
@@ -443,11 +316,12 @@ new ErrorX({ message: 'user not found. please check credentials' })
|
|
|
443
316
|
### Basic Error Handling
|
|
444
317
|
|
|
445
318
|
```typescript
|
|
446
|
-
import { ErrorX } from 'error-x'
|
|
319
|
+
import { ErrorX } from '@bombillazo/error-x'
|
|
447
320
|
|
|
448
321
|
function validateUser(user: unknown) {
|
|
449
322
|
if (!user) {
|
|
450
|
-
throw new ErrorX(
|
|
323
|
+
throw new ErrorX({
|
|
324
|
+
message: 'User validation failed: user is required',
|
|
451
325
|
name: 'ValidationError',
|
|
452
326
|
code: 'USER_REQUIRED',
|
|
453
327
|
uiMessage: 'Please provide user information',
|
|
@@ -464,25 +338,20 @@ async function fetchUser(id: string) {
|
|
|
464
338
|
try {
|
|
465
339
|
const response = await fetch(`/api/users/${id}`)
|
|
466
340
|
if (!response.ok) {
|
|
467
|
-
throw new ErrorX(
|
|
468
|
-
|
|
469
|
-
uiMessage: 'Unable to load user data',
|
|
341
|
+
throw new ErrorX({
|
|
342
|
+
...http[response.status === 404 ? 'notFound' : 'internalServerError'],
|
|
470
343
|
metadata: { status: response.status, statusText: response.statusText }
|
|
471
344
|
})
|
|
472
345
|
}
|
|
473
346
|
return response.json()
|
|
474
347
|
} catch (error) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
throw errorX.withMetadata({
|
|
478
|
-
userId: id,
|
|
479
|
-
operation: 'fetchUser',
|
|
480
|
-
})
|
|
348
|
+
const errorX = ErrorX.from(error)
|
|
349
|
+
throw errorX.withMetadata({ userId: id, operation: 'fetchUser' })
|
|
481
350
|
}
|
|
482
351
|
}
|
|
483
352
|
```
|
|
484
353
|
|
|
485
|
-
### Error Chaining
|
|
354
|
+
### Error Chaining
|
|
486
355
|
|
|
487
356
|
```typescript
|
|
488
357
|
try {
|
|
@@ -490,62 +359,58 @@ try {
|
|
|
490
359
|
await tx.users.create(userData)
|
|
491
360
|
})
|
|
492
361
|
} catch (dbError) {
|
|
493
|
-
|
|
494
|
-
|
|
362
|
+
throw new ErrorX({
|
|
363
|
+
message: 'User creation failed',
|
|
495
364
|
name: 'UserCreationError',
|
|
496
365
|
code: 'USER_CREATE_FAILED',
|
|
497
366
|
uiMessage: 'Unable to create user account',
|
|
498
|
-
cause: dbError, // Preserves original stack trace
|
|
367
|
+
cause: dbError, // Preserves original stack trace
|
|
499
368
|
metadata: {
|
|
500
369
|
operation: 'userRegistration',
|
|
501
|
-
|
|
370
|
+
email: userData.email
|
|
502
371
|
}
|
|
503
372
|
})
|
|
504
|
-
|
|
505
|
-
// Add more context while preserving the error chain
|
|
506
|
-
throw error.withMetadata({
|
|
507
|
-
requestId: generateRequestId(),
|
|
508
|
-
userAgent: request.headers['user-agent']
|
|
509
|
-
})
|
|
510
373
|
}
|
|
511
374
|
```
|
|
512
375
|
|
|
513
|
-
##
|
|
376
|
+
## Message Formatting
|
|
514
377
|
|
|
515
|
-
|
|
378
|
+
**Important:** ErrorX does NOT auto-format messages. Messages are passed through as-is:
|
|
516
379
|
|
|
517
|
-
|
|
380
|
+
```typescript
|
|
381
|
+
new ErrorX({ message: 'test error' })
|
|
382
|
+
// message: 'test error' (exactly as provided)
|
|
383
|
+
|
|
384
|
+
new ErrorX({ message: 'Test error.' })
|
|
385
|
+
// message: 'Test error.' (exactly as provided)
|
|
386
|
+
```
|
|
518
387
|
|
|
519
|
-
|
|
388
|
+
Empty or whitespace-only messages default to `'An error occurred'`:
|
|
520
389
|
|
|
521
390
|
```typescript
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
actions: [
|
|
525
|
-
{ action: 'analytics', payload: { event: 'error' } }, // Loses type safety
|
|
526
|
-
{ action: 'notify', payload: { targets: ['toast'] } }, // Payload type becomes too permissive
|
|
527
|
-
{ action: 'redirect', payload: { redirectURL: '/home' } } // Required properties not enforced
|
|
528
|
-
]
|
|
529
|
-
})
|
|
391
|
+
new ErrorX({ message: '' })
|
|
392
|
+
// message: 'An error occurred'
|
|
530
393
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
actions: [
|
|
534
|
-
{ action: 'custom', payload: { type: 'analytics', event: 'error' } },
|
|
535
|
-
{ action: 'notify', payload: { targets: ['toast'] } }, // Properly typed with required 'targets'
|
|
536
|
-
{ action: 'redirect', payload: { redirectURL: '/home' } } // Properly typed with required 'redirectURL'
|
|
537
|
-
]
|
|
538
|
-
})
|
|
394
|
+
new ErrorX({ message: ' ' })
|
|
395
|
+
// message: 'An error occurred'
|
|
539
396
|
```
|
|
540
397
|
|
|
541
|
-
|
|
398
|
+
HTTP presets provide properly formatted messages with sentence casing and periods.
|
|
399
|
+
|
|
400
|
+
## Auto Code Generation
|
|
401
|
+
|
|
402
|
+
Error codes are automatically generated from names when not provided:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
new ErrorX({ message: 'Failed', name: 'DatabaseError' })
|
|
406
|
+
// code: 'DATABASE_ERROR'
|
|
542
407
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
- Allows your error handlers to properly switch on action types
|
|
546
|
-
- Enables you to create your own discriminated unions within custom payloads
|
|
408
|
+
new ErrorX({ message: 'Failed', name: 'userAuthError' })
|
|
409
|
+
// code: 'USER_AUTH_ERROR'
|
|
547
410
|
|
|
548
|
-
|
|
411
|
+
new ErrorX({ message: 'Failed', name: 'API Timeout' })
|
|
412
|
+
// code: 'API_TIMEOUT'
|
|
413
|
+
```
|
|
549
414
|
|
|
550
415
|
## License
|
|
551
416
|
|