@bombillazo/error-x 0.4.5 → 0.5.0

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