@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 CHANGED
@@ -4,20 +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 satisfying 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
- - 🕒 **Automatic timestamps** for error tracking
16
14
  - 🔗 **Error chaining** with cause preservation and stack trace preservation
17
15
  - 📊 **Flexible metadata** for additional context
18
- - 🎛️ **Error handling options** for UI behavior and application actions
19
- - 📦 **Serialization/deserialization** support for network transfer and storage
20
- - 🎨 **Pre-configured error presets** for common HTTP status codes (400-511)
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, HandlingTargets, type HandlingTarget, type ErrorAction } from 'error-x'
49
+ import { ErrorX, http } from '@bombillazo/error-x'
42
50
 
43
- // Minimal usage (all parameters optional)
44
- const error = new ErrorX()
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
- // String message with additional options
50
- const error = new ErrorX('User authentication failed', {
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
- // Options object (backward compatible)
58
- const error = new ErrorX({
59
- message: 'User authentication failed',
60
- name: 'AuthError',
61
- code: 'AUTH_FAILED',
62
- uiMessage: 'Please check your credentials and try again',
63
- cause: originalError, // Chain errors while preserving stack traces
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
- const apiError = { message: 'User not found', code: 404, statusText: 'Not Found' }
77
- 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
+ }
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
- // String message signature
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
- **All parameters are optional** - ErrorX uses sensible defaults and auto-generates missing values.
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` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
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
- 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
137
112
 
138
- `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.
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 { HandlingTargets, type ErrorAction, type CustomAction } from 'error-x'
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
- // Logout action
162
- const error2 = new ErrorX({
163
- message: 'Session expired',
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
- // Custom actions for application-specific logic
171
- const error3 = new ErrorX({
172
- message: 'API rate limit exceeded',
173
- actions: [
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
- ### Notify Targets
141
+ #### Available Presets
204
142
 
205
- 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
206
150
 
207
- #### 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`
208
153
 
209
- | Target | Enum Value | Description |
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
- ```typescript
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
- ## 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:
241
160
 
242
- 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>;
243
200
 
244
- ### Features
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
- - **Pre-configured templates** with httpStatus, code, name, message, and uiMessage
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
- ### Usage Patterns
208
+ ### Static Methods
253
209
 
254
- #### 1. Direct Usage
210
+ #### `ErrorX.from(error: unknown): ErrorX`
255
211
 
256
- Use a preset as-is without modifications:
212
+ Converts any error type to ErrorX with intelligent property extraction:
257
213
 
258
214
  ```typescript
259
- import { ErrorX, PRESETS } from '@bombillazo/error-x'
260
-
261
- // Simple usage
262
- throw new ErrorX(PRESETS.HTTP.NOT_FOUND)
263
- // 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
264
232
  ```
265
233
 
266
- #### 2. Override Specific Fields
234
+ #### `ErrorX.isErrorX(value: unknown): value is ErrorX`
267
235
 
268
- Customize the error while keeping other preset values:
236
+ Type guard to check if a value is an ErrorX instance:
269
237
 
270
238
  ```typescript
271
- throw new ErrorX({
272
- ...PRESETS.HTTP.NOT_FOUND,
273
- message: 'User not found',
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
- #### 3. Add Metadata and Actions
244
+ #### `ErrorX.configure(config: ErrorXConfig): void`
280
245
 
281
- Enhance presets with additional context and behaviors:
246
+ Set global defaults for all ErrorX instances:
282
247
 
283
248
  ```typescript
284
- throw new ErrorX({
285
- ...PRESETS.HTTP.UNAUTHORIZED,
286
- metadata: { attemptedAction: 'viewProfile', userId: 456 },
287
- actions: [
288
- { action: 'logout', payload: { clearStorage: true } },
289
- { action: 'redirect', payload: { redirectURL: '/login' } }
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
- #### 4. Add Error Cause
263
+ #### `ErrorX.getConfig(): ErrorXConfig | null`
295
264
 
296
- Chain errors by adding a cause:
265
+ Get the current global configuration:
297
266
 
298
267
  ```typescript
299
- try {
300
- // some operation
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
- ### Available HTTP Presets
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
- ```typescript
366
- import { ErrorX, PRESETS } from '@bombillazo/error-x'
274
+ #### `withMetadata(additionalMetadata: Record<string, unknown>): ErrorX`
367
275
 
368
- app.get('/users/:id', async (req, res) => {
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 requireAuth = (req, res, next) => {
387
- if (!req.user) {
388
- throw new ErrorX({
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
- #### Rate Limiting
284
+ #### `cleanStackTrace(delimiter?: string): ErrorX`
285
+
286
+ Removes stack trace lines before a delimiter:
400
287
 
401
288
  ```typescript
402
- if (isRateLimited(req.ip)) {
403
- throw new ErrorX({
404
- ...PRESETS.HTTP.TOO_MANY_REQUESTS,
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
- ## Smart Features
294
+ #### `toJSON(): ErrorXSerialized`
414
295
 
415
- ### Auto Code Generation
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: 'Failed', name: 'DatabaseError' })
421
- // code: 'DATABASE_ERROR'
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
- ### Message Formatting
304
+ #### `fromJSON(serialized: ErrorXSerialized): ErrorX`
431
305
 
432
- Messages are automatically formatted with proper capitalization and punctuation:
306
+ Deserializes a JSON object back to ErrorX:
433
307
 
434
308
  ```typescript
435
- new ErrorX({ message: 'database connection failed' })
436
- // message: 'Database connection failed.'
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('User validation failed: user is required', {
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(`Failed to fetch user: ${response.statusText}`, {
469
- code: `HTTP_${response.status}`,
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
- // Convert any error to ErrorX and add context
477
- const errorX = new ErrorX(error)
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 and Stack Trace Preservation
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
- // Create new ErrorX while preserving the original error in the cause chain
495
- const error = new ErrorX('User creation failed', {
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 and error details
367
+ cause: dbError, // Preserves original stack trace
500
368
  metadata: {
501
369
  operation: 'userRegistration',
502
- userData: { email: userData.email } // Don't log sensitive data
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
- ## FAQ
376
+ ## Message Formatting
515
377
 
516
- ### 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:
517
379
 
518
- 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)
519
383
 
520
- **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.
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
- // Cannot be done - breaks discriminated union
524
- const error = new ErrorX({
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
- // Do this - maintains proper type discrimination
533
- const error = new ErrorX({
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
- **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.
543
399
 
544
- - Maintains type safety for predefined actions (`notify`, `logout`, `redirect`)
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
- 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!.
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