@bombillazo/error-x 0.4.5 → 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,392 +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
50
  ## Quick Start
46
51
 
47
52
  ```typescript
48
- import { ErrorX, http } from '@bombillazo/error-x'
53
+ import { ErrorX, HTTPErrorX, DBErrorX } from "@bombillazo/error-x";
49
54
 
50
55
  // Simple usage
51
- throw new ErrorX('Database connection failed')
56
+ throw new ErrorX("Something went wrong");
52
57
 
53
- // With options
54
- throw new ErrorX({
55
- message: 'User authentication failed',
56
- name: 'AuthError',
57
- code: 'AUTH_FAILED',
58
- uiMessage: 'Please check your credentials and try again',
59
- metadata: { userId: 123, loginAttempt: 3 },
60
- sourceUrl: 'https://api.example.com/auth',
61
- source: 'auth-service',
62
- httpStatus: 401
63
- })
64
-
65
- // Using HTTP presets
66
- throw new ErrorX(http[404])
67
-
68
- // Customizing presets
58
+ // A fully defined error
69
59
  throw new ErrorX({
70
- ...http[401],
71
- message: 'Session expired',
72
- metadata: { userId: 123 }
73
- })
74
-
75
- // Smart conversion from unknown errors
76
- try {
77
- await someOperation()
78
- } catch (error) {
79
- const errorX = ErrorX.from(error)
80
- throw errorX.withMetadata({ context: 'additional info' })
81
- }
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);
82
74
  ```
83
75
 
84
76
  ## Documentation
85
77
 
86
- ### Constructor
78
+ [Full Documentation](docs/index.md)
87
79
 
88
- ```typescript
89
- new ErrorX(input?: string | ErrorXOptions)
90
- ```
80
+ ---
91
81
 
92
- All parameters are optional. ErrorX uses sensible defaults:
82
+ ## API Reference
93
83
 
94
- | Property | Type | Default Value | Description |
95
- | --------- | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
96
- | message | `string` | `'An error occurred'` | Technical error message (pass-through, no auto-formatting) |
97
- | name | `string` | `'Error'` | Error type/title |
98
- | code | `string \| number` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
99
- | uiMessage | `string \| undefined` | `undefined` | User-friendly message for display |
100
- | cause | `Error \| unknown` | `undefined` | Original error that caused this (preserves full error chain) |
101
- | metadata | `Record<string, unknown> \| undefined` | `undefined` | Additional context and data |
102
- | httpStatus | `number \| undefined` | `undefined` | HTTP status code (100-599) for HTTP-related errors |
103
- | type | `string \| undefined` | `undefined` | Error type for categorization (e.g., 'http', 'validation') |
104
- | sourceUrl | `string \| undefined` | `undefined` | URL related to the error (API endpoint, page URL, resource URL) |
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) |
84
+ ### ErrorX Class
109
85
 
110
- ### HTTP Error Presets
86
+ The base error class that extends the native `Error` with enhanced capabilities.
111
87
 
112
- ErrorX provides pre-configured error templates via the `http` export:
88
+ #### Properties
113
89
 
114
- ```typescript
115
- import { ErrorX, http } from '@bombillazo/error-x'
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 |
116
104
 
117
- // Use preset directly
118
- throw new ErrorX(http[404])
119
- // Result: 404 error with message "Not found.", code "NOT_FOUND", etc.
105
+ #### Static Methods
120
106
 
121
- // Override specific fields
122
- throw new ErrorX({
123
- ...http[404],
124
- message: 'User not found',
125
- metadata: { userId: 123 }
126
- })
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 |
127
118
 
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
- }
138
- ```
119
+ #### Instance Methods
139
120
 
140
- #### Available Presets
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 |
141
126
 
142
- All presets are indexed by **HTTP status code** (numeric keys) and include:
143
- - `httpStatus`: HTTP status code
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
- - `type`: Set to `'http'` for all HTTP presets
127
+ ---
149
128
 
150
- **4xx Client Errors:**
129
+ ## Constructor
151
130
 
152
- `400`, `401`, `402`, `403`, `404`, `405`, `406`, `407`, `408`, `409`, `410`, `411`, `412`, `413`, `414`, `415`, `416`, `417`, `418`, `422`, `423`, `424`, `425`, `426`, `428`, `429`, `431`, `451`
131
+ ```typescript
132
+ new ErrorX(input?: string | ErrorXOptions)
133
+ ```
153
134
 
154
- **5xx Server Errors:**
155
- `500`, `501`, `502`, `503`, `504`, `505`, `506`, `507`, `508`, `510`, `511`
135
+ Create a new ErrorX instance. All parameters are optional with sensible defaults.
156
136
 
157
- #### Creating Your Own Presets
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
158
 
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:
159
+ ### ErrorXOptions
160
160
 
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>;
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) |
200
170
 
201
- // Use them just like http presets
202
- throw new ErrorX(dbErrors.connectionFailed);
203
- throw new ErrorX({ ...authErrors.invalidToken, metadata: { userId: 123 } });
204
- ```
171
+ ---
172
+
173
+ ## Common Methods
205
174
 
206
- This approach keeps your error handling consistent while remaining flexible for your specific domain.
175
+ ### ErrorX.from()
207
176
 
208
- ### Static Methods
177
+ Convert any value into an ErrorX instance with intelligent property extraction.
209
178
 
210
- #### `ErrorX.from(error: unknown): ErrorX`
179
+ ```typescript
180
+ static from<T>(payload: unknown, overrides?: Partial<ErrorXOptions<T>>): ErrorX<T>
181
+ ```
211
182
 
212
- Converts any error type to ErrorX with intelligent property extraction:
183
+ Handles strings, Error objects, API responses, and unknown values. Extracts common properties like `message`, `code`, `status`, `statusCode`, and `metadata`.
213
184
 
214
185
  ```typescript
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
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
+ });
232
205
  ```
233
206
 
234
- #### `ErrorX.isErrorX(value: unknown): value is ErrorX`
207
+ ### ErrorX.isErrorX()
235
208
 
236
- Type guard to check if a value is an ErrorX instance:
209
+ Type guard to check if a value is an ErrorX instance.
237
210
 
238
211
  ```typescript
239
- if (ErrorX.isErrorX(error)) {
240
- console.log(error.code, error.uiMessage)
212
+ static isErrorX<T>(value: unknown): value is ErrorX<T>
213
+ ```
214
+
215
+ ```typescript
216
+ try {
217
+ await riskyOperation();
218
+ } catch (error) {
219
+ if (ErrorX.isErrorX(error)) {
220
+ console.log(error.code, error.metadata);
221
+ }
241
222
  }
242
223
  ```
243
224
 
244
- #### `ErrorX.configure(config: ErrorXConfig): void`
225
+ ### withMetadata()
245
226
 
246
- Set global defaults for all ErrorX instances:
227
+ Create a new ErrorX with additional metadata merged with existing metadata.
247
228
 
248
229
  ```typescript
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
- }
256
- })
230
+ withMetadata<T>(additionalMetadata: T): ErrorX<TMetadata & T>
231
+ ```
232
+
233
+ ```typescript
234
+ const error = new ErrorX({
235
+ message: "Request failed",
236
+ metadata: { endpoint: "/api/users" },
237
+ });
257
238
 
258
- // Now all errors automatically get:
259
- // - source: 'my-api-service' (unless overridden)
260
- // - docsUrl: auto-generated from docsBaseURL + docsMap[code]
239
+ const enriched = error.withMetadata({ retryCount: 3, userId: 123 });
240
+ // metadata: { endpoint: '/api/users', retryCount: 3, userId: 123 }
261
241
  ```
262
242
 
263
- #### `ErrorX.getConfig(): ErrorXConfig | null`
243
+ ### Serialization
264
244
 
265
- Get the current global configuration:
245
+ Serialize ErrorX instances for network transfer or storage.
266
246
 
267
247
  ```typescript
268
- const config = ErrorX.getConfig()
269
- console.log(config?.source) // 'my-api-service'
248
+ // Serialize
249
+ const json = error.toJSON();
250
+ // { name, message, code, uiMessage, stack, metadata, timestamp, httpStatus, original, chain }
251
+
252
+ // Deserialize
253
+ const restored = ErrorX.fromJSON(json);
270
254
  ```
271
255
 
272
- ### Instance Methods
256
+ ### Error Chaining
273
257
 
274
- #### `withMetadata(additionalMetadata: Record<string, unknown>): ErrorX`
258
+ Build error timelines by passing `cause` to preserve the full error history.
275
259
 
276
- Creates a new ErrorX instance with merged metadata:
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
+ ```
277
275
 
278
276
  ```typescript
279
- const error = new ErrorX({ message: 'test', metadata: { a: 1 } })
280
- const enriched = error.withMetadata({ b: 2 })
281
- // enriched.metadata = { a: 1, b: 2 }
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
+ }
282
295
  ```
283
296
 
284
- #### `ErrorX.cleanStack(stack?: string, delimiter?: string): string`
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
285
306
 
286
- Cleans a stack trace by removing ErrorX internal calls and optionally trimming after a delimiter:
307
+ ### HTTPErrorX
308
+
309
+ HTTP errors with presets for all standard status codes (400-511).
287
310
 
288
311
  ```typescript
289
- const error = new ErrorX('test')
312
+ import { HTTPErrorX } from "@bombillazo/error-x";
290
313
 
291
- // Clean with pattern-based removal only
292
- const cleaned = ErrorX.cleanStack(error.stack)
314
+ // Create by status code
315
+ HTTPErrorX.create(404);
316
+ // → code: 'NOT_FOUND', name: 'NotFoundError', httpStatus: 404
293
317
 
294
- // Clean and trim after delimiter
295
- const trimmed = ErrorX.cleanStack(error.stack, 'my-app-boundary')
296
- // Returns stack trace starting after the line containing 'my-app-boundary'
297
- ```
318
+ HTTPErrorX.create(401);
319
+ // code: 'UNAUTHORIZED', name: 'UnauthorizedError', httpStatus: 401
298
320
 
299
- #### `toJSON(): ErrorXSerialized`
321
+ // With overrides
322
+ HTTPErrorX.create(404, {
323
+ message: "User not found",
324
+ metadata: { userId: 123 },
325
+ });
300
326
 
301
- Serializes the error for network transfer:
327
+ // With error chaining
328
+ HTTPErrorX.create(500, { cause: originalError });
302
329
 
303
- ```typescript
304
- const error = new ErrorX({ message: 'test', code: 'TEST' })
305
- const json = error.toJSON()
306
- // Returns plain object with all error properties
330
+ // instanceof checks
331
+ if (error instanceof HTTPErrorX) {
332
+ console.log(error.httpStatus);
333
+ }
307
334
  ```
308
335
 
309
- #### `fromJSON(serialized: ErrorXSerialized): ErrorX`
336
+ **Available Presets:** All 4xx client errors (400-451) and 5xx server errors (500-511).
337
+
338
+ ### DBErrorX
310
339
 
311
- Deserializes a JSON object back to ErrorX:
340
+ Database errors with presets for common database scenarios. All codes are automatically prefixed with `DB_`.
312
341
 
313
342
  ```typescript
314
- const json = { name: 'TestError', message: 'test', code: 'TEST', ... }
315
- const error = ErrorX.fromJSON(json)
316
- // Returns fully reconstructed ErrorX instance
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);
382
+ }
317
383
  ```
318
384
 
319
- ## Usage Examples
385
+ ### ValidationErrorX
320
386
 
321
- ### Basic Error Handling
387
+ Validation errors with Zod integration. All codes are prefixed with `VALIDATION_`.
322
388
 
323
389
  ```typescript
324
- import { ErrorX } from '@bombillazo/error-x'
325
-
326
- function validateUser(user: unknown) {
327
- if (!user) {
328
- throw new ErrorX({
329
- message: 'User validation failed: user is required',
330
- name: 'ValidationError',
331
- code: 'USER_REQUIRED',
332
- uiMessage: 'Please provide user information',
333
- metadata: { field: 'user', received: user }
334
- })
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
+
399
+ try {
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
335
408
  }
336
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);
431
+ }
337
432
  ```
338
433
 
339
- ### API Error Handling
434
+ ### Creating Custom Error Classes
435
+
436
+ Extend `ErrorX` to create domain-specific error classes with presets, defaults, and transforms.
340
437
 
341
438
  ```typescript
342
- async function fetchUser(id: string) {
343
- try {
344
- const response = await fetch(`/api/users/${id}`)
345
- if (!response.ok) {
346
- throw new ErrorX({
347
- ...http[response.status === 404 ? 404 : 500],
348
- metadata: { status: response.status, statusText: response.statusText }
349
- })
350
- }
351
- return response.json()
352
- } catch (error) {
353
- const errorX = ErrorX.from(error)
354
- throw errorX.withMetadata({ userId: id, operation: 'fetchUser' })
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;
355
502
  }
356
503
  }
357
- ```
358
504
 
359
- ### Error Chaining
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
+ });
360
510
 
361
- ```typescript
362
- try {
363
- await database.transaction(async (tx) => {
364
- await tx.users.create(userData)
365
- })
366
- } catch (dbError) {
367
- throw new ErrorX({
368
- message: 'User creation failed',
369
- name: 'UserCreationError',
370
- code: 'USER_CREATE_FAILED',
371
- uiMessage: 'Unable to create user account',
372
- cause: dbError, // Preserves original stack trace
373
- metadata: {
374
- operation: 'userRegistration',
375
- email: userData.email
376
- }
377
- })
511
+ // instanceof works
512
+ if (error instanceof PaymentErrorX) {
513
+ console.log(error.metadata?.transactionId);
378
514
  }
379
515
  ```
380
516
 
381
- ## Message Formatting
517
+ ---
518
+
519
+ ## Other Usage
382
520
 
383
- **Important:** ErrorX does NOT auto-format messages. Messages are passed through as-is:
521
+ ### Global Configuration
522
+
523
+ Configure stack trace cleaning and other global settings.
384
524
 
385
525
  ```typescript
386
- new ErrorX({ message: 'test error' })
387
- // message: 'test error' (exactly as provided)
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 });
388
541
 
389
- new ErrorX({ message: 'Test error.' })
390
- // message: 'Test error.' (exactly as provided)
542
+ // Get current config
543
+ const config = ErrorX.getConfig();
544
+
545
+ // Reset to defaults
546
+ ErrorX.resetConfig();
391
547
  ```
392
548
 
393
- Empty or whitespace-only messages default to `'An error occurred'`:
549
+ ### Auto Code Generation
550
+
551
+ Error codes are automatically generated from names in UPPER_SNAKE_CASE when not provided:
394
552
 
395
553
  ```typescript
396
- new ErrorX({ message: '' })
397
- // message: 'An error occurred'
554
+ new ErrorX({ name: "DatabaseError" });
555
+ // → code: 'DATABASE_ERROR'
398
556
 
399
- new ErrorX({ message: ' ' })
400
- // message: 'An error occurred'
401
- ```
557
+ new ErrorX({ name: "userAuthError" });
558
+ // → code: 'USER_AUTH_ERROR'
402
559
 
403
- HTTP presets provide properly formatted messages with sentence casing and periods.
560
+ new ErrorX({ name: "API Timeout" });
561
+ // → code: 'API_TIMEOUT'
562
+ ```
404
563
 
405
- ## Auto Code Generation
564
+ ### Message Formatting
406
565
 
407
- Error codes are automatically generated from names when not provided:
566
+ ErrorX does NOT auto-format messages. Messages are passed through as-is:
408
567
 
409
568
  ```typescript
410
- new ErrorX({ message: 'Failed', name: 'DatabaseError' })
411
- // code: 'DATABASE_ERROR'
569
+ new ErrorX({ message: "test error" });
570
+ // → message: 'test error'
412
571
 
413
- new ErrorX({ message: 'Failed', name: 'userAuthError' })
414
- // code: 'USER_AUTH_ERROR'
572
+ new ErrorX({ message: "Test error." });
573
+ // → message: 'Test error.'
574
+ ```
415
575
 
416
- new ErrorX({ message: 'Failed', name: 'API Timeout' })
417
- // code: 'API_TIMEOUT'
576
+ Empty or whitespace-only messages default to `'An error occurred'`:
577
+
578
+ ```typescript
579
+ new ErrorX({ message: "" });
580
+ // → message: 'An error occurred'
418
581
  ```
419
582
 
583
+ Preset messages in specialized classes (HTTPErrorX, DBErrorX) are properly formatted with sentence casing and periods.
584
+
585
+ ---
586
+
420
587
  ## License
421
588
 
422
589
  MIT