@bombillazo/error-x 0.4.6 → 0.5.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,19 +4,24 @@
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 type-safe error library for TypeScript applications. Provides excellent DX with intelligent error conversion, stack trace preservation, serialization support, and HTTP error presets.
7
+ 🚨❌
8
+
9
+ A smart, isomorphic, and type-safe error library for TypeScript applications. Provides excellent DX with intelligent error conversion, stack trace preservation, serialization support, error chaining, flexible custom error classes, and more.
8
10
 
9
11
  ## Features
10
12
 
11
- - 🎯 **Type-safe error handling** with full TypeScript support
12
- - 🔄 **Smart error conversion** from various formats (API responses, strings, Error objects)
13
- - 👤 **User-friendly messages** separate from technical messages
14
- - 🔗 **Error chaining** with cause preservation and stack trace preservation
15
- - 📊 **Flexible metadata** for additional context
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
13
+ - **Built in TypeScript** for full Type-safe error handling support and generics
14
+ - **Isomorphic** - works in Node.js and browsers
15
+ - **Smart error conversion** from different sources (API responses, strings, Error objects)
16
+ - **Error chaining** for full error sequence
17
+ - **Factory method** `.create()` for preset-based error creation
18
+ - **Custom metadata** with type-safe generics for additional context
19
+ - **Global configuration** for stack cleaning and defaults
20
+ - **Serialization/deserialization** for network transfer and storage
21
+ - **Custom ErrorX class** examples:
22
+ - `HTTPErrorX` - HTTP status code presets (400-511)
23
+ - `DBErrorX` - Database error presets (connection, query, constraints)
24
+ - `ValidationErrorX` - Validation errors with Zod integration
20
25
 
21
26
  ## Installation
22
27
 
@@ -31,275 +36,554 @@ yarn add @bombillazo/error-x
31
36
  ### Requirements
32
37
 
33
38
  - **Node.js**: 18 or higher
34
- - **TypeScript**: 5.0 or higher (optional, but recommended)
39
+ - **TypeScript**: 5.0 or higher
35
40
  - **Target Environment**: ES2022+
36
41
 
37
42
  This library uses modern JavaScript features and ES2022 APIs. For browser compatibility, ensure your build tool (e.g., Vite, webpack, esbuild) is configured to target ES2022 or transpile accordingly.
38
43
 
39
44
  > [!WARNING]
40
45
  >
41
- > This library is currently in pre-v1.0 development. 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.
46
+ > This library is currently in pre-v1.0 development. Breaking changes may occur. We recommend pinning to specific versions and reviewing release notes when updating.
42
47
  >
43
48
  > Once we reach version 1.0, we plan to minimize API changes and follow semantic versioning.
44
49
 
45
- ## Documentation
46
-
47
- [Documentation](docs/index.md)
48
-
49
50
  ## Quick Start
50
51
 
51
52
  ```typescript
52
- import { ErrorX, http } from '@bombillazo/error-x'
53
+ import { ErrorX, HTTPErrorX, DBErrorX } from "@bombillazo/error-x";
53
54
 
54
55
  // Simple usage
55
- throw new ErrorX('Database connection failed')
56
+ throw new ErrorX("Something went wrong");
56
57
 
57
- // With options
58
+ // A fully defined error
58
59
  throw new ErrorX({
59
- message: 'User authentication failed',
60
- name: 'AuthError',
61
- code: 'AUTH_FAILED',
62
- uiMessage: 'Please check your credentials and try again',
63
- metadata: { userId: 123, loginAttempt: 3 },
64
- source: 'auth-service'
65
- })
66
-
67
- // Using HTTP presets
68
- throw new ErrorX(http[404])
69
-
70
- // Customizing presets
71
- throw new ErrorX({
72
- ...http[401],
73
- message: 'Session expired',
74
- metadata: { userId: 123 }
75
- })
76
-
77
- // Smart conversion from unknown errors
78
- try {
79
- await someOperation()
80
- } catch (error) {
81
- const errorX = ErrorX.from(error)
82
- throw errorX.withMetadata({ context: 'additional info' })
83
- }
60
+ message: "User authentication failed",
61
+ name: "AuthError",
62
+ code: "AUTH_FAILED",
63
+ uiMessage: "Please check your credentials",
64
+ httpStatus: 401,
65
+ metadata: { userId: 123 },
66
+ });
67
+
68
+ // Using specialized error classes
69
+ throw HTTPErrorX.create(404);
70
+ throw DBErrorX.create("CONNECTION_FAILED");
71
+
72
+ // Convert unknown errors
73
+ const errorX = ErrorX.from(unknownError);
84
74
  ```
85
75
 
86
76
  ## Documentation
87
77
 
88
- ### Constructor
78
+ [Full Documentation](docs/index.md)
79
+
80
+ ---
81
+
82
+ ## API Reference
83
+
84
+ ### ErrorX Class
85
+
86
+ The base error class that extends the native `Error` with enhanced capabilities.
87
+
88
+ #### Properties
89
+
90
+ | Property | Type | Description |
91
+ | ------------ | -------------------------- | -------------------------------------------------------------- |
92
+ | `message` | `string` | Technical error message |
93
+ | `name` | `string` | Error type/name |
94
+ | `code` | `string` | Error identifier code (auto-generated from name if not set) |
95
+ | `uiMessage` | `string \| undefined` | User-friendly message for UI display |
96
+ | `httpStatus` | `number \| undefined` | HTTP status code associated with this error |
97
+ | `metadata` | `TMetadata \| undefined` | Additional context (type-safe with generics) |
98
+ | `timestamp` | `number` | Unix epoch timestamp (ms) when error was created |
99
+ | `stack` | `string \| undefined` | Stack trace (inherited from Error) |
100
+ | `chain` | `readonly ErrorX[]` | Full error sequence: `[this, parent, grandparent, ..., root]` |
101
+ | `root` | `ErrorX \| undefined` | Error that started the whole error chain |
102
+ | `parent` | `ErrorX \| undefined` | Error that immediately precedes this error in the chain |
103
+ | `original` | `ErrorXCause \| undefined` | Stores the original non-ErrorX error used to create this error |
104
+
105
+ #### Static Methods
106
+
107
+ | Method | Description |
108
+ | --------------------- | ------------------------------------------------------------------- |
109
+ | `from(value, opts?)` | Convert any value to ErrorX with intelligent property extraction |
110
+ | `fromJSON(json)` | Deserialize JSON back to ErrorX instance |
111
+ | `create(key?, opts?)` | Factory method for preset-based error creation (used by subclasses) |
112
+ | `isErrorX(value)` | Type guard to check if value is an ErrorX instance |
113
+ | `isErrorXOptions(v)` | Check if value is a valid ErrorXOptions object |
114
+ | `configure(config)` | Set global configuration (stack cleaning, defaults) |
115
+ | `getConfig()` | Get current global configuration |
116
+ | `resetConfig()` | Reset global configuration to defaults |
117
+ | `cleanStack(stack)` | Clean internal frames from stack trace |
118
+
119
+ #### Instance Methods
120
+
121
+ | Method | Description |
122
+ | -------------------- | ------------------------------------------------- |
123
+ | `withMetadata(meta)` | Create new ErrorX with additional metadata merged |
124
+ | `toJSON()` | Serialize to JSON-compatible object |
125
+ | `toString()` | Detailed string representation with metadata |
126
+
127
+ ---
128
+
129
+ ## Constructor
89
130
 
90
131
  ```typescript
91
132
  new ErrorX(input?: string | ErrorXOptions)
92
133
  ```
93
134
 
94
- All parameters are optional. ErrorX uses sensible defaults:
135
+ Create a new ErrorX instance. All parameters are optional with sensible defaults.
136
+
137
+ ```typescript
138
+ // String message
139
+ new ErrorX("Database connection failed");
140
+
141
+ // Options object
142
+ new ErrorX({
143
+ message: "User not found",
144
+ name: "NotFoundError",
145
+ code: "USER_NOT_FOUND",
146
+ uiMessage: "The requested user does not exist",
147
+ httpStatus: 404,
148
+ metadata: { userId: 123 },
149
+ });
150
+
151
+ // With type-safe metadata
152
+ type UserMeta = { userId: number; action: string };
153
+ new ErrorX<UserMeta>({
154
+ message: "Action failed",
155
+ metadata: { userId: 123, action: "delete" },
156
+ });
157
+ ```
158
+
159
+ ### ErrorXOptions
160
+
161
+ | Property | Type | Default | Description |
162
+ | ---------- | --------------------------------- | --------------------- | ----------------------------------------- |
163
+ | message | `string` | `'An error occurred'` | Technical error message |
164
+ | name | `string` | `'Error'` | Error type/name |
165
+ | code | `string \| number` | Auto-generated | Error identifier (UPPER_SNAKE_CASE) |
166
+ | uiMessage | `string` | `undefined` | User-friendly message |
167
+ | httpStatus | `number` | `undefined` | HTTP status code |
168
+ | metadata | `TMetadata` | `undefined` | Additional context |
169
+ | cause | `ErrorXCause \| Error \| unknown` | `undefined` | Error that caused this (builds the chain) |
170
+
171
+ ---
172
+
173
+ ## Common Methods
95
174
 
96
- | Property | Type | Default Value | Description |
97
- | --------- | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
98
- | message | `string` | `'An error occurred'` | Technical error message (pass-through, no auto-formatting) |
99
- | name | `string` | `'Error'` | Error type/title |
100
- | code | `string \| number` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
101
- | uiMessage | `string \| undefined` | `undefined` | User-friendly message for display |
102
- | cause | `ErrorXCause \| Error \| unknown` | `undefined` | Original error that caused this (preserves full error chain) |
103
- | metadata | `Record<string, unknown> \| undefined` | `undefined` | Additional context and data (flexible storage for any extra info) |
104
- | type | `string \| undefined` | `undefined` | Error type for categorization (e.g., 'http', 'validation') |
105
- | docsUrl | `string \| undefined` | `undefined` or auto-generated | Documentation URL for this specific error |
106
- | source | `string \| undefined` | `undefined` or from config | Where the error originated (service name, module, component) |
107
- | timestamp | `number` | `Date.now()` | Unix epoch timestamp in milliseconds when error was created |
108
- | stack | `string` | Auto-generated | Stack trace with preservation and cleaning (inherited from Error) |
175
+ ### ErrorX.from()
109
176
 
110
- ### HTTP Error Presets
177
+ Convert any value into an ErrorX instance with intelligent property extraction.
111
178
 
112
- ErrorX provides pre-configured error templates via the `http` export:
179
+ ```typescript
180
+ static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>
181
+ ```
182
+
183
+ Handles strings, Error objects, API responses, and unknown values. Extracts common properties like `message`, `code`, `status`, `statusCode`, and `metadata`.
113
184
 
114
185
  ```typescript
115
- import { ErrorX, http } from '@bombillazo/error-x'
186
+ // Convert string
187
+ ErrorX.from("Something went wrong");
188
+
189
+ // Convert Error
190
+ ErrorX.from(new Error("Connection failed"));
191
+
192
+ // Convert API response
193
+ ErrorX.from({
194
+ message: "User not found",
195
+ code: "USER_404",
196
+ status: 404,
197
+ metadata: { userId: 123 },
198
+ });
199
+
200
+ // With overrides (deep merged)
201
+ ErrorX.from(error, {
202
+ httpStatus: 500,
203
+ metadata: { context: "db-layer" },
204
+ });
205
+ ```
116
206
 
117
- // Use preset directly
118
- throw new ErrorX(http[404])
119
- // Result: 404 error with message "Not found.", code "NOT_FOUND", etc.
207
+ ### ErrorX.isErrorX()
120
208
 
121
- // Override specific fields
122
- throw new ErrorX({
123
- ...http[404],
124
- message: 'User not found',
125
- metadata: { userId: 123 }
126
- })
209
+ Type guard to check if a value is an ErrorX instance.
210
+
211
+ ```typescript
212
+ static isErrorX<T>(value: unknown): value is ErrorX<T>
213
+ ```
127
214
 
128
- // Add error cause
215
+ ```typescript
129
216
  try {
130
- // some operation
131
- } catch (originalError) {
132
- throw new ErrorX({
133
- ...http[500],
134
- cause: originalError,
135
- metadata: { operation: 'database-query' }
136
- })
217
+ await riskyOperation();
218
+ } catch (error) {
219
+ if (ErrorX.isErrorX(error)) {
220
+ console.log(error.code, error.metadata);
221
+ }
137
222
  }
138
223
  ```
139
224
 
140
- #### Available Presets
225
+ ### withMetadata()
226
+
227
+ Create a new ErrorX with additional metadata merged with existing metadata.
228
+
229
+ ```typescript
230
+ withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>
231
+ ```
141
232
 
142
- All presets are indexed by **HTTP status code** (numeric keys) and include:
233
+ ```typescript
234
+ const error = new ErrorX({
235
+ message: "Request failed",
236
+ metadata: { endpoint: "/api/users" },
237
+ });
143
238
 
144
- - `code`: Error code in UPPER_SNAKE_CASE
145
- - `name`: Descriptive error name
146
- - `message`: Technical message with proper sentence casing and period
147
- - `uiMessage`: User-friendly message
148
- - `metadata`: Contains `{ status: <number> }` with the HTTP status code
239
+ const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
240
+ // metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }
241
+ ```
149
242
 
150
- #### Creating Your Own Presets
243
+ ### Serialization
151
244
 
152
- HTTP presets work well because HTTP status codes are universally standardized. For domain-specific errors (database, validation, authentication, business logic), create your own presets:
245
+ Serialize ErrorX instances for network transfer or storage.
153
246
 
154
247
  ```typescript
155
- import { type ErrorXOptions } from '@bombillazo/error-x'
156
-
157
- // Define your application-specific presets
158
- export const dbErrors = {
159
- connectionFailed: {
160
- name: 'DatabaseError',
161
- code: 'DB_CONNECTION_FAILED',
162
- message: 'Database connection failed.',
163
- uiMessage: 'Unable to connect to database. Please try again later.',
164
- type: 'database',
165
- },
166
- queryTimeout: {
167
- name: 'DatabaseError',
168
- code: 'DB_QUERY_TIMEOUT',
169
- message: 'Database query timeout.',
170
- uiMessage: 'The operation took too long. Please try again.',
171
- type: 'database',
172
- },
173
- } satisfies Record<string, ErrorXOptions>;
174
-
175
- export const authErrors = {
176
- invalidToken: {
177
- name: 'AuthenticationError',
178
- code: 'AUTH_INVALID_TOKEN',
179
- message: 'Invalid authentication token.',
180
- uiMessage: 'Your session has expired. Please log in again.',
181
- metadata: { status: 401 },
182
- type: 'authentication',
183
- },
184
- insufficientPermissions: {
185
- name: 'AuthorizationError',
186
- code: 'AUTH_INSUFFICIENT_PERMISSIONS',
187
- message: 'Insufficient permissions.',
188
- uiMessage: 'You do not have permission to perform this action.',
189
- metadata: { status: 403 },
190
- type: 'authorization',
191
- },
192
- } satisfies Record<string, ErrorXOptions>;
248
+ // Serialize
249
+ const json = error.toJSON();
250
+ // { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
193
251
 
194
- // Use them just like http presets
195
- throw new ErrorX(dbErrors.connectionFailed);
196
- throw new ErrorX({ ...authErrors.invalidToken, metadata: { userId: 123 } });
252
+ // Deserialize
253
+ const restored = ErrorX.fromJSON(json);
197
254
  ```
198
255
 
199
- This approach keeps your error handling consistent while remaining flexible for your specific domain.
256
+ ### Error Chaining
200
257
 
258
+ Build error timelines by passing `cause` to preserve the full error history.
201
259
 
202
- ## Usage Examples
260
+ ```typescript
261
+ // Build an error chain
262
+ const dbError = ErrorX.from(new Error("ECONNREFUSED"));
263
+ const repoError = new ErrorX({ message: "Query failed", cause: dbError });
264
+ const serviceError = new ErrorX({
265
+ message: "User fetch failed",
266
+ cause: repoError,
267
+ });
268
+
269
+ // Access chain information
270
+ serviceError.chain.length; // 3: [serviceError, repoError, dbError]
271
+ serviceError.parent; // repoError
272
+ serviceError.root; // dbError
273
+ dbError.original; // { message: 'ECONNREFUSED', name: 'Error', stack: '...' }
274
+ ```
203
275
 
204
- ### Basic Error Handling
276
+ ```typescript
277
+ // Practical example
278
+ try {
279
+ await database.query(sql);
280
+ } catch (dbError) {
281
+ throw DBErrorX.create("QUERY_FAILED", {
282
+ cause: dbError,
283
+ metadata: { query: sql, table: "users" },
284
+ });
285
+ }
286
+
287
+ // Later, inspect the chain
288
+ if (ErrorX.isErrorX(error)) {
289
+ console.log(
290
+ "Error chain:",
291
+ error.chain.map((e) => e.name)
292
+ );
293
+ console.log("Root cause:", error.root?.original);
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Custom Error Classes
300
+
301
+ error-x includes several custom error classes out of the box:
302
+
303
+ - **Ready-to-use** - Practical error classes for common scenarios (HTTP, database, validation)
304
+ - **Educational** - Demonstrate how to use presets, defaults, and transforms
305
+ - **Extensible** - Serve as templates and inspiration for your own domain-specific error classes
306
+
307
+ ### HTTPErrorX
308
+
309
+ HTTP errors with presets for all standard status codes (400-511).
205
310
 
206
311
  ```typescript
207
- import { ErrorX } from '@bombillazo/error-x'
208
-
209
- function validateUser(user: unknown) {
210
- if (!user) {
211
- throw new ErrorX({
212
- message: 'User validation failed: user is required',
213
- name: 'ValidationError',
214
- code: 'USER_REQUIRED',
215
- uiMessage: 'Please provide user information',
216
- metadata: { field: 'user', received: user }
217
- })
218
- }
312
+ import { HTTPErrorX } from "@bombillazo/error-x";
313
+
314
+ // Create by status code
315
+ HTTPErrorX.create(404);
316
+ // code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
317
+
318
+ HTTPErrorX.create(401);
319
+ // → code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
320
+
321
+ // With overrides
322
+ HTTPErrorX.create(404, {
323
+ message: "User not found",
324
+ metadata: { userId: 123 },
325
+ });
326
+
327
+ // With error chaining
328
+ HTTPErrorX.create(500, { cause: originalError });
329
+
330
+ // instanceof checks
331
+ if (error instanceof HTTPErrorX) {
332
+ console.log(error.httpStatus);
219
333
  }
220
334
  ```
221
335
 
222
- ### API Error Handling
336
+ **Available Presets:** All 4xx client errors (400-451) and 5xx server errors (500-511).
337
+
338
+ ### DBErrorX
339
+
340
+ Database errors with presets for common database scenarios. All codes are automatically prefixed with `DB_`.
223
341
 
224
342
  ```typescript
225
- async function fetchUser(id: string) {
226
- try {
227
- const response = await fetch(`/api/users/${id}`)
228
- if (!response.ok) {
229
- throw new ErrorX({
230
- ...http[response.status === 404 ? 404 : 500],
231
- metadata: { status: response.status, statusText: response.statusText }
232
- })
233
- }
234
- return response.json()
235
- } catch (error) {
236
- const errorX = ErrorX.from(error)
237
- throw errorX.withMetadata({ userId: id, operation: 'fetchUser' })
238
- }
343
+ import { DBErrorX } from "@bombillazo/error-x";
344
+
345
+ // Connection errors
346
+ DBErrorX.create("CONNECTION_FAILED"); // → code: 'DB_CONNECTION_FAILED'
347
+ DBErrorX.create("CONNECTION_TIMEOUT");
348
+ DBErrorX.create("CONNECTION_REFUSED");
349
+ DBErrorX.create("CONNECTION_LOST");
350
+
351
+ // Query errors
352
+ DBErrorX.create("QUERY_FAILED");
353
+ DBErrorX.create("QUERY_TIMEOUT");
354
+ DBErrorX.create("SYNTAX_ERROR");
355
+
356
+ // Constraint errors (with appropriate httpStatus)
357
+ DBErrorX.create("UNIQUE_VIOLATION"); // httpStatus: 409
358
+ DBErrorX.create("FOREIGN_KEY_VIOLATION"); // httpStatus: 400
359
+ DBErrorX.create("NOT_NULL_VIOLATION"); // httpStatus: 400
360
+ DBErrorX.create("CHECK_VIOLATION"); // httpStatus: 400
361
+
362
+ // Transaction errors
363
+ DBErrorX.create("TRANSACTION_FAILED");
364
+ DBErrorX.create("DEADLOCK"); // httpStatus: 409
365
+
366
+ // Record errors
367
+ DBErrorX.create("NOT_FOUND"); // httpStatus: 404
368
+
369
+ // With metadata
370
+ DBErrorX.create("QUERY_FAILED", {
371
+ message: "Failed to fetch user",
372
+ metadata: {
373
+ query: "SELECT * FROM users WHERE id = ?",
374
+ table: "users",
375
+ operation: "SELECT",
376
+ },
377
+ });
378
+
379
+ // instanceof checks
380
+ if (error instanceof DBErrorX) {
381
+ console.log(error.metadata?.table);
239
382
  }
240
383
  ```
241
384
 
242
- ### Error Chaining
385
+ ### ValidationErrorX
386
+
387
+ Validation errors with Zod integration. All codes are prefixed with `VALIDATION_`.
243
388
 
244
389
  ```typescript
390
+ import { z } from "zod";
391
+ import { ValidationErrorX } from "@bombillazo/error-x";
392
+
393
+ // From Zod errors
394
+ const schema = z.object({
395
+ email: z.string().email(),
396
+ age: z.number().min(18),
397
+ });
398
+
245
399
  try {
246
- await database.transaction(async (tx) => {
247
- await tx.users.create(userData)
248
- })
249
- } catch (dbError) {
250
- throw new ErrorX({
251
- message: 'User creation failed',
252
- name: 'UserCreationError',
253
- code: 'USER_CREATE_FAILED',
254
- uiMessage: 'Unable to create user account',
255
- cause: dbError, // Preserves original stack trace
256
- metadata: {
257
- operation: 'userRegistration',
258
- email: userData.email
259
- }
260
- })
400
+ schema.parse({ email: "invalid", age: 15 });
401
+ } catch (err) {
402
+ if (err instanceof z.ZodError) {
403
+ throw ValidationErrorX.fromZodError(err);
404
+ // code: 'VALIDATION_INVALID_STRING'
405
+ // metadata.field: 'email'
406
+ // → metadata.zodCode: 'invalid_string'
407
+ // → httpStatus: 400
408
+ }
409
+ }
410
+
411
+ // With overrides
412
+ ValidationErrorX.fromZodError(zodError, {
413
+ uiMessage: "Please check your input",
414
+ httpStatus: 422,
415
+ });
416
+
417
+ // Field-specific errors (without Zod)
418
+ ValidationErrorX.forField("email", "Invalid email format");
419
+ ValidationErrorX.forField("age", "Must be 18 or older", { code: "TOO_YOUNG" });
420
+
421
+ // Direct creation
422
+ ValidationErrorX.create({
423
+ message: "Invalid input",
424
+ code: "INVALID_INPUT",
425
+ metadata: { field: "email" },
426
+ });
427
+
428
+ // instanceof checks
429
+ if (error instanceof ValidationErrorX) {
430
+ console.log(error.metadata?.field);
261
431
  }
262
432
  ```
263
433
 
264
- ## Message Formatting
434
+ ### Creating Custom Error Classes
265
435
 
266
- **Important:** ErrorX does NOT auto-format messages. Messages are passed through as-is:
436
+ Extend `ErrorX` to create domain-specific error classes with presets, defaults, and transforms.
267
437
 
268
438
  ```typescript
269
- new ErrorX({ message: 'test error' })
270
- // message: 'test error' (exactly as provided)
439
+ import {
440
+ ErrorX,
441
+ type ErrorXOptions,
442
+ type ErrorXTransform,
443
+ } from "@bombillazo/error-x";
444
+
445
+ // 1. Define your metadata type
446
+ type PaymentMetadata = {
447
+ transactionId?: string;
448
+ amount?: number;
449
+ currency?: string;
450
+ };
451
+
452
+ // 2. Define presets outside the class for type inference
453
+ const paymentPresets = {
454
+ INSUFFICIENT_FUNDS: {
455
+ code: "INSUFFICIENT_FUNDS",
456
+ name: "PaymentError",
457
+ message: "Insufficient funds.",
458
+ uiMessage: "Your payment method has insufficient funds.",
459
+ httpStatus: 402,
460
+ },
461
+ CARD_DECLINED: {
462
+ code: "CARD_DECLINED",
463
+ name: "PaymentError",
464
+ message: "Card declined.",
465
+ uiMessage: "Your card was declined. Please try another payment method.",
466
+ httpStatus: 402,
467
+ },
468
+ EXPIRED_CARD: {
469
+ code: "EXPIRED_CARD",
470
+ name: "PaymentError",
471
+ message: "Card expired.",
472
+ uiMessage: "Your card has expired. Please update your payment method.",
473
+ httpStatus: 402,
474
+ },
475
+ } as const satisfies Record<string, ErrorXOptions>;
476
+
477
+ // 3. Derive preset key type
478
+ type PaymentPresetKey = keyof typeof paymentPresets | (string & {});
479
+
480
+ // 4. Create the class
481
+ export class PaymentErrorX extends ErrorX<PaymentMetadata> {
482
+ static presets = paymentPresets;
483
+ static defaultPreset = "CARD_DECLINED";
484
+ static defaults = { httpStatus: 402 };
485
+
486
+ // Optional: transform to prefix codes
487
+ static transform: ErrorXTransform<PaymentMetadata> = (opts) => ({
488
+ ...opts,
489
+ code: `PAYMENT_${opts.code}`,
490
+ });
491
+
492
+ // Override create for proper typing
493
+ static override create(
494
+ presetKey?: PaymentPresetKey,
495
+ overrides?: Partial<ErrorXOptions<PaymentMetadata>>
496
+ ): PaymentErrorX {
497
+ return ErrorX.create.call(
498
+ PaymentErrorX,
499
+ presetKey,
500
+ overrides
501
+ ) as PaymentErrorX;
502
+ }
503
+ }
504
+
505
+ // Usage
506
+ throw PaymentErrorX.create("INSUFFICIENT_FUNDS");
507
+ throw PaymentErrorX.create("CARD_DECLINED", {
508
+ metadata: { transactionId: "tx_123", amount: 99.99, currency: "USD" },
509
+ });
271
510
 
272
- new ErrorX({ message: 'Test error.' })
273
- // message: 'Test error.' (exactly as provided)
511
+ // instanceof works
512
+ if (error instanceof PaymentErrorX) {
513
+ console.log(error.metadata?.transactionId);
514
+ }
274
515
  ```
275
516
 
276
- Empty or whitespace-only messages default to `'An error occurred'`:
517
+ ---
518
+
519
+ ## Other Usage
520
+
521
+ ### Global Configuration
522
+
523
+ Configure stack trace cleaning and other global settings.
277
524
 
278
525
  ```typescript
279
- new ErrorX({ message: '' })
280
- // message: 'An error occurred'
526
+ import { ErrorX } from "@bombillazo/error-x";
527
+
528
+ // Enable stack cleaning with custom delimiter
529
+ ErrorX.configure({
530
+ cleanStack: true,
531
+ cleanStackDelimiter: "my-app-entry",
532
+ });
533
+
534
+ // Custom patterns to remove from stack traces
535
+ ErrorX.configure({
536
+ cleanStack: ["node_modules", "internal/"],
537
+ });
538
+
539
+ // Disable stack cleaning
540
+ ErrorX.configure({ cleanStack: false });
281
541
 
282
- new ErrorX({ message: ' ' })
283
- // message: 'An error occurred'
542
+ // Get current config
543
+ const config = ErrorX.getConfig();
544
+
545
+ // Reset to defaults
546
+ ErrorX.resetConfig();
284
547
  ```
285
548
 
286
- HTTP presets provide properly formatted messages with sentence casing and periods.
549
+ ### Auto Code Generation
550
+
551
+ Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
552
+
553
+ ```typescript
554
+ new ErrorX({ name: "DatabaseError" });
555
+ // → code: 'DATABASE_ERROR'
556
+
557
+ new ErrorX({ name: "userAuthError" });
558
+ // → code: 'USER_AUTH_ERROR'
559
+
560
+ new ErrorX({ name: "API Timeout" });
561
+ // → code: 'API_TIMEOUT'
562
+ ```
287
563
 
288
- ## Auto Code Generation
564
+ ### Message Formatting
289
565
 
290
- Error codes are automatically generated from names when not provided:
566
+ ErrorX does NOT auto-format messages. Messages are passed through as-is:
291
567
 
292
568
  ```typescript
293
- new ErrorX({ message: 'Failed', name: 'DatabaseError' })
294
- // code: 'DATABASE_ERROR'
569
+ new ErrorX({ message: "test error" });
570
+ // → message: 'test error'
295
571
 
296
- new ErrorX({ message: 'Failed', name: 'userAuthError' })
297
- // code: 'USER_AUTH_ERROR'
572
+ new ErrorX({ message: "Test error." });
573
+ // → message: 'Test error.'
574
+ ```
575
+
576
+ Empty or whitespace-only messages default to `'An error occurred'`:
298
577
 
299
- new ErrorX({ message: 'Failed', name: 'API Timeout' })
300
- // code: 'API_TIMEOUT'
578
+ ```typescript
579
+ new ErrorX({ message: "" });
580
+ // → message: 'An error occurred'
301
581
  ```
302
582
 
583
+ Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
584
+
585
+ ---
586
+
303
587
  ## License
304
588
 
305
589
  MIT