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