@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/dist/index.d.cts CHANGED
@@ -64,12 +64,8 @@ type ErrorXOptions<TMetadata extends ErrorXMetadata = ErrorXMetadata> = {
64
64
  cause?: ErrorXCause | Error | unknown;
65
65
  /** Additional context and debugging information */
66
66
  metadata?: TMetadata | undefined;
67
- /** Error type for categorization */
68
- type?: string | undefined;
69
- /** Documentation URL for this specific error */
70
- docsUrl?: string | undefined;
71
- /** Where the error originated (service name, module, component) */
72
- source?: string | undefined;
67
+ /** HTTP status code associated with this error */
68
+ httpStatus?: number | undefined;
73
69
  };
74
70
  /**
75
71
  * Simplified representation of an error cause for serialization.
@@ -85,13 +81,30 @@ type ErrorXCause = {
85
81
  /** Stack trace (optional) */
86
82
  stack?: string;
87
83
  };
84
+ /**
85
+ * Context passed to the transform function when creating errors via `.create()`.
86
+ * Contains information about the preset being used.
87
+ *
88
+ * @public
89
+ */
90
+ type ErrorXTransformContext = {
91
+ /** The preset key used (if any) */
92
+ presetKey: string | number | undefined;
93
+ };
94
+ /**
95
+ * Transform function signature for custom error classes.
96
+ * Transforms options after merge but before instantiation.
97
+ *
98
+ * @public
99
+ */
100
+ type ErrorXTransform<TMetadata extends ErrorXMetadata = ErrorXMetadata> = (opts: ErrorXOptions<ErrorXMetadata>, ctx: ErrorXTransformContext) => ErrorXOptions<TMetadata>;
88
101
  /**
89
102
  * JSON-serializable representation of an ErrorX instance.
90
103
  * Used for transmitting errors over network or storing in databases.
91
104
  *
92
105
  * @example
93
106
  * ```typescript
94
- * const serialized: SerializableError = {
107
+ * const serialized: ErrorXSerialized = {
95
108
  * name: 'AuthError',
96
109
  * message: 'Authentication failed.',
97
110
  * code: 'AUTH_FAILED',
@@ -99,13 +112,16 @@ type ErrorXCause = {
99
112
  * stack: 'Error: Authentication failed.\n at login (auth.ts:42:15)',
100
113
  * metadata: { userId: 123, loginAttempt: 3 },
101
114
  * timestamp: 1705315845123,
102
- * cause: {
115
+ * httpStatus: 401,
116
+ * original: {
103
117
  * name: 'NetworkError',
104
118
  * message: 'Request timeout.',
105
119
  * stack: '...'
106
120
  * },
107
- * docsUrl: 'https://docs.example.com/errors#auth-failed',
108
- * source: 'auth-service'
121
+ * chain: [
122
+ * { name: 'AuthError', message: 'Authentication failed.' },
123
+ * { name: 'NetworkError', message: 'Request timeout.' }
124
+ * ]
109
125
  * }
110
126
  * ```
111
127
  *
@@ -126,15 +142,20 @@ type ErrorXSerialized = {
126
142
  metadata: ErrorXMetadata | undefined;
127
143
  /** Unix epoch timestamp (milliseconds) when error was created */
128
144
  timestamp: number;
129
- /** Simplified cause error (for error chaining) */
130
- cause?: ErrorXCause;
131
- /** Error type for categorization */
132
- type?: string;
133
- /** Documentation URL for this error */
134
- docsUrl?: string;
135
- /** Where the error originated */
136
- source?: string;
145
+ /** HTTP status code associated with this error */
146
+ httpStatus?: number;
147
+ /** Serialized non-ErrorX entity this was wrapped from (if created via ErrorX.from()) */
148
+ original?: ErrorXCause;
149
+ /** Serialized error chain timeline (this error and all ancestors) */
150
+ chain?: ErrorXCause[];
137
151
  };
152
+ /**
153
+ * Type representing valid preset keys for error creation.
154
+ * Allows both string and number keys while preventing accidental misuse.
155
+ *
156
+ * @public
157
+ */
158
+ type ErrorXBasePresetKey = (number & {}) | (string & {});
138
159
 
139
160
  /**
140
161
  * Configuration interface for ErrorX global settings
@@ -142,12 +163,6 @@ type ErrorXSerialized = {
142
163
  * @public
143
164
  */
144
165
  interface ErrorXConfig {
145
- /** Default source identifier for all errors (e.g., service name, module name) */
146
- source?: string;
147
- /** Base URL for error documentation */
148
- docsBaseURL?: string;
149
- /** Mapping of error codes to documentation paths */
150
- docsMap?: Record<string, string>;
151
166
  /**
152
167
  * Control stack trace cleaning behavior
153
168
  * - true: Enable automatic stack trace cleaning (default)
@@ -168,12 +183,6 @@ interface ErrorXConfig {
168
183
  * ```typescript
169
184
  * // Configure globally (optional)
170
185
  * ErrorX.configure({
171
- * source: 'my-service',
172
- * docsBaseURL: 'https://docs.example.com',
173
- * docsMap: {
174
- * 'AUTH_FAILED': 'errors/authentication',
175
- * 'DB_ERROR': 'errors/database'
176
- * },
177
186
  * cleanStackDelimiter: 'my-app-entry' // Clean stack traces after this line
178
187
  * })
179
188
  *
@@ -211,14 +220,27 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
211
220
  metadata: TMetadata | undefined;
212
221
  /** Unix epoch timestamp (milliseconds) when the error was created */
213
222
  timestamp: number;
214
- /** Error type for categorization */
215
- type: string | undefined;
216
- /** Documentation URL for this specific error */
217
- docsUrl: string | undefined;
218
- /** Where the error originated (service name, module, component) */
219
- source: string | undefined;
220
- /** Original error that caused this error (preserves error chain) */
221
- cause: ErrorXCause | undefined;
223
+ /** HTTP status code associated with this error */
224
+ httpStatus: number | undefined;
225
+ /** Serialized non-ErrorX entity this was wrapped from (if created via ErrorX.from()) */
226
+ original: ErrorXCause | undefined;
227
+ /** Error chain timeline: [this, parent, grandparent, ...] - single source of truth */
228
+ private _chain;
229
+ /**
230
+ * Gets the immediate parent ErrorX in the chain (if any).
231
+ * @returns The ErrorX that caused this error, or undefined if this is the root
232
+ */
233
+ get parent(): ErrorX | undefined;
234
+ /**
235
+ * Gets the deepest ErrorX in the chain (the original root cause).
236
+ * @returns The root cause ErrorX, or undefined if chain has only this error
237
+ */
238
+ get root(): ErrorX | undefined;
239
+ /**
240
+ * Gets the full error chain timeline.
241
+ * @returns Array of ErrorX instances: [this, parent, grandparent, ...]
242
+ */
243
+ get chain(): readonly ErrorX[];
222
244
  /**
223
245
  * Creates a new ErrorX instance with enhanced error handling capabilities.
224
246
  *
@@ -274,12 +296,7 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
274
296
  * @example
275
297
  * ```typescript
276
298
  * ErrorX.configure({
277
- * source: 'my-api-service',
278
- * docsBaseURL: 'https://docs.example.com/errors',
279
- * docsMap: {
280
- * 'AUTH_FAILED': 'authentication-errors',
281
- * 'DB_ERROR': 'database-errors'
282
- * },
299
+ * cleanStack: true, // Enable stack trace cleaning
283
300
  * cleanStackDelimiter: 'app-entry-point' // Trim stack traces after this line
284
301
  * })
285
302
  * ```
@@ -301,13 +318,6 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
301
318
  * ```
302
319
  */
303
320
  static resetConfig(): void;
304
- /**
305
- * Validates and normalizes the type field
306
- *
307
- * @param type - Type value to validate
308
- * @returns Validated type string or undefined if invalid/empty
309
- */
310
- private static validateType;
311
321
  /**
312
322
  * Validates if an object is a valid ErrorXOptions object.
313
323
  * Checks that the object only contains accepted ErrorXOptions fields.
@@ -331,15 +341,6 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
331
341
  * ```
332
342
  */
333
343
  private static generateDefaultCode;
334
- /**
335
- * Preserves the original error's stack trace while updating the error message.
336
- * Combines the new error's message with the original error's stack trace from ErrorXCause.
337
- *
338
- * @param cause - The ErrorXCause containing the original stack to preserve
339
- * @param newError - The new error whose message to use
340
- * @returns Combined stack trace with new error message and original stack
341
- */
342
- private static preserveOriginalStackFromCause;
343
344
  /**
344
345
  * Cleans a stack trace by removing ErrorX internal method calls and optionally trimming after a delimiter.
345
346
  * This provides cleaner stack traces that focus on user code.
@@ -416,8 +417,12 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
416
417
  * Handles strings, regular Error objects, API response objects, and unknown values.
417
418
  * Extracts metadata directly from objects if present.
418
419
  *
419
- * @param error - Value to convert to ErrorX
420
- * @returns ErrorX instance with extracted properties
420
+ * This is a "wrapping" operation - the resulting ErrorX represents the same error
421
+ * in ErrorX form. The original source is stored in the `original` property.
422
+ *
423
+ * @param payload - Value to convert to ErrorX
424
+ * @param overrides - Optional overrides to deep-merge with extracted properties
425
+ * @returns ErrorX instance with extracted properties and `original` set
421
426
  *
422
427
  * @example
423
428
  * ```typescript
@@ -435,12 +440,19 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
435
440
  * }
436
441
  * const error3 = ErrorX.from(apiError)
437
442
  * // error3.metadata = { userId: 123, endpoint: '/api/users' }
443
+ * // error3.original = { message: 'User not found', name: undefined, stack: undefined }
444
+ *
445
+ * // With overrides
446
+ * const error4 = ErrorX.from(new Error('Connection failed'), {
447
+ * httpStatus: 500,
448
+ * metadata: { context: 'db-layer' }
449
+ * })
438
450
  * ```
439
451
  */
440
- static from<TMetadata extends ErrorXMetadata = ErrorXMetadata>(error: ErrorX<TMetadata>): ErrorX<TMetadata>;
441
- static from(error: Error): ErrorX;
442
- static from(error: string): ErrorX;
443
- static from(error: unknown): ErrorX;
452
+ static from<TMetadata extends ErrorXMetadata = ErrorXMetadata>(error: ErrorX<TMetadata>, overrides?: Partial<ErrorXOptions<TMetadata>>): ErrorX<TMetadata>;
453
+ static from<TMetadata extends ErrorXMetadata = ErrorXMetadata>(payload: Error, overrides?: Partial<ErrorXOptions<TMetadata>>): ErrorX<TMetadata>;
454
+ static from<TMetadata extends ErrorXMetadata = ErrorXMetadata>(payload: string, overrides?: Partial<ErrorXOptions<TMetadata>>): ErrorX<TMetadata>;
455
+ static from<TMetadata extends ErrorXMetadata = ErrorXMetadata>(payload: unknown, overrides?: Partial<ErrorXOptions<TMetadata>>): ErrorX<TMetadata>;
444
456
  /**
445
457
  * Converts the ErrorX instance to a detailed string representation.
446
458
  * Includes error name, message, code, timestamp, metadata, and stack trace.
@@ -503,467 +515,1093 @@ declare class ErrorX<TMetadata extends ErrorXMetadata = ErrorXMetadata> extends
503
515
  * ```
504
516
  */
505
517
  static fromJSON<TMetadata extends ErrorXMetadata = ErrorXMetadata>(serialized: ErrorXSerialized): ErrorX<TMetadata>;
518
+ /**
519
+ * Creates a new instance of this error class using optional presets and overrides.
520
+ * This is a factory method that supports preset-based error creation with
521
+ * full TypeScript autocomplete for preset keys.
522
+ *
523
+ * Define static properties on your subclass to customize behavior:
524
+ * - `presets`: Record of preset configurations keyed by identifier
525
+ * - `defaultPreset`: Key of preset to use as fallback
526
+ * - `defaults`: Default values for all errors of this class
527
+ * - `transform`: Function to transform options before instantiation
528
+ *
529
+ * Supported call signatures:
530
+ * - `create()` - uses defaultPreset
531
+ * - `create(presetKey)` - uses specified preset
532
+ * - `create(presetKey, overrides)` - preset with overrides
533
+ * - `create(overrides)` - just overrides, uses defaultPreset
534
+ *
535
+ * @param presetKeyOrOverrides - Preset key (string/number) or overrides object
536
+ * @param overrides - Optional overrides when first arg is preset key
537
+ * @returns New instance of this error class
538
+ *
539
+ * @example
540
+ * ```typescript
541
+ * class DBError extends ErrorX<{ query?: string }> {
542
+ * static presets = {
543
+ * 9333: { message: 'Connection timeout', code: 'TIMEOUT' },
544
+ * CONN_REFUSED: { message: 'Connection refused', code: 'CONN_REFUSED' },
545
+ * GENERIC: { message: 'A database error occurred', code: 'ERROR' },
546
+ * }
547
+ * static defaultPreset = 'GENERIC'
548
+ * static defaults = { httpStatus: 500 }
549
+ * static transform = (opts, ctx) => ({
550
+ * ...opts,
551
+ * code: `DB_${opts.code}`,
552
+ * })
553
+ * }
554
+ *
555
+ * DBError.create() // uses defaultPreset
556
+ * DBError.create(9333) // uses preset 9333
557
+ * DBError.create('CONN_REFUSED') // uses preset CONN_REFUSED
558
+ * DBError.create(9333, { message: 'Custom' }) // preset + overrides
559
+ * DBError.create({ message: 'Custom' }) // just overrides
560
+ * ```
561
+ */
562
+ static create<T extends ErrorXMetadata = ErrorXMetadata>(this: new (options?: ErrorXOptions<T>) => ErrorX<T>, presetKeyOrOverrides?: string | number | Partial<ErrorXOptions<ErrorXMetadata>>, overrides?: Partial<ErrorXOptions<ErrorXMetadata>>): ErrorX<T>;
506
563
  }
507
564
 
508
565
  /**
509
- * HTTP error presets for common HTTP status codes.
510
- *
511
- * These presets provide pre-configured error options for standard HTTP error responses,
512
- * including appropriate status codes, error codes, names, messages (sentence case), and user-friendly UI messages.
566
+ * Database error presets for common scenarios.
567
+ * Defined outside the class to enable type inference for preset keys.
568
+ */
569
+ declare const dbPresets: {
570
+ readonly CONNECTION_FAILED: {
571
+ readonly code: "CONNECTION_FAILED";
572
+ readonly name: "DBConnectionError";
573
+ readonly message: "Failed to connect to database.";
574
+ readonly uiMessage: "Unable to connect to the database. Please try again later.";
575
+ };
576
+ readonly CONNECTION_TIMEOUT: {
577
+ readonly code: "CONNECTION_TIMEOUT";
578
+ readonly name: "DBConnectionTimeoutError";
579
+ readonly message: "Database connection timed out.";
580
+ readonly uiMessage: "The database connection timed out. Please try again.";
581
+ };
582
+ readonly CONNECTION_REFUSED: {
583
+ readonly code: "CONNECTION_REFUSED";
584
+ readonly name: "DBConnectionRefusedError";
585
+ readonly message: "Database connection refused.";
586
+ readonly uiMessage: "Unable to connect to the database. Please try again later.";
587
+ };
588
+ readonly CONNECTION_LOST: {
589
+ readonly code: "CONNECTION_LOST";
590
+ readonly name: "DBConnectionLostError";
591
+ readonly message: "Database connection lost.";
592
+ readonly uiMessage: "The database connection was lost. Please try again.";
593
+ };
594
+ readonly QUERY_FAILED: {
595
+ readonly code: "QUERY_FAILED";
596
+ readonly name: "DBQueryError";
597
+ readonly message: "Database query failed.";
598
+ readonly uiMessage: "The database operation failed. Please try again.";
599
+ };
600
+ readonly QUERY_TIMEOUT: {
601
+ readonly code: "QUERY_TIMEOUT";
602
+ readonly name: "DBQueryTimeoutError";
603
+ readonly message: "Database query timed out.";
604
+ readonly uiMessage: "The database operation took too long. Please try again.";
605
+ };
606
+ readonly SYNTAX_ERROR: {
607
+ readonly code: "SYNTAX_ERROR";
608
+ readonly name: "DBSyntaxError";
609
+ readonly message: "Invalid query syntax.";
610
+ readonly uiMessage: "An internal error occurred. Please contact support.";
611
+ };
612
+ readonly UNIQUE_VIOLATION: {
613
+ readonly code: "UNIQUE_VIOLATION";
614
+ readonly name: "DBUniqueViolationError";
615
+ readonly message: "Unique constraint violation.";
616
+ readonly uiMessage: "This record already exists.";
617
+ readonly httpStatus: 409;
618
+ };
619
+ readonly FOREIGN_KEY_VIOLATION: {
620
+ readonly code: "FOREIGN_KEY_VIOLATION";
621
+ readonly name: "DBForeignKeyError";
622
+ readonly message: "Foreign key constraint violation.";
623
+ readonly uiMessage: "This operation references a record that does not exist.";
624
+ readonly httpStatus: 400;
625
+ };
626
+ readonly NOT_NULL_VIOLATION: {
627
+ readonly code: "NOT_NULL_VIOLATION";
628
+ readonly name: "DBNotNullError";
629
+ readonly message: "Not null constraint violation.";
630
+ readonly uiMessage: "A required field is missing.";
631
+ readonly httpStatus: 400;
632
+ };
633
+ readonly CHECK_VIOLATION: {
634
+ readonly code: "CHECK_VIOLATION";
635
+ readonly name: "DBCheckViolationError";
636
+ readonly message: "Check constraint violation.";
637
+ readonly uiMessage: "The provided data is invalid.";
638
+ readonly httpStatus: 400;
639
+ };
640
+ readonly TRANSACTION_FAILED: {
641
+ readonly code: "TRANSACTION_FAILED";
642
+ readonly name: "DBTransactionError";
643
+ readonly message: "Database transaction failed.";
644
+ readonly uiMessage: "The operation failed. Please try again.";
645
+ };
646
+ readonly DEADLOCK: {
647
+ readonly code: "DEADLOCK";
648
+ readonly name: "DBDeadlockError";
649
+ readonly message: "Database deadlock detected.";
650
+ readonly uiMessage: "The operation encountered a conflict. Please try again.";
651
+ readonly httpStatus: 409;
652
+ };
653
+ readonly NOT_FOUND: {
654
+ readonly code: "NOT_FOUND";
655
+ readonly name: "DBNotFoundError";
656
+ readonly message: "Record not found.";
657
+ readonly uiMessage: "The requested record was not found.";
658
+ readonly httpStatus: 404;
659
+ };
660
+ readonly UNKNOWN: {
661
+ readonly code: "UNKNOWN";
662
+ readonly name: "DBErrorX";
663
+ readonly message: "An unknown database error occurred.";
664
+ readonly uiMessage: "A database error occurred. Please try again later.";
665
+ };
666
+ };
667
+ /**
668
+ * Valid preset keys for DBErrorX.create()
669
+ * Derived from the presets object. Provides autocomplete for known presets
670
+ * while allowing any string for flexibility.
671
+ */
672
+ type DBErrorXPresetKey = keyof typeof dbPresets | ErrorXBasePresetKey;
673
+ /**
674
+ * Metadata type for database errors.
675
+ * Provides context about the database operation that failed.
513
676
  *
514
- * ## Usage Patterns
677
+ * @public
678
+ */
679
+ type DBErrorXMetadata = {
680
+ /** The SQL query or operation that failed */
681
+ query?: string;
682
+ /** The table involved in the operation */
683
+ table?: string;
684
+ /** The database name */
685
+ database?: string;
686
+ /** The operation type (SELECT, INSERT, UPDATE, DELETE, etc.) */
687
+ operation?: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'TRANSACTION' | 'CONNECTION' | string;
688
+ /** The column involved (for constraint errors) */
689
+ column?: string;
690
+ /** The constraint name (for constraint violations) */
691
+ constraint?: string;
692
+ /** Duration of the operation in milliseconds */
693
+ duration?: number;
694
+ };
695
+ /**
696
+ * Database Error class with presets for common database error scenarios.
515
697
  *
516
- * ### 1. Use Preset Directly
517
- * Create an error with all preset values:
518
- * ```typescript
519
- * throw new ErrorX(http[404])
520
- * // Result: 404 error with message "Not found.", code, name, uiMessage, and type
521
- * ```
698
+ * Provides a type-safe way to create database errors with:
699
+ * - Common error presets (connection, query, timeout, constraints)
700
+ * - Automatic code prefixing with `DB_`
701
+ * - Full `instanceof` support
702
+ * - Typed metadata for database context
522
703
  *
523
- * ### 2. Override Specific Fields
524
- * Customize the error while keeping other preset values:
704
+ * @example
525
705
  * ```typescript
526
- * throw new ErrorX({
527
- * ...http[404],
528
- * message: 'User not found',
529
- * metadata: { userId: 123 }
530
- * })
531
- * // Result: 404 error with custom message but keeps httpStatus, code, name, uiMessage, type
532
- * ```
706
+ * // Basic usage with preset
707
+ * throw DBErrorX.create('CONNECTION_FAILED')
708
+ * throw DBErrorX.create('QUERY_FAILED')
709
+ * throw DBErrorX.create('TIMEOUT')
533
710
  *
534
- * ### 3. Add Metadata
535
- * Enhance presets with additional context:
536
- * ```typescript
537
- * throw new ErrorX({
538
- * ...http[401],
539
- * metadata: { attemptedAction: 'viewProfile', userId: 456 }
711
+ * // With metadata
712
+ * throw DBErrorX.create('QUERY_FAILED', {
713
+ * message: 'Failed to fetch user',
714
+ * metadata: {
715
+ * query: 'SELECT * FROM users WHERE id = ?',
716
+ * table: 'users',
717
+ * operation: 'SELECT'
718
+ * }
540
719
  * })
541
- * ```
542
720
  *
543
- * ### 4. Add Error Cause
544
- * Chain errors by adding a cause:
545
- * ```typescript
721
+ * // With error chaining
546
722
  * try {
547
- * // some operation
548
- * } catch (originalError) {
549
- * throw new ErrorX({
550
- * ...http[500],
551
- * cause: originalError,
552
- * metadata: { operation: 'database-query' }
553
- * })
723
+ * await db.query(sql)
724
+ * } catch (err) {
725
+ * throw DBErrorX.create('QUERY_FAILED', { cause: err })
554
726
  * }
555
- * ```
556
- *
557
- * ## Common HTTP Presets
558
- *
559
- * ### 4xx Client Errors
560
- * - `400` - Bad Request - Invalid request data
561
- * - `401` - Unauthorized - Authentication required
562
- * - `403` - Forbidden - Insufficient permissions
563
- * - `404` - Not Found - Resource not found
564
- * - `405` - Method Not Allowed - HTTP method not allowed
565
- * - `409` - Conflict - Resource conflict
566
- * - `422` - Unprocessable Entity - Validation failed
567
- * - `429` - Too Many Requests - Rate limit exceeded
568
- *
569
- * ### 5xx Server Errors
570
- * - `500` - Internal Server Error - Unexpected server error
571
- * - `501` - Not Implemented - Feature not implemented
572
- * - `502` - Bad Gateway - Upstream server error
573
- * - `503` - Service Unavailable - Service temporarily down
574
- * - `504` - Gateway Timeout - Upstream timeout
575
- *
576
- * @example
577
- * ```typescript
578
- * // API endpoint example
579
- * app.get('/users/:id', async (req, res) => {
580
- * const user = await db.users.findById(req.params.id)
581
- *
582
- * if (!user) {
583
- * throw new ErrorX({
584
- * ...http[404],
585
- * message: 'User not found',
586
- * metadata: { userId: req.params.id }
587
- * })
588
- * }
589
- *
590
- * res.json(user)
591
- * })
592
727
  *
593
- * // Authentication middleware example
594
- * const requireAuth = (req, res, next) => {
595
- * if (!req.user) {
596
- * throw new ErrorX(http[401])
597
- * }
598
- * next()
599
- * }
728
+ * // Just overrides (uses default UNKNOWN)
729
+ * throw DBErrorX.create({ message: 'Database error' })
600
730
  *
601
- * // Rate limiting example
602
- * if (isRateLimited(req.ip)) {
603
- * throw new ErrorX({
604
- * ...http[429],
605
- * metadata: {
606
- * ip: req.ip,
607
- * retryAfter: 60
608
- * }
609
- * })
731
+ * // instanceof checks
732
+ * if (error instanceof DBErrorX) {
733
+ * console.log(error.metadata?.table)
610
734
  * }
611
735
  * ```
612
736
  *
613
737
  * @public
614
738
  */
615
- declare const http: {
616
- readonly 400: {
617
- code: string;
618
- name: string;
619
- message: string;
620
- uiMessage: string;
621
- metadata: {
622
- status: number;
739
+ declare class DBErrorX extends ErrorX<DBErrorXMetadata> {
740
+ /**
741
+ * Database error presets for common scenarios.
742
+ */
743
+ static presets: {
744
+ readonly CONNECTION_FAILED: {
745
+ readonly code: "CONNECTION_FAILED";
746
+ readonly name: "DBConnectionError";
747
+ readonly message: "Failed to connect to database.";
748
+ readonly uiMessage: "Unable to connect to the database. Please try again later.";
749
+ };
750
+ readonly CONNECTION_TIMEOUT: {
751
+ readonly code: "CONNECTION_TIMEOUT";
752
+ readonly name: "DBConnectionTimeoutError";
753
+ readonly message: "Database connection timed out.";
754
+ readonly uiMessage: "The database connection timed out. Please try again.";
755
+ };
756
+ readonly CONNECTION_REFUSED: {
757
+ readonly code: "CONNECTION_REFUSED";
758
+ readonly name: "DBConnectionRefusedError";
759
+ readonly message: "Database connection refused.";
760
+ readonly uiMessage: "Unable to connect to the database. Please try again later.";
761
+ };
762
+ readonly CONNECTION_LOST: {
763
+ readonly code: "CONNECTION_LOST";
764
+ readonly name: "DBConnectionLostError";
765
+ readonly message: "Database connection lost.";
766
+ readonly uiMessage: "The database connection was lost. Please try again.";
767
+ };
768
+ readonly QUERY_FAILED: {
769
+ readonly code: "QUERY_FAILED";
770
+ readonly name: "DBQueryError";
771
+ readonly message: "Database query failed.";
772
+ readonly uiMessage: "The database operation failed. Please try again.";
773
+ };
774
+ readonly QUERY_TIMEOUT: {
775
+ readonly code: "QUERY_TIMEOUT";
776
+ readonly name: "DBQueryTimeoutError";
777
+ readonly message: "Database query timed out.";
778
+ readonly uiMessage: "The database operation took too long. Please try again.";
623
779
  };
780
+ readonly SYNTAX_ERROR: {
781
+ readonly code: "SYNTAX_ERROR";
782
+ readonly name: "DBSyntaxError";
783
+ readonly message: "Invalid query syntax.";
784
+ readonly uiMessage: "An internal error occurred. Please contact support.";
785
+ };
786
+ readonly UNIQUE_VIOLATION: {
787
+ readonly code: "UNIQUE_VIOLATION";
788
+ readonly name: "DBUniqueViolationError";
789
+ readonly message: "Unique constraint violation.";
790
+ readonly uiMessage: "This record already exists.";
791
+ readonly httpStatus: 409;
792
+ };
793
+ readonly FOREIGN_KEY_VIOLATION: {
794
+ readonly code: "FOREIGN_KEY_VIOLATION";
795
+ readonly name: "DBForeignKeyError";
796
+ readonly message: "Foreign key constraint violation.";
797
+ readonly uiMessage: "This operation references a record that does not exist.";
798
+ readonly httpStatus: 400;
799
+ };
800
+ readonly NOT_NULL_VIOLATION: {
801
+ readonly code: "NOT_NULL_VIOLATION";
802
+ readonly name: "DBNotNullError";
803
+ readonly message: "Not null constraint violation.";
804
+ readonly uiMessage: "A required field is missing.";
805
+ readonly httpStatus: 400;
806
+ };
807
+ readonly CHECK_VIOLATION: {
808
+ readonly code: "CHECK_VIOLATION";
809
+ readonly name: "DBCheckViolationError";
810
+ readonly message: "Check constraint violation.";
811
+ readonly uiMessage: "The provided data is invalid.";
812
+ readonly httpStatus: 400;
813
+ };
814
+ readonly TRANSACTION_FAILED: {
815
+ readonly code: "TRANSACTION_FAILED";
816
+ readonly name: "DBTransactionError";
817
+ readonly message: "Database transaction failed.";
818
+ readonly uiMessage: "The operation failed. Please try again.";
819
+ };
820
+ readonly DEADLOCK: {
821
+ readonly code: "DEADLOCK";
822
+ readonly name: "DBDeadlockError";
823
+ readonly message: "Database deadlock detected.";
824
+ readonly uiMessage: "The operation encountered a conflict. Please try again.";
825
+ readonly httpStatus: 409;
826
+ };
827
+ readonly NOT_FOUND: {
828
+ readonly code: "NOT_FOUND";
829
+ readonly name: "DBNotFoundError";
830
+ readonly message: "Record not found.";
831
+ readonly uiMessage: "The requested record was not found.";
832
+ readonly httpStatus: 404;
833
+ };
834
+ readonly UNKNOWN: {
835
+ readonly code: "UNKNOWN";
836
+ readonly name: "DBErrorX";
837
+ readonly message: "An unknown database error occurred.";
838
+ readonly uiMessage: "A database error occurred. Please try again later.";
839
+ };
840
+ };
841
+ /** Default to UNKNOWN when no preset specified */
842
+ static defaultPreset: string;
843
+ /** Default httpStatus for database errors (500 = server error) */
844
+ static defaults: {
845
+ httpStatus: number;
846
+ };
847
+ /**
848
+ * Transform that prefixes all codes with `DB_`.
849
+ */
850
+ static transform: ErrorXTransform<DBErrorXMetadata>;
851
+ /**
852
+ * Creates a DBErrorX from a preset key.
853
+ *
854
+ * @param presetKey - Preset key (provides autocomplete for known presets)
855
+ * @param overrides - Optional overrides for the preset values
856
+ * @returns DBErrorX instance
857
+ */
858
+ static create(presetKey?: DBErrorXPresetKey, overrides?: Partial<ErrorXOptions<DBErrorXMetadata>>): DBErrorX;
859
+ static create(overrides?: Partial<ErrorXOptions<DBErrorXMetadata>>): DBErrorX;
860
+ }
861
+
862
+ /**
863
+ * HTTP status code presets for all standard codes.
864
+ * Defined outside the class to enable type inference for preset keys.
865
+ */
866
+ declare const httpPresets: {
867
+ readonly 400: {
868
+ readonly code: "BAD_REQUEST";
869
+ readonly name: "BadRequestError";
870
+ readonly message: "Bad request.";
871
+ readonly uiMessage: "The request could not be processed. Please check your input and try again.";
624
872
  };
625
873
  readonly 401: {
626
- code: string;
627
- name: string;
628
- message: string;
629
- uiMessage: string;
630
- metadata: {
631
- status: number;
632
- };
874
+ readonly code: "UNAUTHORIZED";
875
+ readonly name: "UnauthorizedError";
876
+ readonly message: "Unauthorized.";
877
+ readonly uiMessage: "Authentication required. Please log in to continue.";
633
878
  };
634
879
  readonly 402: {
635
- code: string;
636
- name: string;
637
- message: string;
638
- uiMessage: string;
639
- metadata: {
640
- status: number;
641
- };
880
+ readonly code: "PAYMENT_REQUIRED";
881
+ readonly name: "PaymentRequiredError";
882
+ readonly message: "Payment required.";
883
+ readonly uiMessage: "Payment is required to access this resource.";
642
884
  };
643
885
  readonly 403: {
644
- code: string;
645
- name: string;
646
- message: string;
647
- uiMessage: string;
648
- metadata: {
649
- status: number;
650
- };
886
+ readonly code: "FORBIDDEN";
887
+ readonly name: "ForbiddenError";
888
+ readonly message: "Forbidden.";
889
+ readonly uiMessage: "You do not have permission to access this resource.";
651
890
  };
652
891
  readonly 404: {
653
- code: string;
654
- name: string;
655
- message: string;
656
- uiMessage: string;
657
- metadata: {
658
- status: number;
659
- };
892
+ readonly code: "NOT_FOUND";
893
+ readonly name: "NotFoundError";
894
+ readonly message: "Not found.";
895
+ readonly uiMessage: "The requested resource could not be found.";
660
896
  };
661
897
  readonly 405: {
662
- code: string;
663
- name: string;
664
- message: string;
665
- uiMessage: string;
666
- metadata: {
667
- status: number;
668
- };
898
+ readonly code: "METHOD_NOT_ALLOWED";
899
+ readonly name: "MethodNotAllowedError";
900
+ readonly message: "Method not allowed.";
901
+ readonly uiMessage: "This action is not allowed for the requested resource.";
669
902
  };
670
903
  readonly 406: {
671
- code: string;
672
- name: string;
673
- message: string;
674
- uiMessage: string;
675
- metadata: {
676
- status: number;
677
- };
904
+ readonly code: "NOT_ACCEPTABLE";
905
+ readonly name: "NotAcceptableError";
906
+ readonly message: "Not acceptable.";
907
+ readonly uiMessage: "The requested format is not supported.";
678
908
  };
679
909
  readonly 407: {
680
- code: string;
681
- name: string;
682
- message: string;
683
- uiMessage: string;
684
- metadata: {
685
- status: number;
686
- };
910
+ readonly code: "PROXY_AUTHENTICATION_REQUIRED";
911
+ readonly name: "ProxyAuthenticationRequiredError";
912
+ readonly message: "Proxy authentication required.";
913
+ readonly uiMessage: "Proxy authentication is required to access this resource.";
687
914
  };
688
915
  readonly 408: {
689
- code: string;
690
- name: string;
691
- message: string;
692
- uiMessage: string;
693
- metadata: {
694
- status: number;
695
- };
916
+ readonly code: "REQUEST_TIMEOUT";
917
+ readonly name: "RequestTimeoutError";
918
+ readonly message: "Request timeout.";
919
+ readonly uiMessage: "The request took too long to complete. Please try again.";
696
920
  };
697
921
  readonly 409: {
698
- code: string;
699
- name: string;
700
- message: string;
701
- uiMessage: string;
702
- metadata: {
703
- status: number;
704
- };
922
+ readonly code: "CONFLICT";
923
+ readonly name: "ConflictError";
924
+ readonly message: "Conflict.";
925
+ readonly uiMessage: "The request conflicts with the current state. Please refresh and try again.";
705
926
  };
706
927
  readonly 410: {
707
- code: string;
708
- name: string;
709
- message: string;
710
- uiMessage: string;
711
- metadata: {
712
- status: number;
713
- };
928
+ readonly code: "GONE";
929
+ readonly name: "GoneError";
930
+ readonly message: "Gone.";
931
+ readonly uiMessage: "This resource is no longer available.";
714
932
  };
715
933
  readonly 411: {
716
- code: string;
717
- name: string;
718
- message: string;
719
- uiMessage: string;
720
- metadata: {
721
- status: number;
722
- };
934
+ readonly code: "LENGTH_REQUIRED";
935
+ readonly name: "LengthRequiredError";
936
+ readonly message: "Length required.";
937
+ readonly uiMessage: "The request is missing required length information.";
723
938
  };
724
939
  readonly 412: {
725
- code: string;
726
- name: string;
727
- message: string;
728
- uiMessage: string;
729
- metadata: {
730
- status: number;
731
- };
940
+ readonly code: "PRECONDITION_FAILED";
941
+ readonly name: "PreconditionFailedError";
942
+ readonly message: "Precondition failed.";
943
+ readonly uiMessage: "A required condition was not met. Please try again.";
732
944
  };
733
945
  readonly 413: {
734
- code: string;
735
- name: string;
736
- message: string;
737
- uiMessage: string;
738
- metadata: {
739
- status: number;
740
- };
946
+ readonly code: "PAYLOAD_TOO_LARGE";
947
+ readonly name: "PayloadTooLargeError";
948
+ readonly message: "Payload too large.";
949
+ readonly uiMessage: "The request is too large. Please reduce the size and try again.";
741
950
  };
742
951
  readonly 414: {
743
- code: string;
744
- name: string;
745
- message: string;
746
- uiMessage: string;
747
- metadata: {
748
- status: number;
749
- };
952
+ readonly code: "URI_TOO_LONG";
953
+ readonly name: "UriTooLongError";
954
+ readonly message: "URI too long.";
955
+ readonly uiMessage: "The request URL is too long.";
750
956
  };
751
957
  readonly 415: {
752
- code: string;
753
- name: string;
754
- message: string;
755
- uiMessage: string;
756
- metadata: {
757
- status: number;
758
- };
958
+ readonly code: "UNSUPPORTED_MEDIA_TYPE";
959
+ readonly name: "UnsupportedMediaTypeError";
960
+ readonly message: "Unsupported media type.";
961
+ readonly uiMessage: "The file type is not supported.";
759
962
  };
760
963
  readonly 416: {
761
- code: string;
762
- name: string;
763
- message: string;
764
- uiMessage: string;
765
- metadata: {
766
- status: number;
767
- };
964
+ readonly code: "RANGE_NOT_SATISFIABLE";
965
+ readonly name: "RangeNotSatisfiableError";
966
+ readonly message: "Range not satisfiable.";
967
+ readonly uiMessage: "The requested range cannot be satisfied.";
768
968
  };
769
969
  readonly 417: {
770
- code: string;
771
- name: string;
772
- message: string;
773
- uiMessage: string;
774
- metadata: {
775
- status: number;
776
- };
970
+ readonly code: "EXPECTATION_FAILED";
971
+ readonly name: "ExpectationFailedError";
972
+ readonly message: "Expectation failed.";
973
+ readonly uiMessage: "The server cannot meet the requirements of the request.";
777
974
  };
778
975
  readonly 418: {
779
- code: string;
780
- name: string;
781
- message: string;
782
- uiMessage: string;
783
- metadata: {
784
- status: number;
785
- };
976
+ readonly code: "IM_A_TEAPOT";
977
+ readonly name: "ImATeapotError";
978
+ readonly message: "I'm a teapot.";
979
+ readonly uiMessage: "I'm a teapot and cannot brew coffee.";
786
980
  };
787
981
  readonly 422: {
788
- code: string;
789
- name: string;
790
- message: string;
791
- uiMessage: string;
792
- metadata: {
793
- status: number;
794
- };
982
+ readonly code: "UNPROCESSABLE_ENTITY";
983
+ readonly name: "UnprocessableEntityError";
984
+ readonly message: "Unprocessable entity.";
985
+ readonly uiMessage: "The request contains invalid data. Please check your input.";
795
986
  };
796
987
  readonly 423: {
797
- code: string;
798
- name: string;
799
- message: string;
800
- uiMessage: string;
801
- metadata: {
802
- status: number;
803
- };
988
+ readonly code: "LOCKED";
989
+ readonly name: "LockedError";
990
+ readonly message: "Locked.";
991
+ readonly uiMessage: "This resource is locked and cannot be modified.";
804
992
  };
805
993
  readonly 424: {
806
- code: string;
807
- name: string;
808
- message: string;
809
- uiMessage: string;
810
- metadata: {
811
- status: number;
812
- };
994
+ readonly code: "FAILED_DEPENDENCY";
995
+ readonly name: "FailedDependencyError";
996
+ readonly message: "Failed dependency.";
997
+ readonly uiMessage: "The request failed due to a dependency error.";
813
998
  };
814
999
  readonly 425: {
815
- code: string;
816
- name: string;
817
- message: string;
818
- uiMessage: string;
819
- metadata: {
820
- status: number;
821
- };
1000
+ readonly code: "TOO_EARLY";
1001
+ readonly name: "TooEarlyError";
1002
+ readonly message: "Too early.";
1003
+ readonly uiMessage: "The request was sent too early. Please try again later.";
822
1004
  };
823
1005
  readonly 426: {
824
- code: string;
825
- name: string;
826
- message: string;
827
- uiMessage: string;
828
- metadata: {
829
- status: number;
830
- };
1006
+ readonly code: "UPGRADE_REQUIRED";
1007
+ readonly name: "UpgradeRequiredError";
1008
+ readonly message: "Upgrade required.";
1009
+ readonly uiMessage: "Please upgrade to continue using this service.";
831
1010
  };
832
1011
  readonly 428: {
833
- code: string;
834
- name: string;
835
- message: string;
836
- uiMessage: string;
837
- metadata: {
838
- status: number;
839
- };
1012
+ readonly code: "PRECONDITION_REQUIRED";
1013
+ readonly name: "PreconditionRequiredError";
1014
+ readonly message: "Precondition required.";
1015
+ readonly uiMessage: "Required conditions are missing from the request.";
840
1016
  };
841
1017
  readonly 429: {
842
- code: string;
843
- name: string;
844
- message: string;
845
- uiMessage: string;
846
- metadata: {
847
- status: number;
848
- };
1018
+ readonly code: "TOO_MANY_REQUESTS";
1019
+ readonly name: "TooManyRequestsError";
1020
+ readonly message: "Too many requests.";
1021
+ readonly uiMessage: "You have made too many requests. Please wait and try again.";
849
1022
  };
850
1023
  readonly 431: {
851
- code: string;
852
- name: string;
853
- message: string;
854
- uiMessage: string;
855
- metadata: {
856
- status: number;
857
- };
1024
+ readonly code: "REQUEST_HEADER_FIELDS_TOO_LARGE";
1025
+ readonly name: "RequestHeaderFieldsTooLargeError";
1026
+ readonly message: "Request header fields too large.";
1027
+ readonly uiMessage: "The request headers are too large.";
858
1028
  };
859
1029
  readonly 451: {
860
- code: string;
861
- name: string;
862
- message: string;
863
- uiMessage: string;
864
- metadata: {
865
- status: number;
866
- };
1030
+ readonly code: "UNAVAILABLE_FOR_LEGAL_REASONS";
1031
+ readonly name: "UnavailableForLegalReasonsError";
1032
+ readonly message: "Unavailable for legal reasons.";
1033
+ readonly uiMessage: "This content is unavailable for legal reasons.";
867
1034
  };
868
1035
  readonly 500: {
869
- code: string;
870
- name: string;
871
- message: string;
872
- uiMessage: string;
873
- metadata: {
874
- status: number;
875
- };
1036
+ readonly code: "INTERNAL_SERVER_ERROR";
1037
+ readonly name: "InternalServerError";
1038
+ readonly message: "Internal server error.";
1039
+ readonly uiMessage: "An unexpected error occurred. Please try again later.";
876
1040
  };
877
1041
  readonly 501: {
878
- code: string;
879
- name: string;
880
- message: string;
881
- uiMessage: string;
882
- metadata: {
883
- status: number;
884
- };
1042
+ readonly code: "NOT_IMPLEMENTED";
1043
+ readonly name: "NotImplementedError";
1044
+ readonly message: "Not implemented.";
1045
+ readonly uiMessage: "This feature is not yet available.";
885
1046
  };
886
1047
  readonly 502: {
887
- code: string;
888
- name: string;
889
- message: string;
890
- uiMessage: string;
891
- metadata: {
892
- status: number;
893
- };
1048
+ readonly code: "BAD_GATEWAY";
1049
+ readonly name: "BadGatewayError";
1050
+ readonly message: "Bad gateway.";
1051
+ readonly uiMessage: "Unable to connect to the server. Please try again later.";
894
1052
  };
895
1053
  readonly 503: {
896
- code: string;
897
- name: string;
898
- message: string;
899
- uiMessage: string;
900
- metadata: {
901
- status: number;
902
- };
1054
+ readonly code: "SERVICE_UNAVAILABLE";
1055
+ readonly name: "ServiceUnavailableError";
1056
+ readonly message: "Service unavailable.";
1057
+ readonly uiMessage: "The service is temporarily unavailable. Please try again later.";
903
1058
  };
904
1059
  readonly 504: {
905
- code: string;
906
- name: string;
907
- message: string;
908
- uiMessage: string;
909
- metadata: {
910
- status: number;
911
- };
1060
+ readonly code: "GATEWAY_TIMEOUT";
1061
+ readonly name: "GatewayTimeoutError";
1062
+ readonly message: "Gateway timeout.";
1063
+ readonly uiMessage: "The server took too long to respond. Please try again.";
912
1064
  };
913
1065
  readonly 505: {
914
- code: string;
915
- name: string;
916
- message: string;
917
- uiMessage: string;
918
- metadata: {
919
- status: number;
920
- };
1066
+ readonly code: "HTTP_VERSION_NOT_SUPPORTED";
1067
+ readonly name: "HttpVersionNotSupportedError";
1068
+ readonly message: "HTTP version not supported.";
1069
+ readonly uiMessage: "Your browser version is not supported.";
921
1070
  };
922
1071
  readonly 506: {
923
- code: string;
924
- name: string;
925
- message: string;
926
- uiMessage: string;
927
- metadata: {
928
- status: number;
929
- };
1072
+ readonly code: "VARIANT_ALSO_NEGOTIATES";
1073
+ readonly name: "VariantAlsoNegotiatesError";
1074
+ readonly message: "Variant also negotiates.";
1075
+ readonly uiMessage: "The server has an internal configuration error.";
930
1076
  };
931
1077
  readonly 507: {
932
- code: string;
933
- name: string;
934
- message: string;
935
- uiMessage: string;
936
- metadata: {
937
- status: number;
938
- };
1078
+ readonly code: "INSUFFICIENT_STORAGE";
1079
+ readonly name: "InsufficientStorageError";
1080
+ readonly message: "Insufficient storage.";
1081
+ readonly uiMessage: "The server has insufficient storage to complete the request.";
939
1082
  };
940
1083
  readonly 508: {
941
- code: string;
942
- name: string;
943
- message: string;
944
- uiMessage: string;
945
- metadata: {
946
- status: number;
947
- };
1084
+ readonly code: "LOOP_DETECTED";
1085
+ readonly name: "LoopDetectedError";
1086
+ readonly message: "Loop detected.";
1087
+ readonly uiMessage: "The server detected an infinite loop.";
948
1088
  };
949
1089
  readonly 510: {
950
- code: string;
951
- name: string;
952
- message: string;
953
- uiMessage: string;
954
- metadata: {
955
- status: number;
956
- };
1090
+ readonly code: "NOT_EXTENDED";
1091
+ readonly name: "NotExtendedError";
1092
+ readonly message: "Not extended.";
1093
+ readonly uiMessage: "Additional extensions are required.";
957
1094
  };
958
1095
  readonly 511: {
959
- code: string;
1096
+ readonly code: "NETWORK_AUTHENTICATION_REQUIRED";
1097
+ readonly name: "NetworkAuthenticationRequiredError";
1098
+ readonly message: "Network authentication required.";
1099
+ readonly uiMessage: "Network authentication is required to access this resource.";
1100
+ };
1101
+ };
1102
+ /**
1103
+ * Valid HTTP status codes for HTTPErrorX.create()
1104
+ * Derived from the presets object. Provides autocomplete for known codes
1105
+ * while allowing any number for flexibility.
1106
+ */
1107
+ type HTTPErrorXPresetKey = keyof typeof httpPresets | ErrorXBasePresetKey;
1108
+ /**
1109
+ * Metadata type for HTTP errors.
1110
+ * Provides context about the HTTP request that failed.
1111
+ *
1112
+ * @public
1113
+ */
1114
+ type HTTPErrorXMetadata = {
1115
+ /** The endpoint/URL that was requested */
1116
+ endpoint?: string;
1117
+ /** The HTTP method used (GET, POST, etc.) */
1118
+ method?: string;
1119
+ /** Additional metadata */
1120
+ [key: string]: unknown;
1121
+ };
1122
+ /**
1123
+ * HTTP Error class with presets for all standard HTTP status codes.
1124
+ *
1125
+ * Provides a type-safe, ergonomic way to create HTTP errors with:
1126
+ * - Numeric status code presets (400, 401, 404, 500, etc.)
1127
+ * - Automatic httpStatus from preset key
1128
+ * - Full `instanceof` support
1129
+ * - Typed metadata for HTTP context
1130
+ *
1131
+ * @example
1132
+ * ```typescript
1133
+ * // Basic usage with status code
1134
+ * throw HTTPErrorX.create(404)
1135
+ * throw HTTPErrorX.create(401)
1136
+ * throw HTTPErrorX.create(500)
1137
+ *
1138
+ * // With custom message
1139
+ * throw HTTPErrorX.create(404, { message: 'User not found' })
1140
+ *
1141
+ * // With metadata
1142
+ * throw HTTPErrorX.create(401, {
1143
+ * message: 'Invalid token',
1144
+ * metadata: { endpoint: '/api/users', method: 'GET' }
1145
+ * })
1146
+ *
1147
+ * // With error chaining
1148
+ * try {
1149
+ * await fetchUser(id)
1150
+ * } catch (err) {
1151
+ * throw HTTPErrorX.create(500, { cause: err })
1152
+ * }
1153
+ *
1154
+ * // Just overrides (uses default 500)
1155
+ * throw HTTPErrorX.create({ message: 'Something went wrong' })
1156
+ *
1157
+ * // instanceof checks
1158
+ * if (error instanceof HTTPErrorX) {
1159
+ * console.log(error.httpStatus) // 404
1160
+ * }
1161
+ * ```
1162
+ *
1163
+ * @public
1164
+ */
1165
+ declare class HTTPErrorX extends ErrorX<HTTPErrorXMetadata> {
1166
+ /**
1167
+ * HTTP status code presets for all standard codes.
1168
+ * Keys are numeric status codes (400, 401, 404, 500, etc.)
1169
+ */
1170
+ static presets: {
1171
+ readonly 400: {
1172
+ readonly code: "BAD_REQUEST";
1173
+ readonly name: "BadRequestError";
1174
+ readonly message: "Bad request.";
1175
+ readonly uiMessage: "The request could not be processed. Please check your input and try again.";
1176
+ };
1177
+ readonly 401: {
1178
+ readonly code: "UNAUTHORIZED";
1179
+ readonly name: "UnauthorizedError";
1180
+ readonly message: "Unauthorized.";
1181
+ readonly uiMessage: "Authentication required. Please log in to continue.";
1182
+ };
1183
+ readonly 402: {
1184
+ readonly code: "PAYMENT_REQUIRED";
1185
+ readonly name: "PaymentRequiredError";
1186
+ readonly message: "Payment required.";
1187
+ readonly uiMessage: "Payment is required to access this resource.";
1188
+ };
1189
+ readonly 403: {
1190
+ readonly code: "FORBIDDEN";
1191
+ readonly name: "ForbiddenError";
1192
+ readonly message: "Forbidden.";
1193
+ readonly uiMessage: "You do not have permission to access this resource.";
1194
+ };
1195
+ readonly 404: {
1196
+ readonly code: "NOT_FOUND";
1197
+ readonly name: "NotFoundError";
1198
+ readonly message: "Not found.";
1199
+ readonly uiMessage: "The requested resource could not be found.";
1200
+ };
1201
+ readonly 405: {
1202
+ readonly code: "METHOD_NOT_ALLOWED";
1203
+ readonly name: "MethodNotAllowedError";
1204
+ readonly message: "Method not allowed.";
1205
+ readonly uiMessage: "This action is not allowed for the requested resource.";
1206
+ };
1207
+ readonly 406: {
1208
+ readonly code: "NOT_ACCEPTABLE";
1209
+ readonly name: "NotAcceptableError";
1210
+ readonly message: "Not acceptable.";
1211
+ readonly uiMessage: "The requested format is not supported.";
1212
+ };
1213
+ readonly 407: {
1214
+ readonly code: "PROXY_AUTHENTICATION_REQUIRED";
1215
+ readonly name: "ProxyAuthenticationRequiredError";
1216
+ readonly message: "Proxy authentication required.";
1217
+ readonly uiMessage: "Proxy authentication is required to access this resource.";
1218
+ };
1219
+ readonly 408: {
1220
+ readonly code: "REQUEST_TIMEOUT";
1221
+ readonly name: "RequestTimeoutError";
1222
+ readonly message: "Request timeout.";
1223
+ readonly uiMessage: "The request took too long to complete. Please try again.";
1224
+ };
1225
+ readonly 409: {
1226
+ readonly code: "CONFLICT";
1227
+ readonly name: "ConflictError";
1228
+ readonly message: "Conflict.";
1229
+ readonly uiMessage: "The request conflicts with the current state. Please refresh and try again.";
1230
+ };
1231
+ readonly 410: {
1232
+ readonly code: "GONE";
1233
+ readonly name: "GoneError";
1234
+ readonly message: "Gone.";
1235
+ readonly uiMessage: "This resource is no longer available.";
1236
+ };
1237
+ readonly 411: {
1238
+ readonly code: "LENGTH_REQUIRED";
1239
+ readonly name: "LengthRequiredError";
1240
+ readonly message: "Length required.";
1241
+ readonly uiMessage: "The request is missing required length information.";
1242
+ };
1243
+ readonly 412: {
1244
+ readonly code: "PRECONDITION_FAILED";
1245
+ readonly name: "PreconditionFailedError";
1246
+ readonly message: "Precondition failed.";
1247
+ readonly uiMessage: "A required condition was not met. Please try again.";
1248
+ };
1249
+ readonly 413: {
1250
+ readonly code: "PAYLOAD_TOO_LARGE";
1251
+ readonly name: "PayloadTooLargeError";
1252
+ readonly message: "Payload too large.";
1253
+ readonly uiMessage: "The request is too large. Please reduce the size and try again.";
1254
+ };
1255
+ readonly 414: {
1256
+ readonly code: "URI_TOO_LONG";
1257
+ readonly name: "UriTooLongError";
1258
+ readonly message: "URI too long.";
1259
+ readonly uiMessage: "The request URL is too long.";
1260
+ };
1261
+ readonly 415: {
1262
+ readonly code: "UNSUPPORTED_MEDIA_TYPE";
1263
+ readonly name: "UnsupportedMediaTypeError";
1264
+ readonly message: "Unsupported media type.";
1265
+ readonly uiMessage: "The file type is not supported.";
1266
+ };
1267
+ readonly 416: {
1268
+ readonly code: "RANGE_NOT_SATISFIABLE";
1269
+ readonly name: "RangeNotSatisfiableError";
1270
+ readonly message: "Range not satisfiable.";
1271
+ readonly uiMessage: "The requested range cannot be satisfied.";
1272
+ };
1273
+ readonly 417: {
1274
+ readonly code: "EXPECTATION_FAILED";
1275
+ readonly name: "ExpectationFailedError";
1276
+ readonly message: "Expectation failed.";
1277
+ readonly uiMessage: "The server cannot meet the requirements of the request.";
1278
+ };
1279
+ readonly 418: {
1280
+ readonly code: "IM_A_TEAPOT";
1281
+ readonly name: "ImATeapotError";
1282
+ readonly message: "I'm a teapot.";
1283
+ readonly uiMessage: "I'm a teapot and cannot brew coffee.";
1284
+ };
1285
+ readonly 422: {
1286
+ readonly code: "UNPROCESSABLE_ENTITY";
1287
+ readonly name: "UnprocessableEntityError";
1288
+ readonly message: "Unprocessable entity.";
1289
+ readonly uiMessage: "The request contains invalid data. Please check your input.";
1290
+ };
1291
+ readonly 423: {
1292
+ readonly code: "LOCKED";
1293
+ readonly name: "LockedError";
1294
+ readonly message: "Locked.";
1295
+ readonly uiMessage: "This resource is locked and cannot be modified.";
1296
+ };
1297
+ readonly 424: {
1298
+ readonly code: "FAILED_DEPENDENCY";
1299
+ readonly name: "FailedDependencyError";
1300
+ readonly message: "Failed dependency.";
1301
+ readonly uiMessage: "The request failed due to a dependency error.";
1302
+ };
1303
+ readonly 425: {
1304
+ readonly code: "TOO_EARLY";
1305
+ readonly name: "TooEarlyError";
1306
+ readonly message: "Too early.";
1307
+ readonly uiMessage: "The request was sent too early. Please try again later.";
1308
+ };
1309
+ readonly 426: {
1310
+ readonly code: "UPGRADE_REQUIRED";
1311
+ readonly name: "UpgradeRequiredError";
1312
+ readonly message: "Upgrade required.";
1313
+ readonly uiMessage: "Please upgrade to continue using this service.";
1314
+ };
1315
+ readonly 428: {
1316
+ readonly code: "PRECONDITION_REQUIRED";
1317
+ readonly name: "PreconditionRequiredError";
1318
+ readonly message: "Precondition required.";
1319
+ readonly uiMessage: "Required conditions are missing from the request.";
1320
+ };
1321
+ readonly 429: {
1322
+ readonly code: "TOO_MANY_REQUESTS";
1323
+ readonly name: "TooManyRequestsError";
1324
+ readonly message: "Too many requests.";
1325
+ readonly uiMessage: "You have made too many requests. Please wait and try again.";
1326
+ };
1327
+ readonly 431: {
1328
+ readonly code: "REQUEST_HEADER_FIELDS_TOO_LARGE";
1329
+ readonly name: "RequestHeaderFieldsTooLargeError";
1330
+ readonly message: "Request header fields too large.";
1331
+ readonly uiMessage: "The request headers are too large.";
1332
+ };
1333
+ readonly 451: {
1334
+ readonly code: "UNAVAILABLE_FOR_LEGAL_REASONS";
1335
+ readonly name: "UnavailableForLegalReasonsError";
1336
+ readonly message: "Unavailable for legal reasons.";
1337
+ readonly uiMessage: "This content is unavailable for legal reasons.";
1338
+ };
1339
+ readonly 500: {
1340
+ readonly code: "INTERNAL_SERVER_ERROR";
1341
+ readonly name: "InternalServerError";
1342
+ readonly message: "Internal server error.";
1343
+ readonly uiMessage: "An unexpected error occurred. Please try again later.";
1344
+ };
1345
+ readonly 501: {
1346
+ readonly code: "NOT_IMPLEMENTED";
1347
+ readonly name: "NotImplementedError";
1348
+ readonly message: "Not implemented.";
1349
+ readonly uiMessage: "This feature is not yet available.";
1350
+ };
1351
+ readonly 502: {
1352
+ readonly code: "BAD_GATEWAY";
1353
+ readonly name: "BadGatewayError";
1354
+ readonly message: "Bad gateway.";
1355
+ readonly uiMessage: "Unable to connect to the server. Please try again later.";
1356
+ };
1357
+ readonly 503: {
1358
+ readonly code: "SERVICE_UNAVAILABLE";
1359
+ readonly name: "ServiceUnavailableError";
1360
+ readonly message: "Service unavailable.";
1361
+ readonly uiMessage: "The service is temporarily unavailable. Please try again later.";
1362
+ };
1363
+ readonly 504: {
1364
+ readonly code: "GATEWAY_TIMEOUT";
1365
+ readonly name: "GatewayTimeoutError";
1366
+ readonly message: "Gateway timeout.";
1367
+ readonly uiMessage: "The server took too long to respond. Please try again.";
1368
+ };
1369
+ readonly 505: {
1370
+ readonly code: "HTTP_VERSION_NOT_SUPPORTED";
1371
+ readonly name: "HttpVersionNotSupportedError";
1372
+ readonly message: "HTTP version not supported.";
1373
+ readonly uiMessage: "Your browser version is not supported.";
1374
+ };
1375
+ readonly 506: {
1376
+ readonly code: "VARIANT_ALSO_NEGOTIATES";
1377
+ readonly name: "VariantAlsoNegotiatesError";
1378
+ readonly message: "Variant also negotiates.";
1379
+ readonly uiMessage: "The server has an internal configuration error.";
1380
+ };
1381
+ readonly 507: {
1382
+ readonly code: "INSUFFICIENT_STORAGE";
1383
+ readonly name: "InsufficientStorageError";
1384
+ readonly message: "Insufficient storage.";
1385
+ readonly uiMessage: "The server has insufficient storage to complete the request.";
1386
+ };
1387
+ readonly 508: {
1388
+ readonly code: "LOOP_DETECTED";
1389
+ readonly name: "LoopDetectedError";
1390
+ readonly message: "Loop detected.";
1391
+ readonly uiMessage: "The server detected an infinite loop.";
1392
+ };
1393
+ readonly 510: {
1394
+ readonly code: "NOT_EXTENDED";
1395
+ readonly name: "NotExtendedError";
1396
+ readonly message: "Not extended.";
1397
+ readonly uiMessage: "Additional extensions are required.";
1398
+ };
1399
+ readonly 511: {
1400
+ readonly code: "NETWORK_AUTHENTICATION_REQUIRED";
1401
+ readonly name: "NetworkAuthenticationRequiredError";
1402
+ readonly message: "Network authentication required.";
1403
+ readonly uiMessage: "Network authentication is required to access this resource.";
1404
+ };
1405
+ };
1406
+ /** Default to 500 Internal Server Error when no preset specified */
1407
+ static defaultPreset: number;
1408
+ /** Default httpStatus for all HTTPErrorXs */
1409
+ static defaults: {
1410
+ httpStatus: number;
1411
+ };
1412
+ /**
1413
+ * Transform that automatically sets httpStatus from the preset key.
1414
+ * Only sets httpStatus from presetKey if it matches a known preset.
1415
+ */
1416
+ static transform: ErrorXTransform<HTTPErrorXMetadata>;
1417
+ /**
1418
+ * Creates an HTTPErrorX from a status code preset.
1419
+ *
1420
+ * @param statusCode - HTTP status code (provides autocomplete for standard codes)
1421
+ * @param overrides - Optional overrides for the preset values
1422
+ * @returns HTTPErrorX instance
1423
+ */
1424
+ static create(statusCode?: HTTPErrorXPresetKey, overrides?: Partial<ErrorXOptions<HTTPErrorXMetadata>>): HTTPErrorX;
1425
+ static create(overrides?: Partial<ErrorXOptions<HTTPErrorXMetadata>>): HTTPErrorX;
1426
+ }
1427
+
1428
+ /**
1429
+ * Zod issue structure for type compatibility.
1430
+ * Matches the shape of Zod's ZodIssue type.
1431
+ *
1432
+ * @public
1433
+ */
1434
+ type ZodIssue = {
1435
+ code: string;
1436
+ path: (string | number)[];
1437
+ message: string;
1438
+ expected?: string;
1439
+ received?: string;
1440
+ minimum?: number;
1441
+ maximum?: number;
1442
+ inclusive?: boolean;
1443
+ exact?: boolean;
1444
+ type?: string;
1445
+ };
1446
+ /**
1447
+ * Metadata type for validation errors.
1448
+ * Designed to capture all relevant Zod validation context.
1449
+ *
1450
+ * @public
1451
+ */
1452
+ type ValidationErrorXMetadata = {
1453
+ /** The field that failed validation (dot-notation path) */
1454
+ field?: string;
1455
+ /** The full path to the field (for nested objects) */
1456
+ path?: (string | number)[];
1457
+ /** The Zod issue code (e.g., 'invalid_type', 'too_small') */
1458
+ zodCode?: string;
1459
+ /** The expected type or value */
1460
+ expected?: string;
1461
+ /** The received value (sanitized for safety) */
1462
+ received?: string;
1463
+ /** Total number of validation issues */
1464
+ issueCount?: number;
1465
+ /** All Zod issues (for multi-error handling) */
1466
+ issues?: ZodIssue[];
1467
+ };
1468
+ /**
1469
+ * Validation Error class designed for Zod integration.
1470
+ *
1471
+ * This class demonstrates how to map external library data (Zod) to ErrorX.
1472
+ * Instead of using presets, it dynamically creates errors from Zod's validation output.
1473
+ *
1474
+ * Key features:
1475
+ * - `fromZodError()` - Primary factory method for Zod integration
1476
+ * - Dynamic code mapping from Zod issue codes
1477
+ * - Full `instanceof` support for catch blocks
1478
+ * - Typed metadata capturing all Zod context
1479
+ *
1480
+ * @example
1481
+ * ```typescript
1482
+ * import { z } from 'zod'
1483
+ * import { ValidationErrorX } from 'error-x'
1484
+ *
1485
+ * const userSchema = z.object({
1486
+ * email: z.string().email(),
1487
+ * age: z.number().min(18),
1488
+ * })
1489
+ *
1490
+ * // Primary usage: fromZodError()
1491
+ * try {
1492
+ * userSchema.parse({ email: 'invalid', age: 15 })
1493
+ * } catch (err) {
1494
+ * if (err instanceof z.ZodError) {
1495
+ * throw ValidationErrorX.fromZodError(err)
1496
+ * // → code: 'VALIDATION_INVALID_STRING'
1497
+ * // → message: 'Invalid email'
1498
+ * // → metadata.field: 'email'
1499
+ * // → metadata.zodCode: 'invalid_string'
1500
+ * }
1501
+ * }
1502
+ *
1503
+ * // With overrides
1504
+ * ValidationErrorX.fromZodError(zodError, {
1505
+ * uiMessage: 'Please check your input',
1506
+ * httpStatus: 422,
1507
+ * })
1508
+ *
1509
+ * // Direct creation (without Zod)
1510
+ * ValidationErrorX.create({
1511
+ * message: 'Invalid email format',
1512
+ * code: 'INVALID_EMAIL',
1513
+ * metadata: { field: 'email' }
1514
+ * })
1515
+ *
1516
+ * // instanceof in catch blocks
1517
+ * try {
1518
+ * await processRequest(data)
1519
+ * } catch (err) {
1520
+ * if (err instanceof ValidationErrorX) {
1521
+ * return res.status(err.httpStatus).json({
1522
+ * error: err.code,
1523
+ * message: err.uiMessage,
1524
+ * field: err.metadata?.field,
1525
+ * })
1526
+ * }
1527
+ * }
1528
+ * ```
1529
+ *
1530
+ * @public
1531
+ */
1532
+ declare class ValidationErrorX extends ErrorX<ValidationErrorXMetadata> {
1533
+ /** Default httpStatus for validation errors (400 = bad request) */
1534
+ static defaults: {
1535
+ httpStatus: number;
960
1536
  name: string;
1537
+ code: string;
961
1538
  message: string;
962
1539
  uiMessage: string;
963
- metadata: {
964
- status: number;
965
- };
966
1540
  };
967
- };
1541
+ /**
1542
+ * Transform that maps Zod issue codes to VALIDATION_ prefixed codes.
1543
+ * Converts Zod's snake_case codes to SCREAMING_SNAKE_CASE.
1544
+ */
1545
+ static transform: ErrorXTransform<ValidationErrorXMetadata>;
1546
+ /**
1547
+ * Creates a ValidationErrorX from a Zod error.
1548
+ *
1549
+ * Maps Zod's error structure to ErrorX:
1550
+ * - Uses first issue's message as the error message
1551
+ * - Converts Zod issue code to ErrorX code (e.g., 'invalid_type' → 'VALIDATION_INVALID_TYPE')
1552
+ * - Captures all issues in metadata for multi-error handling
1553
+ *
1554
+ * @param zodError - The Zod error object (or any object with `issues` array)
1555
+ * @param overrides - Optional overrides for any ErrorX options
1556
+ * @returns ValidationErrorX instance
1557
+ *
1558
+ * @example
1559
+ * ```typescript
1560
+ * // Basic usage
1561
+ * try {
1562
+ * schema.parse(data)
1563
+ * } catch (err) {
1564
+ * if (err instanceof ZodError) {
1565
+ * throw ValidationErrorX.fromZodError(err)
1566
+ * }
1567
+ * }
1568
+ *
1569
+ * // With custom uiMessage
1570
+ * ValidationErrorX.fromZodError(zodError, {
1571
+ * uiMessage: 'Please fix the form errors',
1572
+ * })
1573
+ *
1574
+ * // Access all issues
1575
+ * const error = ValidationErrorX.fromZodError(zodError)
1576
+ * error.metadata?.issues?.forEach(issue => {
1577
+ * console.log(`${issue.path.join('.')}: ${issue.message}`)
1578
+ * })
1579
+ * ```
1580
+ */
1581
+ static fromZodError(zodError: {
1582
+ issues: ZodIssue[];
1583
+ }, overrides?: Partial<ErrorXOptions<ValidationErrorXMetadata>>): ValidationErrorX;
1584
+ /**
1585
+ * Creates a ValidationErrorX for a specific field.
1586
+ * Convenience method for manual validation errors.
1587
+ *
1588
+ * @param field - The field name that failed validation
1589
+ * @param message - The error message
1590
+ * @param options - Additional options
1591
+ * @returns ValidationErrorX instance
1592
+ *
1593
+ * @example
1594
+ * ```typescript
1595
+ * // Simple field error
1596
+ * throw ValidationErrorX.forField('email', 'Invalid email format')
1597
+ *
1598
+ * // With code
1599
+ * throw ValidationErrorX.forField('age', 'Must be 18 or older', {
1600
+ * code: 'TOO_YOUNG',
1601
+ * })
1602
+ * ```
1603
+ */
1604
+ static forField(field: string, message: string, options?: Partial<ErrorXOptions<ValidationErrorXMetadata>>): ValidationErrorX;
1605
+ }
968
1606
 
969
- export { ErrorX, type ErrorXCause, type ErrorXConfig, type ErrorXMetadata, type ErrorXOptions, type ErrorXSerialized, http };
1607
+ export { type DBErrorXPresetKey as DBErrorPreset, DBErrorX, type DBErrorXMetadata, ErrorX, type ErrorXCause, type ErrorXConfig, type ErrorXMetadata, type ErrorXOptions, type ErrorXSerialized, type ErrorXTransform, type ErrorXTransformContext, HTTPErrorX, type HTTPErrorXMetadata, type HTTPErrorXPresetKey as HTTPStatusCode, ValidationErrorX, type ValidationErrorXMetadata, type ZodIssue };