@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.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
  }
@@ -381,7 +354,8 @@ var ErrorX = class _ErrorX extends Error {
381
354
  /**
382
355
  * Converts unknown input into ErrorXOptions with intelligent property extraction.
383
356
  * Handles strings, regular Error objects, API response objects, and unknown values.
384
- * This is a private helper method used by both the constructor and toErrorX.
357
+ * Extracts metadata directly from objects if present, without wrapping.
358
+ * This is a private helper method used by ErrorX.from().
385
359
  *
386
360
  * @param error - Value to convert to ErrorXOptions
387
361
  * @returns ErrorXOptions object with extracted properties
@@ -394,13 +368,10 @@ var ErrorX = class _ErrorX extends Error {
394
368
  let uiMessage = "";
395
369
  let cause;
396
370
  let metadata = {};
397
- let type;
398
- let href;
399
- let source;
371
+ let httpStatus;
400
372
  if (error) {
401
373
  if (typeof error === "string") {
402
374
  message = error;
403
- metadata = { originalError: error };
404
375
  } else if (error instanceof Error) {
405
376
  name = error.name;
406
377
  message = error.message;
@@ -419,24 +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));
393
+ if ("metadata" in error && typeof error.metadata === "object" && error.metadata !== null) {
394
+ metadata = error.metadata;
424
395
  }
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);
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;
431
402
  }
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
- metadata = { originalError: error };
440
403
  }
441
404
  }
442
405
  const options = {
@@ -447,15 +410,40 @@ var ErrorX = class _ErrorX extends Error {
447
410
  if (uiMessage) options.uiMessage = uiMessage;
448
411
  if (cause) options.cause = cause;
449
412
  if (Object.keys(metadata).length > 0) options.metadata = metadata;
450
- if (type) options.type = type;
451
- if (href) options.docsUrl = href;
452
- if (source) options.source = source;
413
+ if (httpStatus !== void 0) options.httpStatus = httpStatus;
453
414
  return options;
454
415
  }
455
- static from(error) {
456
- if (error instanceof _ErrorX) return error;
457
- const options = _ErrorX.convertUnknownToOptions(error);
458
- 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;
459
447
  }
460
448
  /**
461
449
  * Converts the ErrorX instance to a detailed string representation.
@@ -525,20 +513,28 @@ ${this.stack}`;
525
513
  metadata: safeMetadata,
526
514
  timestamp: this.timestamp
527
515
  };
528
- if (this.type !== void 0) {
529
- serialized.type = this.type;
530
- }
531
- if (this.docsUrl !== void 0) {
532
- serialized.docsUrl = this.docsUrl;
533
- }
534
- if (this.source !== void 0) {
535
- serialized.source = this.source;
516
+ if (this.httpStatus !== void 0) {
517
+ serialized.httpStatus = this.httpStatus;
536
518
  }
537
519
  if (this.stack) {
538
520
  serialized.stack = this.stack;
539
521
  }
540
- if (this.cause) {
541
- 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
+ });
542
538
  }
543
539
  return serialized;
544
540
  }
@@ -570,10 +566,7 @@ ${this.stack}`;
570
566
  name: serialized.name,
571
567
  code: serialized.code,
572
568
  uiMessage: serialized.uiMessage,
573
- type: serialized.type,
574
- docsUrl: serialized.docsUrl,
575
- source: serialized.source,
576
- cause: serialized.cause
569
+ httpStatus: serialized.httpStatus
577
570
  };
578
571
  if (serialized.metadata !== void 0) {
579
572
  options.metadata = serialized.metadata;
@@ -583,289 +576,626 @@ ${this.stack}`;
583
576
  error.stack = serialized.stack;
584
577
  }
585
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
+ }
586
601
  return error;
587
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
+ }
588
681
  };
589
682
 
590
- // src/presets.ts
591
- var http = {
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
+ }
814
+ };
815
+
816
+ // src/presets/http-error.ts
817
+ var httpPresets = {
592
818
  // 4xx Client Errors
593
819
  400: {
594
820
  code: "BAD_REQUEST",
595
- name: "Bad Request Error",
821
+ name: "BadRequestError",
596
822
  message: "Bad request.",
597
- uiMessage: "The request could not be processed. Please check your input and try again.",
598
- metadata: { status: 400 }
823
+ uiMessage: "The request could not be processed. Please check your input and try again."
599
824
  },
600
825
  401: {
601
826
  code: "UNAUTHORIZED",
602
- name: "Unauthorized Error",
827
+ name: "UnauthorizedError",
603
828
  message: "Unauthorized.",
604
- uiMessage: "Authentication required. Please log in to continue.",
605
- metadata: { status: 401 }
829
+ uiMessage: "Authentication required. Please log in to continue."
606
830
  },
607
831
  402: {
608
832
  code: "PAYMENT_REQUIRED",
609
- name: "Payment Required Error",
833
+ name: "PaymentRequiredError",
610
834
  message: "Payment required.",
611
- uiMessage: "Payment is required to access this resource.",
612
- metadata: { status: 402 }
835
+ uiMessage: "Payment is required to access this resource."
613
836
  },
614
837
  403: {
615
838
  code: "FORBIDDEN",
616
- name: "Forbidden Error",
839
+ name: "ForbiddenError",
617
840
  message: "Forbidden.",
618
- uiMessage: "You do not have permission to access this resource.",
619
- metadata: { status: 403 }
841
+ uiMessage: "You do not have permission to access this resource."
620
842
  },
621
843
  404: {
622
844
  code: "NOT_FOUND",
623
- name: "Not Found Error",
845
+ name: "NotFoundError",
624
846
  message: "Not found.",
625
- uiMessage: "The requested resource could not be found.",
626
- metadata: { status: 404 }
847
+ uiMessage: "The requested resource could not be found."
627
848
  },
628
849
  405: {
629
850
  code: "METHOD_NOT_ALLOWED",
630
- name: "Method Not Allowed Error",
851
+ name: "MethodNotAllowedError",
631
852
  message: "Method not allowed.",
632
- uiMessage: "This action is not allowed for the requested resource.",
633
- metadata: { status: 405 }
853
+ uiMessage: "This action is not allowed for the requested resource."
634
854
  },
635
855
  406: {
636
856
  code: "NOT_ACCEPTABLE",
637
- name: "Not Acceptable Error",
857
+ name: "NotAcceptableError",
638
858
  message: "Not acceptable.",
639
- uiMessage: "The requested format is not supported.",
640
- metadata: { status: 406 }
859
+ uiMessage: "The requested format is not supported."
641
860
  },
642
861
  407: {
643
862
  code: "PROXY_AUTHENTICATION_REQUIRED",
644
- name: "Proxy Authentication Required Error",
863
+ name: "ProxyAuthenticationRequiredError",
645
864
  message: "Proxy authentication required.",
646
- uiMessage: "Proxy authentication is required to access this resource.",
647
- metadata: { status: 407 }
865
+ uiMessage: "Proxy authentication is required to access this resource."
648
866
  },
649
867
  408: {
650
868
  code: "REQUEST_TIMEOUT",
651
- name: "Request Timeout Error",
869
+ name: "RequestTimeoutError",
652
870
  message: "Request timeout.",
653
- uiMessage: "The request took too long to complete. Please try again.",
654
- metadata: { status: 408 }
871
+ uiMessage: "The request took too long to complete. Please try again."
655
872
  },
656
873
  409: {
657
874
  code: "CONFLICT",
658
- name: "Conflict Error",
875
+ name: "ConflictError",
659
876
  message: "Conflict.",
660
- uiMessage: "The request conflicts with the current state. Please refresh and try again.",
661
- metadata: { status: 409 }
877
+ uiMessage: "The request conflicts with the current state. Please refresh and try again."
662
878
  },
663
879
  410: {
664
880
  code: "GONE",
665
- name: "Gone Error",
881
+ name: "GoneError",
666
882
  message: "Gone.",
667
- uiMessage: "This resource is no longer available.",
668
- metadata: { status: 410 }
883
+ uiMessage: "This resource is no longer available."
669
884
  },
670
885
  411: {
671
886
  code: "LENGTH_REQUIRED",
672
- name: "Length Required Error",
887
+ name: "LengthRequiredError",
673
888
  message: "Length required.",
674
- uiMessage: "The request is missing required length information.",
675
- metadata: { status: 411 }
889
+ uiMessage: "The request is missing required length information."
676
890
  },
677
891
  412: {
678
892
  code: "PRECONDITION_FAILED",
679
- name: "Precondition Failed Error",
893
+ name: "PreconditionFailedError",
680
894
  message: "Precondition failed.",
681
- uiMessage: "A required condition was not met. Please try again.",
682
- metadata: { status: 412 }
895
+ uiMessage: "A required condition was not met. Please try again."
683
896
  },
684
897
  413: {
685
898
  code: "PAYLOAD_TOO_LARGE",
686
- name: "Payload Too Large Error",
899
+ name: "PayloadTooLargeError",
687
900
  message: "Payload too large.",
688
- uiMessage: "The request is too large. Please reduce the size and try again.",
689
- metadata: { status: 413 }
901
+ uiMessage: "The request is too large. Please reduce the size and try again."
690
902
  },
691
903
  414: {
692
904
  code: "URI_TOO_LONG",
693
- name: "URI Too Long Error",
905
+ name: "UriTooLongError",
694
906
  message: "URI too long.",
695
- uiMessage: "The request URL is too long.",
696
- metadata: { status: 414 }
907
+ uiMessage: "The request URL is too long."
697
908
  },
698
909
  415: {
699
910
  code: "UNSUPPORTED_MEDIA_TYPE",
700
- name: "Unsupported Media Type Error",
911
+ name: "UnsupportedMediaTypeError",
701
912
  message: "Unsupported media type.",
702
- uiMessage: "The file type is not supported.",
703
- metadata: { status: 415 }
913
+ uiMessage: "The file type is not supported."
704
914
  },
705
915
  416: {
706
916
  code: "RANGE_NOT_SATISFIABLE",
707
- name: "Range Not Satisfiable Error",
917
+ name: "RangeNotSatisfiableError",
708
918
  message: "Range not satisfiable.",
709
- uiMessage: "The requested range cannot be satisfied.",
710
- metadata: { status: 416 }
919
+ uiMessage: "The requested range cannot be satisfied."
711
920
  },
712
921
  417: {
713
922
  code: "EXPECTATION_FAILED",
714
- name: "Expectation Failed Error",
923
+ name: "ExpectationFailedError",
715
924
  message: "Expectation failed.",
716
- uiMessage: "The server cannot meet the requirements of the request.",
717
- metadata: { status: 417 }
925
+ uiMessage: "The server cannot meet the requirements of the request."
718
926
  },
719
927
  418: {
720
928
  code: "IM_A_TEAPOT",
721
- name: "Im A Teapot Error",
929
+ name: "ImATeapotError",
722
930
  message: "I'm a teapot.",
723
- uiMessage: "I'm a teapot and cannot brew coffee.",
724
- metadata: { status: 418 }
931
+ uiMessage: "I'm a teapot and cannot brew coffee."
725
932
  },
726
933
  422: {
727
934
  code: "UNPROCESSABLE_ENTITY",
728
- name: "Unprocessable Entity Error",
935
+ name: "UnprocessableEntityError",
729
936
  message: "Unprocessable entity.",
730
- uiMessage: "The request contains invalid data. Please check your input.",
731
- metadata: { status: 422 }
937
+ uiMessage: "The request contains invalid data. Please check your input."
732
938
  },
733
939
  423: {
734
940
  code: "LOCKED",
735
- name: "Locked Error",
941
+ name: "LockedError",
736
942
  message: "Locked.",
737
- uiMessage: "This resource is locked and cannot be modified.",
738
- metadata: { status: 423 }
943
+ uiMessage: "This resource is locked and cannot be modified."
739
944
  },
740
945
  424: {
741
946
  code: "FAILED_DEPENDENCY",
742
- name: "Failed Dependency Error",
947
+ name: "FailedDependencyError",
743
948
  message: "Failed dependency.",
744
- uiMessage: "The request failed due to a dependency error.",
745
- metadata: { status: 424 }
949
+ uiMessage: "The request failed due to a dependency error."
746
950
  },
747
951
  425: {
748
952
  code: "TOO_EARLY",
749
- name: "Too Early Error",
953
+ name: "TooEarlyError",
750
954
  message: "Too early.",
751
- uiMessage: "The request was sent too early. Please try again later.",
752
- metadata: { status: 425 }
955
+ uiMessage: "The request was sent too early. Please try again later."
753
956
  },
754
957
  426: {
755
958
  code: "UPGRADE_REQUIRED",
756
- name: "Upgrade Required Error",
959
+ name: "UpgradeRequiredError",
757
960
  message: "Upgrade required.",
758
- uiMessage: "Please upgrade to continue using this service.",
759
- metadata: { status: 426 }
961
+ uiMessage: "Please upgrade to continue using this service."
760
962
  },
761
963
  428: {
762
964
  code: "PRECONDITION_REQUIRED",
763
- name: "Precondition Required Error",
965
+ name: "PreconditionRequiredError",
764
966
  message: "Precondition required.",
765
- uiMessage: "Required conditions are missing from the request.",
766
- metadata: { status: 428 }
967
+ uiMessage: "Required conditions are missing from the request."
767
968
  },
768
969
  429: {
769
970
  code: "TOO_MANY_REQUESTS",
770
- name: "Too Many Requests Error",
971
+ name: "TooManyRequestsError",
771
972
  message: "Too many requests.",
772
- uiMessage: "You have made too many requests. Please wait and try again.",
773
- metadata: { status: 429 }
973
+ uiMessage: "You have made too many requests. Please wait and try again."
774
974
  },
775
975
  431: {
776
976
  code: "REQUEST_HEADER_FIELDS_TOO_LARGE",
777
- name: "Request Header Fields Too Large Error",
977
+ name: "RequestHeaderFieldsTooLargeError",
778
978
  message: "Request header fields too large.",
779
- uiMessage: "The request headers are too large.",
780
- metadata: { status: 431 }
979
+ uiMessage: "The request headers are too large."
781
980
  },
782
981
  451: {
783
982
  code: "UNAVAILABLE_FOR_LEGAL_REASONS",
784
- name: "Unavailable For Legal Reasons Error",
983
+ name: "UnavailableForLegalReasonsError",
785
984
  message: "Unavailable for legal reasons.",
786
- uiMessage: "This content is unavailable for legal reasons.",
787
- metadata: { status: 451 }
985
+ uiMessage: "This content is unavailable for legal reasons."
788
986
  },
789
987
  // 5xx Server Errors
790
988
  500: {
791
989
  code: "INTERNAL_SERVER_ERROR",
792
- name: "Internal Server Error",
990
+ name: "InternalServerError",
793
991
  message: "Internal server error.",
794
- uiMessage: "An unexpected error occurred. Please try again later.",
795
- metadata: { status: 500 }
992
+ uiMessage: "An unexpected error occurred. Please try again later."
796
993
  },
797
994
  501: {
798
995
  code: "NOT_IMPLEMENTED",
799
- name: "Not Implemented Error",
996
+ name: "NotImplementedError",
800
997
  message: "Not implemented.",
801
- uiMessage: "This feature is not yet available.",
802
- metadata: { status: 501 }
998
+ uiMessage: "This feature is not yet available."
803
999
  },
804
1000
  502: {
805
1001
  code: "BAD_GATEWAY",
806
- name: "Bad Gateway Error",
1002
+ name: "BadGatewayError",
807
1003
  message: "Bad gateway.",
808
- uiMessage: "Unable to connect to the server. Please try again later.",
809
- metadata: { status: 502 }
1004
+ uiMessage: "Unable to connect to the server. Please try again later."
810
1005
  },
811
1006
  503: {
812
1007
  code: "SERVICE_UNAVAILABLE",
813
- name: "Service Unavailable Error",
1008
+ name: "ServiceUnavailableError",
814
1009
  message: "Service unavailable.",
815
- uiMessage: "The service is temporarily unavailable. Please try again later.",
816
- metadata: { status: 503 }
1010
+ uiMessage: "The service is temporarily unavailable. Please try again later."
817
1011
  },
818
1012
  504: {
819
1013
  code: "GATEWAY_TIMEOUT",
820
- name: "Gateway Timeout Error",
1014
+ name: "GatewayTimeoutError",
821
1015
  message: "Gateway timeout.",
822
- uiMessage: "The server took too long to respond. Please try again.",
823
- metadata: { status: 504 }
1016
+ uiMessage: "The server took too long to respond. Please try again."
824
1017
  },
825
1018
  505: {
826
1019
  code: "HTTP_VERSION_NOT_SUPPORTED",
827
- name: "HTTP Version Not Supported Error",
1020
+ name: "HttpVersionNotSupportedError",
828
1021
  message: "HTTP version not supported.",
829
- uiMessage: "Your browser version is not supported.",
830
- metadata: { status: 505 }
1022
+ uiMessage: "Your browser version is not supported."
831
1023
  },
832
1024
  506: {
833
1025
  code: "VARIANT_ALSO_NEGOTIATES",
834
- name: "Variant Also Negotiates Error",
1026
+ name: "VariantAlsoNegotiatesError",
835
1027
  message: "Variant also negotiates.",
836
- uiMessage: "The server has an internal configuration error.",
837
- metadata: { status: 506 }
1028
+ uiMessage: "The server has an internal configuration error."
838
1029
  },
839
1030
  507: {
840
1031
  code: "INSUFFICIENT_STORAGE",
841
- name: "Insufficient Storage Error",
1032
+ name: "InsufficientStorageError",
842
1033
  message: "Insufficient storage.",
843
- uiMessage: "The server has insufficient storage to complete the request.",
844
- metadata: { status: 507 }
1034
+ uiMessage: "The server has insufficient storage to complete the request."
845
1035
  },
846
1036
  508: {
847
1037
  code: "LOOP_DETECTED",
848
- name: "Loop Detected Error",
1038
+ name: "LoopDetectedError",
849
1039
  message: "Loop detected.",
850
- uiMessage: "The server detected an infinite loop.",
851
- metadata: { status: 508 }
1040
+ uiMessage: "The server detected an infinite loop."
852
1041
  },
853
1042
  510: {
854
1043
  code: "NOT_EXTENDED",
855
- name: "Not Extended Error",
1044
+ name: "NotExtendedError",
856
1045
  message: "Not extended.",
857
- uiMessage: "Additional extensions are required.",
858
- metadata: { status: 510 }
1046
+ uiMessage: "Additional extensions are required."
859
1047
  },
860
1048
  511: {
861
1049
  code: "NETWORK_AUTHENTICATION_REQUIRED",
862
- name: "Network Authentication Required Error",
1050
+ name: "NetworkAuthenticationRequiredError",
863
1051
  message: "Network authentication required.",
864
- uiMessage: "Network authentication is required to access this resource.",
865
- 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);
866
1196
  }
867
1197
  };
868
1198
 
869
- export { ErrorX, http };
1199
+ export { DBErrorX, ErrorX, HTTPErrorX, ValidationErrorX };
870
1200
  //# sourceMappingURL=index.js.map
871
1201
  //# sourceMappingURL=index.js.map