@bombillazo/error-x 0.4.6 → 0.5.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,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,273 +36,546 @@ 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` | `ErrorXSnapshot \| 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.
95
136
 
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) |
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
109
160
 
110
- ### HTTP Error Presets
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 | `unknown` | `undefined` | Error that caused this (builds the chain) |
111
170
 
112
- ErrorX provides pre-configured error templates via the `http` export:
171
+ ## Global Configuration
172
+
173
+ Configure stack trace cleaning and other global settings.
113
174
 
114
175
  ```typescript
115
- import { ErrorX, http } from '@bombillazo/error-x'
176
+ import { ErrorX } from "@bombillazo/error-x";
116
177
 
117
- // Use preset directly
118
- throw new ErrorX(http[404])
119
- // Result: 404 error with message "Not found.", code "NOT_FOUND", etc.
178
+ // Enable stack cleaning with custom delimiter
179
+ ErrorX.configure({
180
+ cleanStack: true,
181
+ cleanStackDelimiter: "my-app-entry",
182
+ });
120
183
 
121
- // Override specific fields
122
- throw new ErrorX({
123
- ...http[404],
124
- message: 'User not found',
125
- metadata: { userId: 123 }
126
- })
184
+ // Custom patterns to remove from stack traces
185
+ ErrorX.configure({
186
+ cleanStack: ["node_modules", "internal/"],
187
+ });
127
188
 
128
- // Add error cause
129
- try {
130
- // some operation
131
- } catch (originalError) {
132
- throw new ErrorX({
133
- ...http[500],
134
- cause: originalError,
135
- metadata: { operation: 'database-query' }
136
- })
137
- }
189
+ // Disable stack cleaning
190
+ ErrorX.configure({ cleanStack: false });
191
+
192
+ // Get current config
193
+ const config = ErrorX.getConfig();
194
+
195
+ // Reset to defaults
196
+ ErrorX.resetConfig();
138
197
  ```
139
198
 
140
- #### Available Presets
199
+ ### Auto Code Generation
141
200
 
142
- All presets are indexed by **HTTP status code** (numeric keys) and include:
201
+ Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
143
202
 
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
203
+ ```typescript
204
+ new ErrorX({ name: "DatabaseError" });
205
+ // code: 'DATABASE_ERROR'
206
+
207
+ new ErrorX({ name: "userAuthError" });
208
+ // → code: 'USER_AUTH_ERROR'
209
+
210
+ new ErrorX({ name: "API Timeout" });
211
+ // → code: 'API_TIMEOUT'
212
+ ```
149
213
 
150
- #### Creating Your Own Presets
214
+ ### Message Formatting
151
215
 
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:
216
+ ErrorX does NOT auto-format messages. Messages are passed through as-is:
153
217
 
154
218
  ```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>;
219
+ new ErrorX({ message: "test error" });
220
+ // → message: 'test error'
193
221
 
194
- // Use them just like http presets
195
- throw new ErrorX(dbErrors.connectionFailed);
196
- throw new ErrorX({ ...authErrors.invalidToken, metadata: { userId: 123 } });
222
+ new ErrorX({ message: "Test error." });
223
+ // message: 'Test error.'
197
224
  ```
198
225
 
199
- This approach keeps your error handling consistent while remaining flexible for your specific domain.
226
+ Empty or whitespace-only messages default to `'An error occurred'`:
200
227
 
228
+ ```typescript
229
+ new ErrorX({ message: "" });
230
+ // → message: 'An error occurred'
231
+ ```
201
232
 
202
- ## Usage Examples
233
+ Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
203
234
 
204
- ### Basic Error Handling
235
+ ---
236
+
237
+ ## Common Methods
238
+
239
+ ### ErrorX.from()
240
+
241
+ Convert any value into an ErrorX instance with intelligent property extraction.
205
242
 
206
243
  ```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
- }
219
- }
244
+ static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>
220
245
  ```
221
246
 
222
- ### API Error Handling
247
+ Handles strings, Error objects, API responses, and unknown values. Extracts common properties like `message`, `code`, `status`, `statusCode`, and `metadata`.
248
+
249
+ ```typescript
250
+ // Convert string
251
+ ErrorX.from("Something went wrong");
252
+
253
+ // Convert Error
254
+ ErrorX.from(new Error("Connection failed"));
255
+
256
+ // Convert API response
257
+ ErrorX.from({
258
+ message: "User not found",
259
+ code: "USER_404",
260
+ status: 404,
261
+ metadata: { userId: 123 },
262
+ });
263
+
264
+ // With overrides (deep merged)
265
+ ErrorX.from(error, {
266
+ httpStatus: 500,
267
+ metadata: { context: "db-layer" },
268
+ });
269
+ ```
270
+
271
+ ### ErrorX.isErrorX()
272
+
273
+ Type guard to check if a value is an ErrorX instance.
274
+
275
+ ```typescript
276
+ static isErrorX<T>(value: unknown): value is ErrorX<T>
277
+ ```
223
278
 
224
279
  ```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' })
280
+ try {
281
+ await riskyOperation();
282
+ } catch (error) {
283
+ if (ErrorX.isErrorX(error)) {
284
+ console.log(error.code, error.metadata);
238
285
  }
239
286
  }
240
287
  ```
241
288
 
289
+ ### withMetadata()
290
+
291
+ Create a new ErrorX with additional metadata merged with existing metadata.
292
+
293
+ ```typescript
294
+ withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>
295
+ ```
296
+
297
+ ```typescript
298
+ const error = new ErrorX({
299
+ message: "Request failed",
300
+ metadata: { endpoint: "/api/users" },
301
+ });
302
+
303
+ const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
304
+ // metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }
305
+ ```
306
+
307
+ ### Serialization
308
+
309
+ Serialize ErrorX instances for network transfer or storage.
310
+
311
+ ```typescript
312
+ // Serialize
313
+ const json = error.toJSON();
314
+ // { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
315
+
316
+ // Deserialize
317
+ const restored = ErrorX.fromJSON(json);
318
+ ```
319
+
242
320
  ### Error Chaining
243
321
 
322
+ Build error timelines by passing `cause` to preserve the full error history.
323
+
324
+ ```typescript
325
+ // Build an error chain
326
+ const dbError = ErrorX.from(new Error("ECONNREFUSED"));
327
+ const repoError = new ErrorX({ message: "Query failed", cause: dbError });
328
+ const serviceError = new ErrorX({
329
+ message: "User fetch failed",
330
+ cause: repoError,
331
+ });
332
+
333
+ // Access chain information
334
+ serviceError.chain.length; // 3: [serviceError, repoError, dbError]
335
+ serviceError.parent; // repoError
336
+ serviceError.root; // dbError
337
+ dbError.original; // { message: 'ECONNREFUSED', name: 'Error', stack: '...' }
338
+ ```
339
+
244
340
  ```typescript
341
+ // Practical example
245
342
  try {
246
- await database.transaction(async (tx) => {
247
- await tx.users.create(userData)
248
- })
343
+ await database.query(sql);
249
344
  } 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
- })
345
+ throw DBErrorX.create("QUERY_FAILED", {
346
+ cause: dbError,
347
+ metadata: { query: sql, table: "users" },
348
+ });
349
+ }
350
+
351
+ // Later, inspect the chain
352
+ if (ErrorX.isErrorX(error)) {
353
+ console.log(
354
+ "Error chain:",
355
+ error.chain.map((e) => e.name),
356
+ );
357
+ console.log("Root cause:", error.root?.original);
261
358
  }
262
359
  ```
263
360
 
264
- ## Message Formatting
361
+ ---
362
+
363
+ ## Custom Error Classes
364
+
365
+ error-x includes several custom error classes out of the box:
366
+
367
+ - **Ready-to-use** - Practical error classes for common scenarios (HTTP, database, validation)
368
+ - **Educational** - Demonstrate how to use presets, defaults, and transforms
369
+ - **Extensible** - Serve as templates and inspiration for your own domain-specific error classes
370
+
371
+ ### HTTPErrorX
265
372
 
266
- **Important:** ErrorX does NOT auto-format messages. Messages are passed through as-is:
373
+ HTTP errors with presets for all standard status codes (400-511).
267
374
 
268
375
  ```typescript
269
- new ErrorX({ message: 'test error' })
270
- // message: 'test error' (exactly as provided)
376
+ import { HTTPErrorX } from "@bombillazo/error-x";
271
377
 
272
- new ErrorX({ message: 'Test error.' })
273
- // message: 'Test error.' (exactly as provided)
378
+ // Create by status code
379
+ HTTPErrorX.create(404);
380
+ // → code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
381
+
382
+ HTTPErrorX.create(401);
383
+ // → code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
384
+
385
+ // With overrides
386
+ HTTPErrorX.create(404, {
387
+ message: "User not found",
388
+ metadata: { userId: 123 },
389
+ });
390
+
391
+ // With error chaining
392
+ HTTPErrorX.create(500, { cause: originalError });
393
+
394
+ // instanceof checks
395
+ if (error instanceof HTTPErrorX) {
396
+ console.log(error.httpStatus);
397
+ }
274
398
  ```
275
399
 
276
- Empty or whitespace-only messages default to `'An error occurred'`:
400
+ **Available Presets:** All 4xx client errors (400-451) and 5xx server errors (500-511).
401
+
402
+ ### DBErrorX
403
+
404
+ Database errors with presets for common database scenarios. All codes are automatically prefixed with `DB_`.
277
405
 
278
406
  ```typescript
279
- new ErrorX({ message: '' })
280
- // message: 'An error occurred'
407
+ import { DBErrorX } from "@bombillazo/error-x";
408
+
409
+ // Connection errors
410
+ DBErrorX.create("CONNECTION_FAILED"); // → code: 'DB_CONNECTION_FAILED'
411
+ DBErrorX.create("CONNECTION_TIMEOUT");
412
+ DBErrorX.create("CONNECTION_REFUSED");
413
+ DBErrorX.create("CONNECTION_LOST");
414
+
415
+ // Query errors
416
+ DBErrorX.create("QUERY_FAILED");
417
+ DBErrorX.create("QUERY_TIMEOUT");
418
+ DBErrorX.create("SYNTAX_ERROR");
419
+
420
+ // Constraint errors (with appropriate httpStatus)
421
+ DBErrorX.create("UNIQUE_VIOLATION"); // httpStatus: 409
422
+ DBErrorX.create("FOREIGN_KEY_VIOLATION"); // httpStatus: 400
423
+ DBErrorX.create("NOT_NULL_VIOLATION"); // httpStatus: 400
424
+ DBErrorX.create("CHECK_VIOLATION"); // httpStatus: 400
425
+
426
+ // Transaction errors
427
+ DBErrorX.create("TRANSACTION_FAILED");
428
+ DBErrorX.create("DEADLOCK"); // httpStatus: 409
429
+
430
+ // Record errors
431
+ DBErrorX.create("NOT_FOUND"); // httpStatus: 404
432
+
433
+ // With metadata
434
+ DBErrorX.create("QUERY_FAILED", {
435
+ message: "Failed to fetch user",
436
+ metadata: {
437
+ query: "SELECT * FROM users WHERE id = ?",
438
+ table: "users",
439
+ operation: "SELECT",
440
+ },
441
+ });
281
442
 
282
- new ErrorX({ message: ' ' })
283
- // message: 'An error occurred'
443
+ // instanceof checks
444
+ if (error instanceof DBErrorX) {
445
+ console.log(error.metadata?.table);
446
+ }
284
447
  ```
285
448
 
286
- HTTP presets provide properly formatted messages with sentence casing and periods.
449
+ ### ValidationErrorX
287
450
 
288
- ## Auto Code Generation
451
+ Validation errors with Zod integration. All codes are prefixed with `VALIDATION_`.
289
452
 
290
- Error codes are automatically generated from names when not provided:
453
+ ```typescript
454
+ import { z } from "zod";
455
+ import { ValidationErrorX } from "@bombillazo/error-x";
456
+
457
+ // From Zod errors
458
+ const schema = z.object({
459
+ email: z.string().email(),
460
+ age: z.number().min(18),
461
+ });
462
+
463
+ try {
464
+ schema.parse({ email: "invalid", age: 15 });
465
+ } catch (err) {
466
+ if (err instanceof z.ZodError) {
467
+ throw ValidationErrorX.fromZodError(err);
468
+ // → code: 'VALIDATION_INVALID_STRING'
469
+ // → metadata.field: 'email'
470
+ // → metadata.zodCode: 'invalid_string'
471
+ // → httpStatus: 400
472
+ }
473
+ }
474
+
475
+ // With overrides
476
+ ValidationErrorX.fromZodError(zodError, {
477
+ uiMessage: "Please check your input",
478
+ httpStatus: 422,
479
+ });
480
+
481
+ // Field-specific errors (without Zod)
482
+ ValidationErrorX.forField("email", "Invalid email format");
483
+ ValidationErrorX.forField("age", "Must be 18 or older", { code: "TOO_YOUNG" });
484
+
485
+ // Direct creation
486
+ ValidationErrorX.create({
487
+ message: "Invalid input",
488
+ code: "INVALID_INPUT",
489
+ metadata: { field: "email" },
490
+ });
491
+
492
+ // instanceof checks
493
+ if (error instanceof ValidationErrorX) {
494
+ console.log(error.metadata?.field);
495
+ }
496
+ ```
497
+
498
+ ### Creating Custom Error Classes
499
+
500
+ Extend `ErrorX` to create domain-specific error classes with presets, defaults, and transforms.
291
501
 
292
502
  ```typescript
293
- new ErrorX({ message: 'Failed', name: 'DatabaseError' })
294
- // code: 'DATABASE_ERROR'
503
+ import {
504
+ ErrorX,
505
+ type ErrorXOptions,
506
+ type ErrorXTransform,
507
+ } from "@bombillazo/error-x";
508
+
509
+ // 1. Define your metadata type
510
+ type PaymentMetadata = {
511
+ transactionId?: string;
512
+ amount?: number;
513
+ currency?: string;
514
+ };
515
+
516
+ // 2. Define presets outside the class for type inference
517
+ const paymentPresets = {
518
+ INSUFFICIENT_FUNDS: {
519
+ code: "INSUFFICIENT_FUNDS",
520
+ name: "PaymentError",
521
+ message: "Insufficient funds.",
522
+ uiMessage: "Your payment method has insufficient funds.",
523
+ httpStatus: 402,
524
+ },
525
+ CARD_DECLINED: {
526
+ code: "CARD_DECLINED",
527
+ name: "PaymentError",
528
+ message: "Card declined.",
529
+ uiMessage: "Your card was declined. Please try another payment method.",
530
+ httpStatus: 402,
531
+ },
532
+ EXPIRED_CARD: {
533
+ code: "EXPIRED_CARD",
534
+ name: "PaymentError",
535
+ message: "Card expired.",
536
+ uiMessage: "Your card has expired. Please update your payment method.",
537
+ httpStatus: 402,
538
+ },
539
+ } as const satisfies Record<string, ErrorXOptions>;
540
+
541
+ // 3. Derive preset key type
542
+ type PaymentPresetKey = keyof typeof paymentPresets | (string & {});
543
+
544
+ // 4. Create the class
545
+ export class PaymentErrorX extends ErrorX<PaymentMetadata> {
546
+ static presets = paymentPresets;
547
+ static defaultPreset = "CARD_DECLINED";
548
+ static defaults = { httpStatus: 402 };
549
+
550
+ // Optional: transform to prefix codes
551
+ static transform: ErrorXTransform<PaymentMetadata> = (opts) => ({
552
+ ...opts,
553
+ code: `PAYMENT_${opts.code}`,
554
+ });
555
+
556
+ // Override create for proper typing
557
+ static override create(
558
+ presetKey?: PaymentPresetKey,
559
+ overrides?: Partial<ErrorXOptions<PaymentMetadata>>,
560
+ ): PaymentErrorX {
561
+ return ErrorX.create.call(
562
+ PaymentErrorX,
563
+ presetKey,
564
+ overrides,
565
+ ) as PaymentErrorX;
566
+ }
567
+ }
295
568
 
296
- new ErrorX({ message: 'Failed', name: 'userAuthError' })
297
- // code: 'USER_AUTH_ERROR'
569
+ // Usage
570
+ throw PaymentErrorX.create("INSUFFICIENT_FUNDS");
571
+ throw PaymentErrorX.create("CARD_DECLINED", {
572
+ metadata: { transactionId: "tx_123", amount: 99.99, currency: "USD" },
573
+ });
298
574
 
299
- new ErrorX({ message: 'Failed', name: 'API Timeout' })
300
- // code: 'API_TIMEOUT'
575
+ // instanceof works
576
+ if (error instanceof PaymentErrorX) {
577
+ console.log(error.metadata?.transactionId);
578
+ }
301
579
  ```
302
580
 
303
581
  ## License