@bombillazo/error-x 0.3.0 → 0.4.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 CHANGED
@@ -4,19 +4,19 @@
4
4
  [![npm](https://img.shields.io/npm/dt/@bombillazo/error-x.svg?style=for-the-badge)](https://www.npmjs.com/package/@bombillazo/error-x)
5
5
  [![npm](https://img.shields.io/npm/l/@bombillazo/error-x?style=for-the-badge)](https://github.com/bombillazo/error-x/blob/master/LICENSE)
6
6
 
7
- A smart, isomorphic, and opinionated error library for TypeScript applications. Provides type-safe error handling with great DX, solving common pain points like unknown error types, lost stack traces, async error handling, and error serialization.
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** for great DX
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
- - 🎛️ **Error handling options** for UI behavior and application actions
18
- - 📦 **Serialization/deserialization** support for network transfer and storage
19
- - 🎨 **Pre-configured error presets** for common error types
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, HandlingTargets, type HandlingTarget, type ErrorAction } from 'error-x'
49
+ import { ErrorX, http } from '@bombillazo/error-x'
41
50
 
42
- // Minimal usage (all parameters optional)
43
- const error = new ErrorX()
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
- // String message with additional options
49
- const error = new ErrorX('User authentication failed', {
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
- // Options object (backward compatible)
57
- const error = new ErrorX({
58
- message: 'User authentication failed',
59
- name: 'AuthError',
60
- code: 'AUTH_FAILED',
61
- uiMessage: 'Please check your credentials and try again',
62
- cause: originalError, // Chain errors while preserving stack traces
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
- const apiError = { message: 'User not found', code: 404, statusText: 'Not Found' }
76
- const error = new ErrorX(apiError)
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
- // String message signature
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(input?: string | ErrorXOptions)
115
91
  ```
116
92
 
117
- **All parameters are optional** - ErrorX uses sensible defaults and auto-generates missing values.
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` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
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
- The `actions` property allows errors to trigger application logic, passing along the necessary data. Your application error handler can route or execute these actions to achieve the desired behavior.
111
+ ### HTTP Error Presets
136
112
 
137
- `actions` accepts an array of `ErrorAction` objects. The library provides predefined action types with type-safe payloads, and a `CustomAction` type for application-specific actions.
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 { HandlingTargets, type ErrorAction, type CustomAction } from 'error-x'
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
- // Logout action
161
- const error2 = new ErrorX({
162
- message: 'Session expired',
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
- // Custom actions for application-specific logic
170
- const error3 = new ErrorX({
171
- message: 'API rate limit exceeded',
172
- actions: [
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
- ### Notify Targets
141
+ #### Available Presets
203
142
 
204
- For the `NotifyAction`, notify targets can be predefined enum values or custom strings for flexibility:
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
- #### Predefined Display Targets
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
- | Target | Enum Value | Description |
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
- ```typescript
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
- ## Error Presets
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
- ErrorX provides pre-configured error templates for common scenarios, making it easy to create consistent, well-structured errors without repetition. All HTTP presets (400-511) are included with proper status codes, messages, and user-friendly text.
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
- ### Features
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
- - **Pre-configured templates** with httpStatus, code, name, message, and uiMessage
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
- ### Usage Patterns
208
+ ### Static Methods
252
209
 
253
- #### 1. Direct Usage
210
+ #### `ErrorX.from(error: unknown): ErrorX`
254
211
 
255
- Use a preset as-is without modifications:
212
+ Converts any error type to ErrorX with intelligent property extraction:
256
213
 
257
214
  ```typescript
258
- import { ErrorX } from '@bombillazo/error-x'
259
-
260
- // Simple usage
261
- throw new ErrorX(ErrorX.HTTP.NOT_FOUND)
262
- // Result: 404 error with default message and UI message
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
- #### 2. Override Specific Fields
234
+ #### `ErrorX.isErrorX(value: unknown): value is ErrorX`
266
235
 
267
- Customize the error while keeping other preset values:
236
+ Type guard to check if a value is an ErrorX instance:
268
237
 
269
238
  ```typescript
270
- throw new ErrorX({
271
- ...ErrorX.HTTP.NOT_FOUND,
272
- message: 'User not found',
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
- #### 3. Add Metadata and Actions
244
+ #### `ErrorX.configure(config: ErrorXConfig): void`
279
245
 
280
- Enhance presets with additional context and behaviors:
246
+ Set global defaults for all ErrorX instances:
281
247
 
282
248
  ```typescript
283
- throw new ErrorX({
284
- ...ErrorX.HTTP.UNAUTHORIZED,
285
- metadata: { attemptedAction: 'viewProfile', userId: 456 },
286
- actions: [
287
- { action: 'logout', payload: { clearStorage: true } },
288
- { action: 'redirect', payload: { redirectURL: '/login' } }
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
- #### 4. Add Error Cause
263
+ #### `ErrorX.getConfig(): ErrorXConfig | null`
294
264
 
295
- Chain errors by adding a cause:
265
+ Get the current global configuration:
296
266
 
297
267
  ```typescript
298
- try {
299
- // some operation
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
- ### Available HTTP Presets
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
- res.json(user)
379
- })
380
- ```
274
+ #### `withMetadata(additionalMetadata: Record<string, unknown>): ErrorX`
381
275
 
382
- #### Authentication Middleware
276
+ Creates a new ErrorX instance with merged metadata:
383
277
 
384
278
  ```typescript
385
- const requireAuth = (req, res, next) => {
386
- if (!req.user) {
387
- throw new ErrorX({
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
- #### Rate Limiting
284
+ #### `cleanStackTrace(delimiter?: string): ErrorX`
285
+
286
+ Removes stack trace lines before a delimiter:
399
287
 
400
288
  ```typescript
401
- if (isRateLimited(req.ip)) {
402
- throw new ErrorX({
403
- ...ErrorX.HTTP.TOO_MANY_REQUESTS,
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
- ## Smart Features
294
+ #### `toJSON(): ErrorXSerialized`
413
295
 
414
- ### Auto Code Generation
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: 'Failed', name: 'DatabaseError' })
420
- // code: 'DATABASE_ERROR'
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
- ### Message Formatting
304
+ #### `fromJSON(serialized: ErrorXSerialized): ErrorX`
430
305
 
431
- Messages are automatically formatted with proper capitalization and punctuation:
306
+ Deserializes a JSON object back to ErrorX:
432
307
 
433
308
  ```typescript
434
- new ErrorX({ message: 'database connection failed' })
435
- // message: 'Database connection failed.'
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('User validation failed: user is required', {
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(`Failed to fetch user: ${response.statusText}`, {
468
- code: `HTTP_${response.status}`,
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
- // Convert any error to ErrorX and add context
476
- const errorX = new ErrorX(error)
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 and Stack Trace Preservation
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
- // Create new ErrorX while preserving the original error in the cause chain
494
- const error = new ErrorX('User creation failed', {
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 and error details
367
+ cause: dbError, // Preserves original stack trace
499
368
  metadata: {
500
369
  operation: 'userRegistration',
501
- userData: { email: userData.email } // Don't log sensitive data
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
- ## FAQ
376
+ ## Message Formatting
514
377
 
515
- ### Why use action type "custom" instead of an open string type for CustomAction?
378
+ **Important:** ErrorX does NOT auto-format messages. Messages are passed through as-is:
516
379
 
517
- The `ErrorAction` type uses a discriminated union based on the `action` property. When you use arbitrary values instead of the predefined action types (`'notify'`, `'logout'`, `'redirect'`, `'custom'`), it breaks TypeScript's ability to properly narrow the payload types.
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
- **The Problem:** If `ErrorAction` allowed any string as the action type, TypeScript would default to the most permissive payload type (`{ ...any }`) for all actions, causing type definition to leak between different action types.
388
+ Empty or whitespace-only messages default to `'An error occurred'`:
520
389
 
521
390
  ```typescript
522
- // Cannot be done - breaks discriminated union
523
- const error = new ErrorX({
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
- // Do this - maintains proper type discrimination
532
- const error = new ErrorX({
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
- **The Solution:** Using `action: 'custom'` with a discriminating `type` property in the payload preserves the discriminated union while allowing unlimited flexibility for custom actions. This approach:
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
- - Maintains type safety for predefined actions (`notify`, `logout`, `redirect`)
544
- - Provides a structured way to handle custom application logic
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
- Ideally, we would support custom action types directly in the action. If there is a solution to this problem, we are more than happy to review it. Please open an issue or PR!.
411
+ new ErrorX({ message: 'Failed', name: 'API Timeout' })
412
+ // code: 'API_TIMEOUT'
413
+ ```
549
414
 
550
415
  ## License
551
416