@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.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { deepmerge } from 'deepmerge-ts';
1
2
  import safeStringify from 'safe-stringify';
2
3
 
3
4
  // src/error.ts
@@ -10,9 +11,7 @@ var ERROR_X_OPTION_FIELDS = [
10
11
  "uiMessage",
11
12
  "cause",
12
13
  "metadata",
13
- "type",
14
- "docsUrl",
15
- "source"
14
+ "httpStatus"
16
15
  ];
17
16
 
18
17
  // src/error.ts
@@ -28,14 +27,33 @@ var ErrorX = class _ErrorX extends Error {
28
27
  metadata;
29
28
  /** Unix epoch timestamp (milliseconds) when the error was created */
30
29
  timestamp;
31
- /** Error type for categorization */
32
- type;
33
- /** Documentation URL for this specific error */
34
- docsUrl;
35
- /** Where the error originated (service name, module, component) */
36
- source;
37
- /** Original error that caused this error (preserves error chain) */
38
- cause;
30
+ /** HTTP status code associated with this error */
31
+ httpStatus;
32
+ /** Serialized non-ErrorX entity this was wrapped from (if created via ErrorX.from()) */
33
+ original;
34
+ /** Error chain timeline: [this, parent, grandparent, ...] - single source of truth */
35
+ _chain = [];
36
+ /**
37
+ * Gets the immediate parent ErrorX in the chain (if any).
38
+ * @returns The ErrorX that caused this error, or undefined if this is the root
39
+ */
40
+ get parent() {
41
+ return this._chain[1];
42
+ }
43
+ /**
44
+ * Gets the deepest ErrorX in the chain (the original root cause).
45
+ * @returns The root cause ErrorX, or undefined if chain has only this error
46
+ */
47
+ get root() {
48
+ return this._chain.length > 1 ? this._chain[this._chain.length - 1] : void 0;
49
+ }
50
+ /**
51
+ * Gets the full error chain timeline.
52
+ * @returns Array of ErrorX instances: [this, parent, grandparent, ...]
53
+ */
54
+ get chain() {
55
+ return this._chain;
56
+ }
39
57
  /**
40
58
  * Creates a new ErrorX instance with enhanced error handling capabilities.
41
59
  *
@@ -77,34 +95,26 @@ var ErrorX = class _ErrorX extends Error {
77
95
  } else if (messageOrOptions != null) {
78
96
  options = messageOrOptions;
79
97
  }
80
- const envConfig = _ErrorX.getConfig();
81
98
  const message = options.message?.trim() ? options.message : "An error occurred";
82
- const convertedCause = _ErrorX.toErrorXCause(options.cause);
83
99
  super(message);
84
- this.cause = convertedCause;
85
100
  this.name = options.name ?? _ErrorX.getDefaultName();
86
101
  this.code = options.code != null ? String(options.code) : _ErrorX.generateDefaultCode(options.name);
87
102
  this.uiMessage = options.uiMessage;
88
103
  this.metadata = options.metadata;
89
- this.type = _ErrorX.validateType(options.type);
90
104
  this.timestamp = Date.now();
91
- this.source = options.source ?? envConfig?.source;
92
- let generatedDocsUrl;
93
- if (envConfig?.docsBaseURL && envConfig?.docsMap && this.code) {
94
- const docPath = envConfig.docsMap[this.code];
95
- if (docPath) {
96
- const base = envConfig.docsBaseURL.replace(/\/+$/, "");
97
- const path = docPath.replace(/^\/+/, "");
98
- generatedDocsUrl = `${base}/${path}`;
105
+ this.httpStatus = options.httpStatus;
106
+ if (options.cause != null) {
107
+ if (options.cause instanceof _ErrorX) {
108
+ this._chain = [this, ...options.cause._chain];
109
+ } else {
110
+ const wrappedCause = _ErrorX.from(options.cause);
111
+ this._chain = [this, ...wrappedCause._chain];
99
112
  }
100
- }
101
- this.docsUrl = options.docsUrl ?? generatedDocsUrl;
102
- if (convertedCause?.stack) {
103
- this.stack = _ErrorX.preserveOriginalStackFromCause(convertedCause, this);
104
113
  } else {
105
- if (typeof Error.captureStackTrace === "function") {
106
- Error.captureStackTrace(this, this.constructor);
107
- }
114
+ this._chain = [this];
115
+ }
116
+ if (typeof Error.captureStackTrace === "function") {
117
+ Error.captureStackTrace(this, this.constructor);
108
118
  }
109
119
  this.stack = _ErrorX.cleanStack(this.stack);
110
120
  }
@@ -162,12 +172,7 @@ var ErrorX = class _ErrorX extends Error {
162
172
  * @example
163
173
  * ```typescript
164
174
  * ErrorX.configure({
165
- * source: 'my-api-service',
166
- * docsBaseURL: 'https://docs.example.com/errors',
167
- * docsMap: {
168
- * 'AUTH_FAILED': 'authentication-errors',
169
- * 'DB_ERROR': 'database-errors'
170
- * },
175
+ * cleanStack: true, // Enable stack trace cleaning
171
176
  * cleanStackDelimiter: 'app-entry-point' // Trim stack traces after this line
172
177
  * })
173
178
  * ```
@@ -195,22 +200,6 @@ var ErrorX = class _ErrorX extends Error {
195
200
  static resetConfig() {
196
201
  _ErrorX._config = null;
197
202
  }
198
- /**
199
- * Validates and normalizes the type field
200
- *
201
- * @param type - Type value to validate
202
- * @returns Validated type string or undefined if invalid/empty
203
- */
204
- static validateType(type) {
205
- if (type === void 0 || type === null) {
206
- return void 0;
207
- }
208
- const typeStr = String(type).trim();
209
- if (typeStr === "") {
210
- return void 0;
211
- }
212
- return typeStr;
213
- }
214
203
  /**
215
204
  * Validates if an object is a valid ErrorXOptions object.
216
205
  * Checks that the object only contains accepted ErrorXOptions fields.
@@ -250,21 +239,6 @@ var ErrorX = class _ErrorX extends Error {
250
239
  if (!name) return "ERROR";
251
240
  return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").toUpperCase();
252
241
  }
253
- /**
254
- * Preserves the original error's stack trace while updating the error message.
255
- * Combines the new error's message with the original error's stack trace from ErrorXCause.
256
- *
257
- * @param cause - The ErrorXCause containing the original stack to preserve
258
- * @param newError - The new error whose message to use
259
- * @returns Combined stack trace with new error message and original stack
260
- */
261
- static preserveOriginalStackFromCause(cause, newError) {
262
- if (!cause.stack) return newError.stack || "";
263
- const newErrorFirstLine = `${newError.name}: ${newError.message}`;
264
- const originalStackLines = cause.stack.split("\n");
265
- const originalStackTrace = originalStackLines.slice(1);
266
- return [newErrorFirstLine, ...originalStackTrace].join("\n");
267
- }
268
242
  /**
269
243
  * Cleans a stack trace by removing ErrorX internal method calls and optionally trimming after a delimiter.
270
244
  * This provides cleaner stack traces that focus on user code.
@@ -296,7 +270,7 @@ var ErrorX = class _ErrorX extends Error {
296
270
  "new ErrorX",
297
271
  "ErrorX.constructor",
298
272
  "ErrorX.from",
299
- "error-x/dist/",
273
+ "error-x/dist/error.js",
300
274
  "error-x/src/error.ts"
301
275
  ];
302
276
  const patterns = Array.isArray(cleanStackConfig) ? cleanStackConfig : defaultPatterns;
@@ -344,13 +318,12 @@ var ErrorX = class _ErrorX extends Error {
344
318
  name: this.name,
345
319
  code: this.code,
346
320
  uiMessage: this.uiMessage,
347
- cause: this.cause,
348
321
  metadata: { ...this.metadata ?? {}, ...additionalMetadata },
349
- type: this.type,
350
- docsUrl: this.docsUrl,
351
- source: this.source
322
+ httpStatus: this.httpStatus
352
323
  };
353
324
  const newError = new _ErrorX(options);
325
+ newError._chain = [newError, ...this._chain.slice(1)];
326
+ newError.original = this.original;
354
327
  if (this.stack) {
355
328
  newError.stack = this.stack;
356
329
  }
@@ -395,9 +368,7 @@ var ErrorX = class _ErrorX extends Error {
395
368
  let uiMessage = "";
396
369
  let cause;
397
370
  let metadata = {};
398
- let type;
399
- let href;
400
- let source;
371
+ let httpStatus;
401
372
  if (error) {
402
373
  if (typeof error === "string") {
403
374
  message = error;
@@ -419,26 +390,16 @@ var ErrorX = class _ErrorX extends Error {
419
390
  if ("code" in error && error.code) code = String(error.code);
420
391
  if ("uiMessage" in error && error.uiMessage) uiMessage = String(error.uiMessage);
421
392
  else if ("userMessage" in error && error.userMessage) uiMessage = String(error.userMessage);
422
- if ("type" in error && error.type) {
423
- type = _ErrorX.validateType(String(error.type));
424
- }
425
- if ("docsUrl" in error && error.docsUrl) {
426
- href = String(error.docsUrl);
427
- } else if ("href" in error && error.href) {
428
- href = String(error.href);
429
- } else if ("documentationUrl" in error && error.documentationUrl) {
430
- href = String(error.documentationUrl);
431
- }
432
- if ("source" in error && error.source) {
433
- source = String(error.source);
434
- } else if ("service" in error && error.service) {
435
- source = String(error.service);
436
- } else if ("component" in error && error.component) {
437
- source = String(error.component);
438
- }
439
393
  if ("metadata" in error && typeof error.metadata === "object" && error.metadata !== null) {
440
394
  metadata = error.metadata;
441
395
  }
396
+ if ("httpStatus" in error && typeof error.httpStatus === "number") {
397
+ httpStatus = error.httpStatus;
398
+ } else if ("status" in error && typeof error.status === "number") {
399
+ httpStatus = error.status;
400
+ } else if ("statusCode" in error && typeof error.statusCode === "number") {
401
+ httpStatus = error.statusCode;
402
+ }
442
403
  }
443
404
  }
444
405
  const options = {
@@ -449,15 +410,40 @@ var ErrorX = class _ErrorX extends Error {
449
410
  if (uiMessage) options.uiMessage = uiMessage;
450
411
  if (cause) options.cause = cause;
451
412
  if (Object.keys(metadata).length > 0) options.metadata = metadata;
452
- if (type) options.type = type;
453
- if (href) options.docsUrl = href;
454
- if (source) options.source = source;
413
+ if (httpStatus !== void 0) options.httpStatus = httpStatus;
455
414
  return options;
456
415
  }
457
- static from(error) {
458
- if (error instanceof _ErrorX) return error;
459
- const options = _ErrorX.convertUnknownToOptions(error);
460
- return new _ErrorX(options);
416
+ static from(payload, overrides) {
417
+ if (payload instanceof _ErrorX) {
418
+ if (overrides && Object.keys(overrides).length > 0) {
419
+ const mergedOptions = {
420
+ message: overrides.message ?? payload.message,
421
+ name: overrides.name ?? payload.name,
422
+ code: overrides.code ?? payload.code,
423
+ uiMessage: overrides.uiMessage ?? payload.uiMessage,
424
+ httpStatus: overrides.httpStatus ?? payload.httpStatus,
425
+ metadata: overrides.metadata ? deepmerge(payload.metadata ?? {}, overrides.metadata) : payload.metadata
426
+ };
427
+ const newError = new _ErrorX(mergedOptions);
428
+ newError.original = payload.original;
429
+ newError._chain = [newError];
430
+ return newError;
431
+ }
432
+ return payload;
433
+ }
434
+ const extractedOptions = _ErrorX.convertUnknownToOptions(payload);
435
+ const finalOptions = overrides ? {
436
+ ...extractedOptions,
437
+ ...overrides,
438
+ metadata: extractedOptions.metadata || overrides.metadata ? deepmerge(extractedOptions.metadata ?? {}, overrides.metadata ?? {}) : void 0
439
+ } : extractedOptions;
440
+ const error = new _ErrorX(finalOptions);
441
+ error.original = _ErrorX.toErrorXCause(payload);
442
+ if (payload instanceof Error && payload.stack) {
443
+ error.stack = payload.stack;
444
+ }
445
+ error._chain = [error];
446
+ return error;
461
447
  }
462
448
  /**
463
449
  * Converts the ErrorX instance to a detailed string representation.
@@ -527,20 +513,28 @@ ${this.stack}`;
527
513
  metadata: safeMetadata,
528
514
  timestamp: this.timestamp
529
515
  };
530
- if (this.type !== void 0) {
531
- serialized.type = this.type;
532
- }
533
- if (this.docsUrl !== void 0) {
534
- serialized.docsUrl = this.docsUrl;
535
- }
536
- if (this.source !== void 0) {
537
- serialized.source = this.source;
516
+ if (this.httpStatus !== void 0) {
517
+ serialized.httpStatus = this.httpStatus;
538
518
  }
539
519
  if (this.stack) {
540
520
  serialized.stack = this.stack;
541
521
  }
542
- if (this.cause) {
543
- serialized.cause = this.cause;
522
+ if (this.original) {
523
+ serialized.original = this.original;
524
+ }
525
+ if (this._chain.length > 1) {
526
+ serialized.chain = this._chain.map((err) => {
527
+ const causeEntry = {
528
+ message: err.message
529
+ };
530
+ if (err.name) {
531
+ causeEntry.name = err.name;
532
+ }
533
+ if (err.stack) {
534
+ causeEntry.stack = err.stack;
535
+ }
536
+ return causeEntry;
537
+ });
544
538
  }
545
539
  return serialized;
546
540
  }
@@ -572,10 +566,7 @@ ${this.stack}`;
572
566
  name: serialized.name,
573
567
  code: serialized.code,
574
568
  uiMessage: serialized.uiMessage,
575
- type: serialized.type,
576
- docsUrl: serialized.docsUrl,
577
- source: serialized.source,
578
- cause: serialized.cause
569
+ httpStatus: serialized.httpStatus
579
570
  };
580
571
  if (serialized.metadata !== void 0) {
581
572
  options.metadata = serialized.metadata;
@@ -585,289 +576,626 @@ ${this.stack}`;
585
576
  error.stack = serialized.stack;
586
577
  }
587
578
  error.timestamp = serialized.timestamp;
579
+ if (serialized.original) {
580
+ error.original = serialized.original;
581
+ }
582
+ if (serialized.chain && serialized.chain.length > 0) {
583
+ const chainErrors = [error];
584
+ for (let i = 1; i < serialized.chain.length; i++) {
585
+ const causeData = serialized.chain[i];
586
+ if (!causeData) continue;
587
+ const chainErrorOptions = {
588
+ message: causeData.message
589
+ };
590
+ if (causeData.name) {
591
+ chainErrorOptions.name = causeData.name;
592
+ }
593
+ const chainError = new _ErrorX(chainErrorOptions);
594
+ if (causeData.stack) {
595
+ chainError.stack = causeData.stack;
596
+ }
597
+ chainErrors.push(chainError);
598
+ }
599
+ error._chain = chainErrors;
600
+ }
588
601
  return error;
589
602
  }
603
+ /**
604
+ * Creates a new instance of this error class using optional presets and overrides.
605
+ * This is a factory method that supports preset-based error creation with
606
+ * full TypeScript autocomplete for preset keys.
607
+ *
608
+ * Define static properties on your subclass to customize behavior:
609
+ * - `presets`: Record of preset configurations keyed by identifier
610
+ * - `defaultPreset`: Key of preset to use as fallback
611
+ * - `defaults`: Default values for all errors of this class
612
+ * - `transform`: Function to transform options before instantiation
613
+ *
614
+ * Supported call signatures:
615
+ * - `create()` - uses defaultPreset
616
+ * - `create(presetKey)` - uses specified preset
617
+ * - `create(presetKey, overrides)` - preset with overrides
618
+ * - `create(overrides)` - just overrides, uses defaultPreset
619
+ *
620
+ * @param presetKeyOrOverrides - Preset key (string/number) or overrides object
621
+ * @param overrides - Optional overrides when first arg is preset key
622
+ * @returns New instance of this error class
623
+ *
624
+ * @example
625
+ * ```typescript
626
+ * class DBError extends ErrorX<{ query?: string }> {
627
+ * static presets = {
628
+ * 9333: { message: 'Connection timeout', code: 'TIMEOUT' },
629
+ * CONN_REFUSED: { message: 'Connection refused', code: 'CONN_REFUSED' },
630
+ * GENERIC: { message: 'A database error occurred', code: 'ERROR' },
631
+ * }
632
+ * static defaultPreset = 'GENERIC'
633
+ * static defaults = { httpStatus: 500 }
634
+ * static transform = (opts, ctx) => ({
635
+ * ...opts,
636
+ * code: `DB_${opts.code}`,
637
+ * })
638
+ * }
639
+ *
640
+ * DBError.create() // uses defaultPreset
641
+ * DBError.create(9333) // uses preset 9333
642
+ * DBError.create('CONN_REFUSED') // uses preset CONN_REFUSED
643
+ * DBError.create(9333, { message: 'Custom' }) // preset + overrides
644
+ * DBError.create({ message: 'Custom' }) // just overrides
645
+ * ```
646
+ */
647
+ static create(presetKeyOrOverrides, overrides) {
648
+ let presetKey;
649
+ let finalOverrides;
650
+ if (typeof presetKeyOrOverrides === "object" && presetKeyOrOverrides !== null) {
651
+ presetKey = void 0;
652
+ finalOverrides = presetKeyOrOverrides;
653
+ } else {
654
+ presetKey = presetKeyOrOverrides;
655
+ finalOverrides = overrides;
656
+ }
657
+ const ctor = this;
658
+ const presets = ctor.presets ?? {};
659
+ const defaultPreset = ctor.defaultPreset;
660
+ const defaults = ctor.defaults ?? {};
661
+ const transform = ctor.transform;
662
+ let resolvedPreset = {};
663
+ if (presetKey !== void 0) {
664
+ if (presetKey in presets) {
665
+ resolvedPreset = presets[presetKey] ?? {};
666
+ } else if (defaultPreset !== void 0 && defaultPreset in presets) {
667
+ resolvedPreset = presets[defaultPreset] ?? {};
668
+ }
669
+ } else if (defaultPreset !== void 0 && defaultPreset in presets) {
670
+ resolvedPreset = presets[defaultPreset] ?? {};
671
+ }
672
+ const mergedOptions = deepmerge(
673
+ defaults,
674
+ resolvedPreset,
675
+ finalOverrides ?? {}
676
+ );
677
+ const transformContext = { presetKey };
678
+ const finalOptions = transform ? transform(mergedOptions, transformContext) : mergedOptions;
679
+ return new this(finalOptions);
680
+ }
681
+ };
682
+
683
+ // src/presets/db-error.ts
684
+ var dbPresets = {
685
+ // Connection errors
686
+ CONNECTION_FAILED: {
687
+ code: "CONNECTION_FAILED",
688
+ name: "DBConnectionError",
689
+ message: "Failed to connect to database.",
690
+ uiMessage: "Unable to connect to the database. Please try again later."
691
+ },
692
+ CONNECTION_TIMEOUT: {
693
+ code: "CONNECTION_TIMEOUT",
694
+ name: "DBConnectionTimeoutError",
695
+ message: "Database connection timed out.",
696
+ uiMessage: "The database connection timed out. Please try again."
697
+ },
698
+ CONNECTION_REFUSED: {
699
+ code: "CONNECTION_REFUSED",
700
+ name: "DBConnectionRefusedError",
701
+ message: "Database connection refused.",
702
+ uiMessage: "Unable to connect to the database. Please try again later."
703
+ },
704
+ CONNECTION_LOST: {
705
+ code: "CONNECTION_LOST",
706
+ name: "DBConnectionLostError",
707
+ message: "Database connection lost.",
708
+ uiMessage: "The database connection was lost. Please try again."
709
+ },
710
+ // Query errors
711
+ QUERY_FAILED: {
712
+ code: "QUERY_FAILED",
713
+ name: "DBQueryError",
714
+ message: "Database query failed.",
715
+ uiMessage: "The database operation failed. Please try again."
716
+ },
717
+ QUERY_TIMEOUT: {
718
+ code: "QUERY_TIMEOUT",
719
+ name: "DBQueryTimeoutError",
720
+ message: "Database query timed out.",
721
+ uiMessage: "The database operation took too long. Please try again."
722
+ },
723
+ SYNTAX_ERROR: {
724
+ code: "SYNTAX_ERROR",
725
+ name: "DBSyntaxError",
726
+ message: "Invalid query syntax.",
727
+ uiMessage: "An internal error occurred. Please contact support."
728
+ },
729
+ // Constraint errors
730
+ UNIQUE_VIOLATION: {
731
+ code: "UNIQUE_VIOLATION",
732
+ name: "DBUniqueViolationError",
733
+ message: "Unique constraint violation.",
734
+ uiMessage: "This record already exists.",
735
+ httpStatus: 409
736
+ },
737
+ FOREIGN_KEY_VIOLATION: {
738
+ code: "FOREIGN_KEY_VIOLATION",
739
+ name: "DBForeignKeyError",
740
+ message: "Foreign key constraint violation.",
741
+ uiMessage: "This operation references a record that does not exist.",
742
+ httpStatus: 400
743
+ },
744
+ NOT_NULL_VIOLATION: {
745
+ code: "NOT_NULL_VIOLATION",
746
+ name: "DBNotNullError",
747
+ message: "Not null constraint violation.",
748
+ uiMessage: "A required field is missing.",
749
+ httpStatus: 400
750
+ },
751
+ CHECK_VIOLATION: {
752
+ code: "CHECK_VIOLATION",
753
+ name: "DBCheckViolationError",
754
+ message: "Check constraint violation.",
755
+ uiMessage: "The provided data is invalid.",
756
+ httpStatus: 400
757
+ },
758
+ // Transaction errors
759
+ TRANSACTION_FAILED: {
760
+ code: "TRANSACTION_FAILED",
761
+ name: "DBTransactionError",
762
+ message: "Database transaction failed.",
763
+ uiMessage: "The operation failed. Please try again."
764
+ },
765
+ DEADLOCK: {
766
+ code: "DEADLOCK",
767
+ name: "DBDeadlockError",
768
+ message: "Database deadlock detected.",
769
+ uiMessage: "The operation encountered a conflict. Please try again.",
770
+ httpStatus: 409
771
+ },
772
+ // Record errors
773
+ NOT_FOUND: {
774
+ code: "NOT_FOUND",
775
+ name: "DBNotFoundError",
776
+ message: "Record not found.",
777
+ uiMessage: "The requested record was not found.",
778
+ httpStatus: 404
779
+ },
780
+ // Generic
781
+ UNKNOWN: {
782
+ code: "UNKNOWN",
783
+ name: "DBErrorX",
784
+ message: "An unknown database error occurred.",
785
+ uiMessage: "A database error occurred. Please try again later."
786
+ }
787
+ };
788
+ var DBErrorX = class _DBErrorX extends ErrorX {
789
+ /**
790
+ * Database error presets for common scenarios.
791
+ */
792
+ static presets = dbPresets;
793
+ /** Default to UNKNOWN when no preset specified */
794
+ static defaultPreset = "UNKNOWN";
795
+ /** Default httpStatus for database errors (500 = server error) */
796
+ static defaults = { httpStatus: 500 };
797
+ /**
798
+ * Transform that prefixes all codes with `DB_`.
799
+ */
800
+ static transform = (opts, _ctx) => {
801
+ const code = String(opts.code ?? "UNKNOWN");
802
+ return {
803
+ ...opts,
804
+ code: code.startsWith("DB_") ? code : `DB_${code}`
805
+ };
806
+ };
807
+ static create(presetKeyOrOverrides, overrides) {
808
+ return ErrorX.create.call(
809
+ _DBErrorX,
810
+ presetKeyOrOverrides,
811
+ overrides
812
+ );
813
+ }
590
814
  };
591
815
 
592
- // src/presets.ts
593
- var http = {
816
+ // src/presets/http-error.ts
817
+ var httpPresets = {
594
818
  // 4xx Client Errors
595
819
  400: {
596
820
  code: "BAD_REQUEST",
597
- name: "Bad Request Error",
821
+ name: "BadRequestError",
598
822
  message: "Bad request.",
599
- uiMessage: "The request could not be processed. Please check your input and try again.",
600
- metadata: { status: 400 }
823
+ uiMessage: "The request could not be processed. Please check your input and try again."
601
824
  },
602
825
  401: {
603
826
  code: "UNAUTHORIZED",
604
- name: "Unauthorized Error",
827
+ name: "UnauthorizedError",
605
828
  message: "Unauthorized.",
606
- uiMessage: "Authentication required. Please log in to continue.",
607
- metadata: { status: 401 }
829
+ uiMessage: "Authentication required. Please log in to continue."
608
830
  },
609
831
  402: {
610
832
  code: "PAYMENT_REQUIRED",
611
- name: "Payment Required Error",
833
+ name: "PaymentRequiredError",
612
834
  message: "Payment required.",
613
- uiMessage: "Payment is required to access this resource.",
614
- metadata: { status: 402 }
835
+ uiMessage: "Payment is required to access this resource."
615
836
  },
616
837
  403: {
617
838
  code: "FORBIDDEN",
618
- name: "Forbidden Error",
839
+ name: "ForbiddenError",
619
840
  message: "Forbidden.",
620
- uiMessage: "You do not have permission to access this resource.",
621
- metadata: { status: 403 }
841
+ uiMessage: "You do not have permission to access this resource."
622
842
  },
623
843
  404: {
624
844
  code: "NOT_FOUND",
625
- name: "Not Found Error",
845
+ name: "NotFoundError",
626
846
  message: "Not found.",
627
- uiMessage: "The requested resource could not be found.",
628
- metadata: { status: 404 }
847
+ uiMessage: "The requested resource could not be found."
629
848
  },
630
849
  405: {
631
850
  code: "METHOD_NOT_ALLOWED",
632
- name: "Method Not Allowed Error",
851
+ name: "MethodNotAllowedError",
633
852
  message: "Method not allowed.",
634
- uiMessage: "This action is not allowed for the requested resource.",
635
- metadata: { status: 405 }
853
+ uiMessage: "This action is not allowed for the requested resource."
636
854
  },
637
855
  406: {
638
856
  code: "NOT_ACCEPTABLE",
639
- name: "Not Acceptable Error",
857
+ name: "NotAcceptableError",
640
858
  message: "Not acceptable.",
641
- uiMessage: "The requested format is not supported.",
642
- metadata: { status: 406 }
859
+ uiMessage: "The requested format is not supported."
643
860
  },
644
861
  407: {
645
862
  code: "PROXY_AUTHENTICATION_REQUIRED",
646
- name: "Proxy Authentication Required Error",
863
+ name: "ProxyAuthenticationRequiredError",
647
864
  message: "Proxy authentication required.",
648
- uiMessage: "Proxy authentication is required to access this resource.",
649
- metadata: { status: 407 }
865
+ uiMessage: "Proxy authentication is required to access this resource."
650
866
  },
651
867
  408: {
652
868
  code: "REQUEST_TIMEOUT",
653
- name: "Request Timeout Error",
869
+ name: "RequestTimeoutError",
654
870
  message: "Request timeout.",
655
- uiMessage: "The request took too long to complete. Please try again.",
656
- metadata: { status: 408 }
871
+ uiMessage: "The request took too long to complete. Please try again."
657
872
  },
658
873
  409: {
659
874
  code: "CONFLICT",
660
- name: "Conflict Error",
875
+ name: "ConflictError",
661
876
  message: "Conflict.",
662
- uiMessage: "The request conflicts with the current state. Please refresh and try again.",
663
- metadata: { status: 409 }
877
+ uiMessage: "The request conflicts with the current state. Please refresh and try again."
664
878
  },
665
879
  410: {
666
880
  code: "GONE",
667
- name: "Gone Error",
881
+ name: "GoneError",
668
882
  message: "Gone.",
669
- uiMessage: "This resource is no longer available.",
670
- metadata: { status: 410 }
883
+ uiMessage: "This resource is no longer available."
671
884
  },
672
885
  411: {
673
886
  code: "LENGTH_REQUIRED",
674
- name: "Length Required Error",
887
+ name: "LengthRequiredError",
675
888
  message: "Length required.",
676
- uiMessage: "The request is missing required length information.",
677
- metadata: { status: 411 }
889
+ uiMessage: "The request is missing required length information."
678
890
  },
679
891
  412: {
680
892
  code: "PRECONDITION_FAILED",
681
- name: "Precondition Failed Error",
893
+ name: "PreconditionFailedError",
682
894
  message: "Precondition failed.",
683
- uiMessage: "A required condition was not met. Please try again.",
684
- metadata: { status: 412 }
895
+ uiMessage: "A required condition was not met. Please try again."
685
896
  },
686
897
  413: {
687
898
  code: "PAYLOAD_TOO_LARGE",
688
- name: "Payload Too Large Error",
899
+ name: "PayloadTooLargeError",
689
900
  message: "Payload too large.",
690
- uiMessage: "The request is too large. Please reduce the size and try again.",
691
- metadata: { status: 413 }
901
+ uiMessage: "The request is too large. Please reduce the size and try again."
692
902
  },
693
903
  414: {
694
904
  code: "URI_TOO_LONG",
695
- name: "URI Too Long Error",
905
+ name: "UriTooLongError",
696
906
  message: "URI too long.",
697
- uiMessage: "The request URL is too long.",
698
- metadata: { status: 414 }
907
+ uiMessage: "The request URL is too long."
699
908
  },
700
909
  415: {
701
910
  code: "UNSUPPORTED_MEDIA_TYPE",
702
- name: "Unsupported Media Type Error",
911
+ name: "UnsupportedMediaTypeError",
703
912
  message: "Unsupported media type.",
704
- uiMessage: "The file type is not supported.",
705
- metadata: { status: 415 }
913
+ uiMessage: "The file type is not supported."
706
914
  },
707
915
  416: {
708
916
  code: "RANGE_NOT_SATISFIABLE",
709
- name: "Range Not Satisfiable Error",
917
+ name: "RangeNotSatisfiableError",
710
918
  message: "Range not satisfiable.",
711
- uiMessage: "The requested range cannot be satisfied.",
712
- metadata: { status: 416 }
919
+ uiMessage: "The requested range cannot be satisfied."
713
920
  },
714
921
  417: {
715
922
  code: "EXPECTATION_FAILED",
716
- name: "Expectation Failed Error",
923
+ name: "ExpectationFailedError",
717
924
  message: "Expectation failed.",
718
- uiMessage: "The server cannot meet the requirements of the request.",
719
- metadata: { status: 417 }
925
+ uiMessage: "The server cannot meet the requirements of the request."
720
926
  },
721
927
  418: {
722
928
  code: "IM_A_TEAPOT",
723
- name: "Im A Teapot Error",
929
+ name: "ImATeapotError",
724
930
  message: "I'm a teapot.",
725
- uiMessage: "I'm a teapot and cannot brew coffee.",
726
- metadata: { status: 418 }
931
+ uiMessage: "I'm a teapot and cannot brew coffee."
727
932
  },
728
933
  422: {
729
934
  code: "UNPROCESSABLE_ENTITY",
730
- name: "Unprocessable Entity Error",
935
+ name: "UnprocessableEntityError",
731
936
  message: "Unprocessable entity.",
732
- uiMessage: "The request contains invalid data. Please check your input.",
733
- metadata: { status: 422 }
937
+ uiMessage: "The request contains invalid data. Please check your input."
734
938
  },
735
939
  423: {
736
940
  code: "LOCKED",
737
- name: "Locked Error",
941
+ name: "LockedError",
738
942
  message: "Locked.",
739
- uiMessage: "This resource is locked and cannot be modified.",
740
- metadata: { status: 423 }
943
+ uiMessage: "This resource is locked and cannot be modified."
741
944
  },
742
945
  424: {
743
946
  code: "FAILED_DEPENDENCY",
744
- name: "Failed Dependency Error",
947
+ name: "FailedDependencyError",
745
948
  message: "Failed dependency.",
746
- uiMessage: "The request failed due to a dependency error.",
747
- metadata: { status: 424 }
949
+ uiMessage: "The request failed due to a dependency error."
748
950
  },
749
951
  425: {
750
952
  code: "TOO_EARLY",
751
- name: "Too Early Error",
953
+ name: "TooEarlyError",
752
954
  message: "Too early.",
753
- uiMessage: "The request was sent too early. Please try again later.",
754
- metadata: { status: 425 }
955
+ uiMessage: "The request was sent too early. Please try again later."
755
956
  },
756
957
  426: {
757
958
  code: "UPGRADE_REQUIRED",
758
- name: "Upgrade Required Error",
959
+ name: "UpgradeRequiredError",
759
960
  message: "Upgrade required.",
760
- uiMessage: "Please upgrade to continue using this service.",
761
- metadata: { status: 426 }
961
+ uiMessage: "Please upgrade to continue using this service."
762
962
  },
763
963
  428: {
764
964
  code: "PRECONDITION_REQUIRED",
765
- name: "Precondition Required Error",
965
+ name: "PreconditionRequiredError",
766
966
  message: "Precondition required.",
767
- uiMessage: "Required conditions are missing from the request.",
768
- metadata: { status: 428 }
967
+ uiMessage: "Required conditions are missing from the request."
769
968
  },
770
969
  429: {
771
970
  code: "TOO_MANY_REQUESTS",
772
- name: "Too Many Requests Error",
971
+ name: "TooManyRequestsError",
773
972
  message: "Too many requests.",
774
- uiMessage: "You have made too many requests. Please wait and try again.",
775
- metadata: { status: 429 }
973
+ uiMessage: "You have made too many requests. Please wait and try again."
776
974
  },
777
975
  431: {
778
976
  code: "REQUEST_HEADER_FIELDS_TOO_LARGE",
779
- name: "Request Header Fields Too Large Error",
977
+ name: "RequestHeaderFieldsTooLargeError",
780
978
  message: "Request header fields too large.",
781
- uiMessage: "The request headers are too large.",
782
- metadata: { status: 431 }
979
+ uiMessage: "The request headers are too large."
783
980
  },
784
981
  451: {
785
982
  code: "UNAVAILABLE_FOR_LEGAL_REASONS",
786
- name: "Unavailable For Legal Reasons Error",
983
+ name: "UnavailableForLegalReasonsError",
787
984
  message: "Unavailable for legal reasons.",
788
- uiMessage: "This content is unavailable for legal reasons.",
789
- metadata: { status: 451 }
985
+ uiMessage: "This content is unavailable for legal reasons."
790
986
  },
791
987
  // 5xx Server Errors
792
988
  500: {
793
989
  code: "INTERNAL_SERVER_ERROR",
794
- name: "Internal Server Error",
990
+ name: "InternalServerError",
795
991
  message: "Internal server error.",
796
- uiMessage: "An unexpected error occurred. Please try again later.",
797
- metadata: { status: 500 }
992
+ uiMessage: "An unexpected error occurred. Please try again later."
798
993
  },
799
994
  501: {
800
995
  code: "NOT_IMPLEMENTED",
801
- name: "Not Implemented Error",
996
+ name: "NotImplementedError",
802
997
  message: "Not implemented.",
803
- uiMessage: "This feature is not yet available.",
804
- metadata: { status: 501 }
998
+ uiMessage: "This feature is not yet available."
805
999
  },
806
1000
  502: {
807
1001
  code: "BAD_GATEWAY",
808
- name: "Bad Gateway Error",
1002
+ name: "BadGatewayError",
809
1003
  message: "Bad gateway.",
810
- uiMessage: "Unable to connect to the server. Please try again later.",
811
- metadata: { status: 502 }
1004
+ uiMessage: "Unable to connect to the server. Please try again later."
812
1005
  },
813
1006
  503: {
814
1007
  code: "SERVICE_UNAVAILABLE",
815
- name: "Service Unavailable Error",
1008
+ name: "ServiceUnavailableError",
816
1009
  message: "Service unavailable.",
817
- uiMessage: "The service is temporarily unavailable. Please try again later.",
818
- metadata: { status: 503 }
1010
+ uiMessage: "The service is temporarily unavailable. Please try again later."
819
1011
  },
820
1012
  504: {
821
1013
  code: "GATEWAY_TIMEOUT",
822
- name: "Gateway Timeout Error",
1014
+ name: "GatewayTimeoutError",
823
1015
  message: "Gateway timeout.",
824
- uiMessage: "The server took too long to respond. Please try again.",
825
- metadata: { status: 504 }
1016
+ uiMessage: "The server took too long to respond. Please try again."
826
1017
  },
827
1018
  505: {
828
1019
  code: "HTTP_VERSION_NOT_SUPPORTED",
829
- name: "HTTP Version Not Supported Error",
1020
+ name: "HttpVersionNotSupportedError",
830
1021
  message: "HTTP version not supported.",
831
- uiMessage: "Your browser version is not supported.",
832
- metadata: { status: 505 }
1022
+ uiMessage: "Your browser version is not supported."
833
1023
  },
834
1024
  506: {
835
1025
  code: "VARIANT_ALSO_NEGOTIATES",
836
- name: "Variant Also Negotiates Error",
1026
+ name: "VariantAlsoNegotiatesError",
837
1027
  message: "Variant also negotiates.",
838
- uiMessage: "The server has an internal configuration error.",
839
- metadata: { status: 506 }
1028
+ uiMessage: "The server has an internal configuration error."
840
1029
  },
841
1030
  507: {
842
1031
  code: "INSUFFICIENT_STORAGE",
843
- name: "Insufficient Storage Error",
1032
+ name: "InsufficientStorageError",
844
1033
  message: "Insufficient storage.",
845
- uiMessage: "The server has insufficient storage to complete the request.",
846
- metadata: { status: 507 }
1034
+ uiMessage: "The server has insufficient storage to complete the request."
847
1035
  },
848
1036
  508: {
849
1037
  code: "LOOP_DETECTED",
850
- name: "Loop Detected Error",
1038
+ name: "LoopDetectedError",
851
1039
  message: "Loop detected.",
852
- uiMessage: "The server detected an infinite loop.",
853
- metadata: { status: 508 }
1040
+ uiMessage: "The server detected an infinite loop."
854
1041
  },
855
1042
  510: {
856
1043
  code: "NOT_EXTENDED",
857
- name: "Not Extended Error",
1044
+ name: "NotExtendedError",
858
1045
  message: "Not extended.",
859
- uiMessage: "Additional extensions are required.",
860
- metadata: { status: 510 }
1046
+ uiMessage: "Additional extensions are required."
861
1047
  },
862
1048
  511: {
863
1049
  code: "NETWORK_AUTHENTICATION_REQUIRED",
864
- name: "Network Authentication Required Error",
1050
+ name: "NetworkAuthenticationRequiredError",
865
1051
  message: "Network authentication required.",
866
- uiMessage: "Network authentication is required to access this resource.",
867
- metadata: { status: 511 }
1052
+ uiMessage: "Network authentication is required to access this resource."
1053
+ }
1054
+ };
1055
+ var HTTPErrorX = class _HTTPErrorX extends ErrorX {
1056
+ /**
1057
+ * HTTP status code presets for all standard codes.
1058
+ * Keys are numeric status codes (400, 401, 404, 500, etc.)
1059
+ */
1060
+ static presets = httpPresets;
1061
+ /** Default to 500 Internal Server Error when no preset specified */
1062
+ static defaultPreset = 500;
1063
+ /** Default httpStatus for all HTTPErrorXs */
1064
+ static defaults = { httpStatus: 500 };
1065
+ /**
1066
+ * Transform that automatically sets httpStatus from the preset key.
1067
+ * Only sets httpStatus from presetKey if it matches a known preset.
1068
+ */
1069
+ static transform = (opts, { presetKey }) => ({
1070
+ ...opts,
1071
+ httpStatus: typeof presetKey === "number" && presetKey in httpPresets ? presetKey : opts.httpStatus
1072
+ });
1073
+ static create(statusCodeOrOverrides, overrides) {
1074
+ return ErrorX.create.call(
1075
+ _HTTPErrorX,
1076
+ statusCodeOrOverrides,
1077
+ overrides
1078
+ );
1079
+ }
1080
+ };
1081
+
1082
+ // src/presets/validation-error.ts
1083
+ var ValidationErrorX = class _ValidationErrorX extends ErrorX {
1084
+ /** Default httpStatus for validation errors (400 = bad request) */
1085
+ static defaults = {
1086
+ httpStatus: 400,
1087
+ name: "ValidationErrorX",
1088
+ code: "VALIDATION_ERROR",
1089
+ message: "Validation failed.",
1090
+ uiMessage: "The provided input is invalid. Please check your data."
1091
+ };
1092
+ /**
1093
+ * Transform that maps Zod issue codes to VALIDATION_ prefixed codes.
1094
+ * Converts Zod's snake_case codes to SCREAMING_SNAKE_CASE.
1095
+ */
1096
+ static transform = (opts, _ctx) => {
1097
+ const code = String(opts.code ?? "ERROR").toUpperCase();
1098
+ return {
1099
+ ...opts,
1100
+ code: code.startsWith("VALIDATION_") ? code : `VALIDATION_${code}`
1101
+ };
1102
+ };
1103
+ /**
1104
+ * Creates a ValidationErrorX from a Zod error.
1105
+ *
1106
+ * Maps Zod's error structure to ErrorX:
1107
+ * - Uses first issue's message as the error message
1108
+ * - Converts Zod issue code to ErrorX code (e.g., 'invalid_type' → 'VALIDATION_INVALID_TYPE')
1109
+ * - Captures all issues in metadata for multi-error handling
1110
+ *
1111
+ * @param zodError - The Zod error object (or any object with `issues` array)
1112
+ * @param overrides - Optional overrides for any ErrorX options
1113
+ * @returns ValidationErrorX instance
1114
+ *
1115
+ * @example
1116
+ * ```typescript
1117
+ * // Basic usage
1118
+ * try {
1119
+ * schema.parse(data)
1120
+ * } catch (err) {
1121
+ * if (err instanceof ZodError) {
1122
+ * throw ValidationErrorX.fromZodError(err)
1123
+ * }
1124
+ * }
1125
+ *
1126
+ * // With custom uiMessage
1127
+ * ValidationErrorX.fromZodError(zodError, {
1128
+ * uiMessage: 'Please fix the form errors',
1129
+ * })
1130
+ *
1131
+ * // Access all issues
1132
+ * const error = ValidationErrorX.fromZodError(zodError)
1133
+ * error.metadata?.issues?.forEach(issue => {
1134
+ * console.log(`${issue.path.join('.')}: ${issue.message}`)
1135
+ * })
1136
+ * ```
1137
+ */
1138
+ static fromZodError(zodError, overrides) {
1139
+ const firstIssue = zodError.issues[0];
1140
+ const fieldPath = firstIssue?.path.join(".");
1141
+ const zodCode = firstIssue?.code ?? "unknown";
1142
+ const errorCode = zodCode.toUpperCase().replace(/-/g, "_");
1143
+ const metadata = {
1144
+ zodCode,
1145
+ issueCount: zodError.issues.length,
1146
+ issues: zodError.issues
1147
+ };
1148
+ if (fieldPath) metadata.field = fieldPath;
1149
+ if (firstIssue?.path) metadata.path = firstIssue.path;
1150
+ if (firstIssue?.expected) metadata.expected = firstIssue.expected;
1151
+ if (firstIssue?.received) metadata.received = firstIssue.received;
1152
+ const mergedOpts = {
1153
+ ..._ValidationErrorX.defaults,
1154
+ code: errorCode,
1155
+ message: firstIssue?.message ?? "Validation failed",
1156
+ metadata,
1157
+ ...overrides
1158
+ };
1159
+ const transformedOpts = _ValidationErrorX.transform(mergedOpts, { presetKey: void 0 });
1160
+ return new _ValidationErrorX(transformedOpts);
1161
+ }
1162
+ /**
1163
+ * Creates a ValidationErrorX for a specific field.
1164
+ * Convenience method for manual validation errors.
1165
+ *
1166
+ * @param field - The field name that failed validation
1167
+ * @param message - The error message
1168
+ * @param options - Additional options
1169
+ * @returns ValidationErrorX instance
1170
+ *
1171
+ * @example
1172
+ * ```typescript
1173
+ * // Simple field error
1174
+ * throw ValidationErrorX.forField('email', 'Invalid email format')
1175
+ *
1176
+ * // With code
1177
+ * throw ValidationErrorX.forField('age', 'Must be 18 or older', {
1178
+ * code: 'TOO_YOUNG',
1179
+ * })
1180
+ * ```
1181
+ */
1182
+ static forField(field, message, options) {
1183
+ const mergedOpts = {
1184
+ ..._ValidationErrorX.defaults,
1185
+ message,
1186
+ code: options?.code ?? "INVALID_FIELD",
1187
+ metadata: {
1188
+ field,
1189
+ path: field.split("."),
1190
+ ...options?.metadata
1191
+ },
1192
+ ...options
1193
+ };
1194
+ const transformedOpts = _ValidationErrorX.transform(mergedOpts, { presetKey: void 0 });
1195
+ return new _ValidationErrorX(transformedOpts);
868
1196
  }
869
1197
  };
870
1198
 
871
- export { ErrorX, http };
1199
+ export { DBErrorX, ErrorX, HTTPErrorX, ValidationErrorX };
872
1200
  //# sourceMappingURL=index.js.map
873
1201
  //# sourceMappingURL=index.js.map