@bombillazo/error-x 0.4.6 → 0.5.1

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
  }
@@ -122,38 +132,38 @@ var ErrorX = class _ErrorX extends Error {
122
132
  return "Error";
123
133
  }
124
134
  /**
125
- * Converts any value to ErrorXCause format.
126
- * @param value - Value to convert to ErrorXCause
127
- * @returns ErrorXCause object or undefined if value is null/undefined
135
+ * Converts any value to ErrorXSnapshot format.
136
+ * @param value - Value to convert to ErrorXSnapshot
137
+ * @returns ErrorXSnapshot object or undefined if value is null/undefined
128
138
  */
129
- static toErrorXCause(value) {
139
+ static toErrorXSnapshot(value) {
130
140
  if (value === void 0 || value === null) {
131
141
  return void 0;
132
142
  }
133
143
  if (value instanceof Error) {
134
- const cause = {
144
+ const snapshot = {
135
145
  message: value.message
136
146
  };
137
147
  if (value.name) {
138
- cause.name = value.name;
148
+ snapshot.name = value.name;
139
149
  }
140
150
  if (value.stack) {
141
- cause.stack = value.stack;
151
+ snapshot.stack = value.stack;
142
152
  }
143
- return cause;
153
+ return snapshot;
144
154
  }
145
155
  if (typeof value === "object") {
146
156
  const obj = value;
147
- const cause = {
157
+ const snapshot = {
148
158
  message: String(obj.message || obj)
149
159
  };
150
160
  if (obj.name) {
151
- cause.name = String(obj.name);
161
+ snapshot.name = String(obj.name);
152
162
  }
153
163
  if (obj.stack) {
154
- cause.stack = String(obj.stack);
164
+ snapshot.stack = String(obj.stack);
155
165
  }
156
- return cause;
166
+ return snapshot;
157
167
  }
158
168
  return {
159
169
  message: String(value)
@@ -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,15 @@ 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
- metadata: { ...this.metadata ?? {}, ...additionalMetadata },
355
- type: this.type,
356
- docsUrl: this.docsUrl,
357
- source: this.source
327
+ metadata: {
328
+ ...this.metadata ?? {},
329
+ ...additionalMetadata
330
+ },
331
+ httpStatus: this.httpStatus
358
332
  };
359
333
  const newError = new _ErrorX(options);
334
+ newError._chain = [newError, ...this._chain.slice(1)];
335
+ newError.original = this.original;
360
336
  if (this.stack) {
361
337
  newError.stack = this.stack;
362
338
  }
@@ -401,9 +377,7 @@ var ErrorX = class _ErrorX extends Error {
401
377
  let uiMessage = "";
402
378
  let cause;
403
379
  let metadata = {};
404
- let type;
405
- let href;
406
- let source;
380
+ let httpStatus;
407
381
  if (error) {
408
382
  if (typeof error === "string") {
409
383
  message = error;
@@ -425,26 +399,16 @@ var ErrorX = class _ErrorX extends Error {
425
399
  if ("code" in error && error.code) code = String(error.code);
426
400
  if ("uiMessage" in error && error.uiMessage) uiMessage = String(error.uiMessage);
427
401
  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));
430
- }
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);
437
- }
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
402
  if ("metadata" in error && typeof error.metadata === "object" && error.metadata !== null) {
446
403
  metadata = error.metadata;
447
404
  }
405
+ if ("httpStatus" in error && typeof error.httpStatus === "number") {
406
+ httpStatus = error.httpStatus;
407
+ } else if ("status" in error && typeof error.status === "number") {
408
+ httpStatus = error.status;
409
+ } else if ("statusCode" in error && typeof error.statusCode === "number") {
410
+ httpStatus = error.statusCode;
411
+ }
448
412
  }
449
413
  }
450
414
  const options = {
@@ -455,15 +419,40 @@ var ErrorX = class _ErrorX extends Error {
455
419
  if (uiMessage) options.uiMessage = uiMessage;
456
420
  if (cause) options.cause = cause;
457
421
  if (Object.keys(metadata).length > 0) options.metadata = metadata;
458
- if (type) options.type = type;
459
- if (href) options.docsUrl = href;
460
- if (source) options.source = source;
422
+ if (httpStatus !== void 0) options.httpStatus = httpStatus;
461
423
  return options;
462
424
  }
463
- static from(error) {
464
- if (error instanceof _ErrorX) return error;
465
- const options = _ErrorX.convertUnknownToOptions(error);
466
- return new _ErrorX(options);
425
+ static from(payload, overrides) {
426
+ if (payload instanceof _ErrorX) {
427
+ if (overrides && Object.keys(overrides).length > 0) {
428
+ const mergedOptions = {
429
+ message: overrides.message ?? payload.message,
430
+ name: overrides.name ?? payload.name,
431
+ code: overrides.code ?? payload.code,
432
+ uiMessage: overrides.uiMessage ?? payload.uiMessage,
433
+ httpStatus: overrides.httpStatus ?? payload.httpStatus,
434
+ metadata: overrides.metadata ? deepmergeTs.deepmerge(payload.metadata ?? {}, overrides.metadata) : payload.metadata
435
+ };
436
+ const newError = new _ErrorX(mergedOptions);
437
+ newError.original = payload.original;
438
+ newError._chain = [newError];
439
+ return newError;
440
+ }
441
+ return payload;
442
+ }
443
+ const extractedOptions = _ErrorX.convertUnknownToOptions(payload);
444
+ const finalOptions = overrides ? {
445
+ ...extractedOptions,
446
+ ...overrides,
447
+ metadata: extractedOptions.metadata || overrides.metadata ? deepmergeTs.deepmerge(extractedOptions.metadata ?? {}, overrides.metadata ?? {}) : void 0
448
+ } : extractedOptions;
449
+ const error = new _ErrorX(finalOptions);
450
+ error.original = _ErrorX.toErrorXSnapshot(payload);
451
+ if (payload instanceof Error && payload.stack) {
452
+ error.stack = payload.stack;
453
+ }
454
+ error._chain = [error];
455
+ return error;
467
456
  }
468
457
  /**
469
458
  * Converts the ErrorX instance to a detailed string representation.
@@ -533,20 +522,34 @@ ${this.stack}`;
533
522
  metadata: safeMetadata,
534
523
  timestamp: this.timestamp
535
524
  };
536
- if (this.type !== void 0) {
537
- serialized.type = this.type;
538
- }
539
- if (this.docsUrl !== void 0) {
540
- serialized.docsUrl = this.docsUrl;
541
- }
542
- if (this.source !== void 0) {
543
- serialized.source = this.source;
525
+ if (this.httpStatus !== void 0) {
526
+ serialized.httpStatus = this.httpStatus;
544
527
  }
545
528
  if (this.stack) {
546
529
  serialized.stack = this.stack;
547
530
  }
548
- if (this.cause) {
549
- serialized.cause = this.cause;
531
+ if (this.original) {
532
+ serialized.original = this.original;
533
+ }
534
+ if (this._chain.length > 1) {
535
+ serialized.chain = this._chain.map((err) => {
536
+ let safeMetadata2;
537
+ if (err.metadata) {
538
+ safeMetadata2 = JSON.parse(safeStringify__default.default(err.metadata));
539
+ }
540
+ const chainEntry = {
541
+ name: err.name,
542
+ message: err.message,
543
+ code: err.code,
544
+ uiMessage: err.uiMessage,
545
+ metadata: safeMetadata2,
546
+ timestamp: err.timestamp
547
+ };
548
+ if (err.httpStatus !== void 0) chainEntry.httpStatus = err.httpStatus;
549
+ if (err.stack) chainEntry.stack = err.stack;
550
+ if (err.original) chainEntry.original = err.original;
551
+ return chainEntry;
552
+ });
550
553
  }
551
554
  return serialized;
552
555
  }
@@ -578,10 +581,7 @@ ${this.stack}`;
578
581
  name: serialized.name,
579
582
  code: serialized.code,
580
583
  uiMessage: serialized.uiMessage,
581
- type: serialized.type,
582
- docsUrl: serialized.docsUrl,
583
- source: serialized.source,
584
- cause: serialized.cause
584
+ httpStatus: serialized.httpStatus
585
585
  };
586
586
  if (serialized.metadata !== void 0) {
587
587
  options.metadata = serialized.metadata;
@@ -591,290 +591,641 @@ ${this.stack}`;
591
591
  error.stack = serialized.stack;
592
592
  }
593
593
  error.timestamp = serialized.timestamp;
594
+ if (serialized.original) {
595
+ error.original = serialized.original;
596
+ }
597
+ if (serialized.chain && serialized.chain.length > 0) {
598
+ const chainErrors = [error];
599
+ for (let i = 1; i < serialized.chain.length; i++) {
600
+ const causeData = serialized.chain[i];
601
+ if (!causeData) continue;
602
+ const chainErrorOptions = {
603
+ message: causeData.message
604
+ };
605
+ if (causeData.name) chainErrorOptions.name = causeData.name;
606
+ if ("code" in causeData && causeData.code !== void 0)
607
+ chainErrorOptions.code = causeData.code;
608
+ if ("uiMessage" in causeData && causeData.uiMessage !== void 0)
609
+ chainErrorOptions.uiMessage = causeData.uiMessage;
610
+ if ("metadata" in causeData && causeData.metadata !== void 0)
611
+ chainErrorOptions.metadata = causeData.metadata;
612
+ if ("httpStatus" in causeData && causeData.httpStatus !== void 0)
613
+ chainErrorOptions.httpStatus = causeData.httpStatus;
614
+ const chainError = new _ErrorX(chainErrorOptions);
615
+ if (causeData.stack) chainError.stack = causeData.stack;
616
+ if ("timestamp" in causeData && causeData.timestamp !== void 0)
617
+ chainError.timestamp = causeData.timestamp;
618
+ if ("original" in causeData && causeData.original) chainError.original = causeData.original;
619
+ chainErrors.push(chainError);
620
+ }
621
+ for (let i = 0; i < chainErrors.length; i++) {
622
+ const chainError = chainErrors[i];
623
+ if (chainError) {
624
+ chainError._chain = chainErrors.slice(i);
625
+ }
626
+ }
627
+ }
594
628
  return error;
595
629
  }
630
+ /**
631
+ * Creates a new instance of this error class using optional presets and overrides.
632
+ * This is a factory method that supports preset-based error creation with
633
+ * full TypeScript autocomplete for preset keys.
634
+ *
635
+ * Define static properties on your subclass to customize behavior:
636
+ * - `presets`: Record of preset configurations keyed by identifier
637
+ * - `defaultPreset`: Key of preset to use as fallback
638
+ * - `defaults`: Default values for all errors of this class
639
+ * - `transform`: Function to transform options before instantiation
640
+ *
641
+ * Supported call signatures:
642
+ * - `create()` - uses defaultPreset
643
+ * - `create(presetKey)` - uses specified preset
644
+ * - `create(presetKey, overrides)` - preset with overrides
645
+ * - `create(overrides)` - just overrides, uses defaultPreset
646
+ *
647
+ * @param presetKeyOrOverrides - Preset key (string/number) or overrides object
648
+ * @param overrides - Optional overrides when first arg is preset key
649
+ * @returns New instance of this error class
650
+ *
651
+ * @example
652
+ * ```typescript
653
+ * class DBError extends ErrorX<{ query?: string }> {
654
+ * static presets = {
655
+ * 9333: { message: 'Connection timeout', code: 'TIMEOUT' },
656
+ * CONN_REFUSED: { message: 'Connection refused', code: 'CONN_REFUSED' },
657
+ * GENERIC: { message: 'A database error occurred', code: 'ERROR' },
658
+ * }
659
+ * static defaultPreset = 'GENERIC'
660
+ * static defaults = { httpStatus: 500 }
661
+ * static transform = (opts, ctx) => ({
662
+ * ...opts,
663
+ * code: `DB_${opts.code}`,
664
+ * })
665
+ * }
666
+ *
667
+ * DBError.create() // uses defaultPreset
668
+ * DBError.create(9333) // uses preset 9333
669
+ * DBError.create('CONN_REFUSED') // uses preset CONN_REFUSED
670
+ * DBError.create(9333, { message: 'Custom' }) // preset + overrides
671
+ * DBError.create({ message: 'Custom' }) // just overrides
672
+ * ```
673
+ */
674
+ static create(presetKeyOrOverrides, overrides) {
675
+ let presetKey;
676
+ let finalOverrides;
677
+ if (typeof presetKeyOrOverrides === "object" && presetKeyOrOverrides !== null) {
678
+ presetKey = void 0;
679
+ finalOverrides = presetKeyOrOverrides;
680
+ } else {
681
+ presetKey = presetKeyOrOverrides;
682
+ finalOverrides = overrides;
683
+ }
684
+ const ctor = this;
685
+ const presets = ctor.presets ?? {};
686
+ const defaultPreset = ctor.defaultPreset;
687
+ const defaults = ctor.defaults ?? {};
688
+ const transform = ctor.transform;
689
+ let resolvedPreset = {};
690
+ if (presetKey !== void 0) {
691
+ if (presetKey in presets) {
692
+ resolvedPreset = presets[presetKey] ?? {};
693
+ } else if (defaultPreset !== void 0 && defaultPreset in presets) {
694
+ resolvedPreset = presets[defaultPreset] ?? {};
695
+ }
696
+ } else if (defaultPreset !== void 0 && defaultPreset in presets) {
697
+ resolvedPreset = presets[defaultPreset] ?? {};
698
+ }
699
+ const mergedOptions = deepmergeTs.deepmerge(
700
+ defaults,
701
+ resolvedPreset,
702
+ finalOverrides ?? {}
703
+ );
704
+ const transformContext = { presetKey };
705
+ const finalOptions = transform ? transform(mergedOptions, transformContext) : mergedOptions;
706
+ return new this(finalOptions);
707
+ }
596
708
  };
597
709
 
598
- // src/presets.ts
599
- var http = {
710
+ // src/presets/db-error.ts
711
+ var dbPresets = {
712
+ // Connection errors
713
+ CONNECTION_FAILED: {
714
+ code: "CONNECTION_FAILED",
715
+ name: "DBConnectionError",
716
+ message: "Failed to connect to database.",
717
+ uiMessage: "Unable to connect to the database. Please try again later."
718
+ },
719
+ CONNECTION_TIMEOUT: {
720
+ code: "CONNECTION_TIMEOUT",
721
+ name: "DBConnectionTimeoutError",
722
+ message: "Database connection timed out.",
723
+ uiMessage: "The database connection timed out. Please try again."
724
+ },
725
+ CONNECTION_REFUSED: {
726
+ code: "CONNECTION_REFUSED",
727
+ name: "DBConnectionRefusedError",
728
+ message: "Database connection refused.",
729
+ uiMessage: "Unable to connect to the database. Please try again later."
730
+ },
731
+ CONNECTION_LOST: {
732
+ code: "CONNECTION_LOST",
733
+ name: "DBConnectionLostError",
734
+ message: "Database connection lost.",
735
+ uiMessage: "The database connection was lost. Please try again."
736
+ },
737
+ // Query errors
738
+ QUERY_FAILED: {
739
+ code: "QUERY_FAILED",
740
+ name: "DBQueryError",
741
+ message: "Database query failed.",
742
+ uiMessage: "The database operation failed. Please try again."
743
+ },
744
+ QUERY_TIMEOUT: {
745
+ code: "QUERY_TIMEOUT",
746
+ name: "DBQueryTimeoutError",
747
+ message: "Database query timed out.",
748
+ uiMessage: "The database operation took too long. Please try again."
749
+ },
750
+ SYNTAX_ERROR: {
751
+ code: "SYNTAX_ERROR",
752
+ name: "DBSyntaxError",
753
+ message: "Invalid query syntax.",
754
+ uiMessage: "An internal error occurred. Please contact support."
755
+ },
756
+ // Constraint errors
757
+ UNIQUE_VIOLATION: {
758
+ code: "UNIQUE_VIOLATION",
759
+ name: "DBUniqueViolationError",
760
+ message: "Unique constraint violation.",
761
+ uiMessage: "This record already exists.",
762
+ httpStatus: 409
763
+ },
764
+ FOREIGN_KEY_VIOLATION: {
765
+ code: "FOREIGN_KEY_VIOLATION",
766
+ name: "DBForeignKeyError",
767
+ message: "Foreign key constraint violation.",
768
+ uiMessage: "This operation references a record that does not exist.",
769
+ httpStatus: 400
770
+ },
771
+ NOT_NULL_VIOLATION: {
772
+ code: "NOT_NULL_VIOLATION",
773
+ name: "DBNotNullError",
774
+ message: "Not null constraint violation.",
775
+ uiMessage: "A required field is missing.",
776
+ httpStatus: 400
777
+ },
778
+ CHECK_VIOLATION: {
779
+ code: "CHECK_VIOLATION",
780
+ name: "DBCheckViolationError",
781
+ message: "Check constraint violation.",
782
+ uiMessage: "The provided data is invalid.",
783
+ httpStatus: 400
784
+ },
785
+ // Transaction errors
786
+ TRANSACTION_FAILED: {
787
+ code: "TRANSACTION_FAILED",
788
+ name: "DBTransactionError",
789
+ message: "Database transaction failed.",
790
+ uiMessage: "The operation failed. Please try again."
791
+ },
792
+ DEADLOCK: {
793
+ code: "DEADLOCK",
794
+ name: "DBDeadlockError",
795
+ message: "Database deadlock detected.",
796
+ uiMessage: "The operation encountered a conflict. Please try again.",
797
+ httpStatus: 409
798
+ },
799
+ // Record errors
800
+ NOT_FOUND: {
801
+ code: "NOT_FOUND",
802
+ name: "DBNotFoundError",
803
+ message: "Record not found.",
804
+ uiMessage: "The requested record was not found.",
805
+ httpStatus: 404
806
+ },
807
+ // Generic
808
+ UNKNOWN: {
809
+ code: "UNKNOWN",
810
+ name: "DBErrorX",
811
+ message: "An unknown database error occurred.",
812
+ uiMessage: "A database error occurred. Please try again later."
813
+ }
814
+ };
815
+ var DBErrorX = class _DBErrorX extends ErrorX {
816
+ /**
817
+ * Database error presets for common scenarios.
818
+ */
819
+ static presets = dbPresets;
820
+ /** Default to UNKNOWN when no preset specified */
821
+ static defaultPreset = "UNKNOWN";
822
+ /** Default httpStatus for database errors (500 = server error) */
823
+ static defaults = { httpStatus: 500 };
824
+ /**
825
+ * Transform that prefixes all codes with `DB_`.
826
+ */
827
+ static transform = (opts, _ctx) => {
828
+ const code = String(opts.code ?? "UNKNOWN");
829
+ return {
830
+ ...opts,
831
+ code: code.startsWith("DB_") ? code : `DB_${code}`
832
+ };
833
+ };
834
+ static create(presetKeyOrOverrides, overrides) {
835
+ return ErrorX.create.call(
836
+ _DBErrorX,
837
+ presetKeyOrOverrides,
838
+ overrides
839
+ );
840
+ }
841
+ };
842
+
843
+ // src/presets/http-error.ts
844
+ var httpPresets = {
600
845
  // 4xx Client Errors
601
846
  400: {
602
847
  code: "BAD_REQUEST",
603
- name: "Bad Request Error",
848
+ name: "BadRequestError",
604
849
  message: "Bad request.",
605
- uiMessage: "The request could not be processed. Please check your input and try again.",
606
- metadata: { status: 400 }
850
+ uiMessage: "The request could not be processed. Please check your input and try again."
607
851
  },
608
852
  401: {
609
853
  code: "UNAUTHORIZED",
610
- name: "Unauthorized Error",
854
+ name: "UnauthorizedError",
611
855
  message: "Unauthorized.",
612
- uiMessage: "Authentication required. Please log in to continue.",
613
- metadata: { status: 401 }
856
+ uiMessage: "Authentication required. Please log in to continue."
614
857
  },
615
858
  402: {
616
859
  code: "PAYMENT_REQUIRED",
617
- name: "Payment Required Error",
860
+ name: "PaymentRequiredError",
618
861
  message: "Payment required.",
619
- uiMessage: "Payment is required to access this resource.",
620
- metadata: { status: 402 }
862
+ uiMessage: "Payment is required to access this resource."
621
863
  },
622
864
  403: {
623
865
  code: "FORBIDDEN",
624
- name: "Forbidden Error",
866
+ name: "ForbiddenError",
625
867
  message: "Forbidden.",
626
- uiMessage: "You do not have permission to access this resource.",
627
- metadata: { status: 403 }
868
+ uiMessage: "You do not have permission to access this resource."
628
869
  },
629
870
  404: {
630
871
  code: "NOT_FOUND",
631
- name: "Not Found Error",
872
+ name: "NotFoundError",
632
873
  message: "Not found.",
633
- uiMessage: "The requested resource could not be found.",
634
- metadata: { status: 404 }
874
+ uiMessage: "The requested resource could not be found."
635
875
  },
636
876
  405: {
637
877
  code: "METHOD_NOT_ALLOWED",
638
- name: "Method Not Allowed Error",
878
+ name: "MethodNotAllowedError",
639
879
  message: "Method not allowed.",
640
- uiMessage: "This action is not allowed for the requested resource.",
641
- metadata: { status: 405 }
880
+ uiMessage: "This action is not allowed for the requested resource."
642
881
  },
643
882
  406: {
644
883
  code: "NOT_ACCEPTABLE",
645
- name: "Not Acceptable Error",
884
+ name: "NotAcceptableError",
646
885
  message: "Not acceptable.",
647
- uiMessage: "The requested format is not supported.",
648
- metadata: { status: 406 }
886
+ uiMessage: "The requested format is not supported."
649
887
  },
650
888
  407: {
651
889
  code: "PROXY_AUTHENTICATION_REQUIRED",
652
- name: "Proxy Authentication Required Error",
890
+ name: "ProxyAuthenticationRequiredError",
653
891
  message: "Proxy authentication required.",
654
- uiMessage: "Proxy authentication is required to access this resource.",
655
- metadata: { status: 407 }
892
+ uiMessage: "Proxy authentication is required to access this resource."
656
893
  },
657
894
  408: {
658
895
  code: "REQUEST_TIMEOUT",
659
- name: "Request Timeout Error",
896
+ name: "RequestTimeoutError",
660
897
  message: "Request timeout.",
661
- uiMessage: "The request took too long to complete. Please try again.",
662
- metadata: { status: 408 }
898
+ uiMessage: "The request took too long to complete. Please try again."
663
899
  },
664
900
  409: {
665
901
  code: "CONFLICT",
666
- name: "Conflict Error",
902
+ name: "ConflictError",
667
903
  message: "Conflict.",
668
- uiMessage: "The request conflicts with the current state. Please refresh and try again.",
669
- metadata: { status: 409 }
904
+ uiMessage: "The request conflicts with the current state. Please refresh and try again."
670
905
  },
671
906
  410: {
672
907
  code: "GONE",
673
- name: "Gone Error",
908
+ name: "GoneError",
674
909
  message: "Gone.",
675
- uiMessage: "This resource is no longer available.",
676
- metadata: { status: 410 }
910
+ uiMessage: "This resource is no longer available."
677
911
  },
678
912
  411: {
679
913
  code: "LENGTH_REQUIRED",
680
- name: "Length Required Error",
914
+ name: "LengthRequiredError",
681
915
  message: "Length required.",
682
- uiMessage: "The request is missing required length information.",
683
- metadata: { status: 411 }
916
+ uiMessage: "The request is missing required length information."
684
917
  },
685
918
  412: {
686
919
  code: "PRECONDITION_FAILED",
687
- name: "Precondition Failed Error",
920
+ name: "PreconditionFailedError",
688
921
  message: "Precondition failed.",
689
- uiMessage: "A required condition was not met. Please try again.",
690
- metadata: { status: 412 }
922
+ uiMessage: "A required condition was not met. Please try again."
691
923
  },
692
924
  413: {
693
925
  code: "PAYLOAD_TOO_LARGE",
694
- name: "Payload Too Large Error",
926
+ name: "PayloadTooLargeError",
695
927
  message: "Payload too large.",
696
- uiMessage: "The request is too large. Please reduce the size and try again.",
697
- metadata: { status: 413 }
928
+ uiMessage: "The request is too large. Please reduce the size and try again."
698
929
  },
699
930
  414: {
700
931
  code: "URI_TOO_LONG",
701
- name: "URI Too Long Error",
932
+ name: "UriTooLongError",
702
933
  message: "URI too long.",
703
- uiMessage: "The request URL is too long.",
704
- metadata: { status: 414 }
934
+ uiMessage: "The request URL is too long."
705
935
  },
706
936
  415: {
707
937
  code: "UNSUPPORTED_MEDIA_TYPE",
708
- name: "Unsupported Media Type Error",
938
+ name: "UnsupportedMediaTypeError",
709
939
  message: "Unsupported media type.",
710
- uiMessage: "The file type is not supported.",
711
- metadata: { status: 415 }
940
+ uiMessage: "The file type is not supported."
712
941
  },
713
942
  416: {
714
943
  code: "RANGE_NOT_SATISFIABLE",
715
- name: "Range Not Satisfiable Error",
944
+ name: "RangeNotSatisfiableError",
716
945
  message: "Range not satisfiable.",
717
- uiMessage: "The requested range cannot be satisfied.",
718
- metadata: { status: 416 }
946
+ uiMessage: "The requested range cannot be satisfied."
719
947
  },
720
948
  417: {
721
949
  code: "EXPECTATION_FAILED",
722
- name: "Expectation Failed Error",
950
+ name: "ExpectationFailedError",
723
951
  message: "Expectation failed.",
724
- uiMessage: "The server cannot meet the requirements of the request.",
725
- metadata: { status: 417 }
952
+ uiMessage: "The server cannot meet the requirements of the request."
726
953
  },
727
954
  418: {
728
955
  code: "IM_A_TEAPOT",
729
- name: "Im A Teapot Error",
956
+ name: "ImATeapotError",
730
957
  message: "I'm a teapot.",
731
- uiMessage: "I'm a teapot and cannot brew coffee.",
732
- metadata: { status: 418 }
958
+ uiMessage: "I'm a teapot and cannot brew coffee."
733
959
  },
734
960
  422: {
735
961
  code: "UNPROCESSABLE_ENTITY",
736
- name: "Unprocessable Entity Error",
962
+ name: "UnprocessableEntityError",
737
963
  message: "Unprocessable entity.",
738
- uiMessage: "The request contains invalid data. Please check your input.",
739
- metadata: { status: 422 }
964
+ uiMessage: "The request contains invalid data. Please check your input."
740
965
  },
741
966
  423: {
742
967
  code: "LOCKED",
743
- name: "Locked Error",
968
+ name: "LockedError",
744
969
  message: "Locked.",
745
- uiMessage: "This resource is locked and cannot be modified.",
746
- metadata: { status: 423 }
970
+ uiMessage: "This resource is locked and cannot be modified."
747
971
  },
748
972
  424: {
749
973
  code: "FAILED_DEPENDENCY",
750
- name: "Failed Dependency Error",
974
+ name: "FailedDependencyError",
751
975
  message: "Failed dependency.",
752
- uiMessage: "The request failed due to a dependency error.",
753
- metadata: { status: 424 }
976
+ uiMessage: "The request failed due to a dependency error."
754
977
  },
755
978
  425: {
756
979
  code: "TOO_EARLY",
757
- name: "Too Early Error",
980
+ name: "TooEarlyError",
758
981
  message: "Too early.",
759
- uiMessage: "The request was sent too early. Please try again later.",
760
- metadata: { status: 425 }
982
+ uiMessage: "The request was sent too early. Please try again later."
761
983
  },
762
984
  426: {
763
985
  code: "UPGRADE_REQUIRED",
764
- name: "Upgrade Required Error",
986
+ name: "UpgradeRequiredError",
765
987
  message: "Upgrade required.",
766
- uiMessage: "Please upgrade to continue using this service.",
767
- metadata: { status: 426 }
988
+ uiMessage: "Please upgrade to continue using this service."
768
989
  },
769
990
  428: {
770
991
  code: "PRECONDITION_REQUIRED",
771
- name: "Precondition Required Error",
992
+ name: "PreconditionRequiredError",
772
993
  message: "Precondition required.",
773
- uiMessage: "Required conditions are missing from the request.",
774
- metadata: { status: 428 }
994
+ uiMessage: "Required conditions are missing from the request."
775
995
  },
776
996
  429: {
777
997
  code: "TOO_MANY_REQUESTS",
778
- name: "Too Many Requests Error",
998
+ name: "TooManyRequestsError",
779
999
  message: "Too many requests.",
780
- uiMessage: "You have made too many requests. Please wait and try again.",
781
- metadata: { status: 429 }
1000
+ uiMessage: "You have made too many requests. Please wait and try again."
782
1001
  },
783
1002
  431: {
784
1003
  code: "REQUEST_HEADER_FIELDS_TOO_LARGE",
785
- name: "Request Header Fields Too Large Error",
1004
+ name: "RequestHeaderFieldsTooLargeError",
786
1005
  message: "Request header fields too large.",
787
- uiMessage: "The request headers are too large.",
788
- metadata: { status: 431 }
1006
+ uiMessage: "The request headers are too large."
789
1007
  },
790
1008
  451: {
791
1009
  code: "UNAVAILABLE_FOR_LEGAL_REASONS",
792
- name: "Unavailable For Legal Reasons Error",
1010
+ name: "UnavailableForLegalReasonsError",
793
1011
  message: "Unavailable for legal reasons.",
794
- uiMessage: "This content is unavailable for legal reasons.",
795
- metadata: { status: 451 }
1012
+ uiMessage: "This content is unavailable for legal reasons."
796
1013
  },
797
1014
  // 5xx Server Errors
798
1015
  500: {
799
1016
  code: "INTERNAL_SERVER_ERROR",
800
- name: "Internal Server Error",
1017
+ name: "InternalServerError",
801
1018
  message: "Internal server error.",
802
- uiMessage: "An unexpected error occurred. Please try again later.",
803
- metadata: { status: 500 }
1019
+ uiMessage: "An unexpected error occurred. Please try again later."
804
1020
  },
805
1021
  501: {
806
1022
  code: "NOT_IMPLEMENTED",
807
- name: "Not Implemented Error",
1023
+ name: "NotImplementedError",
808
1024
  message: "Not implemented.",
809
- uiMessage: "This feature is not yet available.",
810
- metadata: { status: 501 }
1025
+ uiMessage: "This feature is not yet available."
811
1026
  },
812
1027
  502: {
813
1028
  code: "BAD_GATEWAY",
814
- name: "Bad Gateway Error",
1029
+ name: "BadGatewayError",
815
1030
  message: "Bad gateway.",
816
- uiMessage: "Unable to connect to the server. Please try again later.",
817
- metadata: { status: 502 }
1031
+ uiMessage: "Unable to connect to the server. Please try again later."
818
1032
  },
819
1033
  503: {
820
1034
  code: "SERVICE_UNAVAILABLE",
821
- name: "Service Unavailable Error",
1035
+ name: "ServiceUnavailableError",
822
1036
  message: "Service unavailable.",
823
- uiMessage: "The service is temporarily unavailable. Please try again later.",
824
- metadata: { status: 503 }
1037
+ uiMessage: "The service is temporarily unavailable. Please try again later."
825
1038
  },
826
1039
  504: {
827
1040
  code: "GATEWAY_TIMEOUT",
828
- name: "Gateway Timeout Error",
1041
+ name: "GatewayTimeoutError",
829
1042
  message: "Gateway timeout.",
830
- uiMessage: "The server took too long to respond. Please try again.",
831
- metadata: { status: 504 }
1043
+ uiMessage: "The server took too long to respond. Please try again."
832
1044
  },
833
1045
  505: {
834
1046
  code: "HTTP_VERSION_NOT_SUPPORTED",
835
- name: "HTTP Version Not Supported Error",
1047
+ name: "HttpVersionNotSupportedError",
836
1048
  message: "HTTP version not supported.",
837
- uiMessage: "Your browser version is not supported.",
838
- metadata: { status: 505 }
1049
+ uiMessage: "Your browser version is not supported."
839
1050
  },
840
1051
  506: {
841
1052
  code: "VARIANT_ALSO_NEGOTIATES",
842
- name: "Variant Also Negotiates Error",
1053
+ name: "VariantAlsoNegotiatesError",
843
1054
  message: "Variant also negotiates.",
844
- uiMessage: "The server has an internal configuration error.",
845
- metadata: { status: 506 }
1055
+ uiMessage: "The server has an internal configuration error."
846
1056
  },
847
1057
  507: {
848
1058
  code: "INSUFFICIENT_STORAGE",
849
- name: "Insufficient Storage Error",
1059
+ name: "InsufficientStorageError",
850
1060
  message: "Insufficient storage.",
851
- uiMessage: "The server has insufficient storage to complete the request.",
852
- metadata: { status: 507 }
1061
+ uiMessage: "The server has insufficient storage to complete the request."
853
1062
  },
854
1063
  508: {
855
1064
  code: "LOOP_DETECTED",
856
- name: "Loop Detected Error",
1065
+ name: "LoopDetectedError",
857
1066
  message: "Loop detected.",
858
- uiMessage: "The server detected an infinite loop.",
859
- metadata: { status: 508 }
1067
+ uiMessage: "The server detected an infinite loop."
860
1068
  },
861
1069
  510: {
862
1070
  code: "NOT_EXTENDED",
863
- name: "Not Extended Error",
1071
+ name: "NotExtendedError",
864
1072
  message: "Not extended.",
865
- uiMessage: "Additional extensions are required.",
866
- metadata: { status: 510 }
1073
+ uiMessage: "Additional extensions are required."
867
1074
  },
868
1075
  511: {
869
1076
  code: "NETWORK_AUTHENTICATION_REQUIRED",
870
- name: "Network Authentication Required Error",
1077
+ name: "NetworkAuthenticationRequiredError",
871
1078
  message: "Network authentication required.",
872
- uiMessage: "Network authentication is required to access this resource.",
873
- metadata: { status: 511 }
1079
+ uiMessage: "Network authentication is required to access this resource."
1080
+ }
1081
+ };
1082
+ var HTTPErrorX = class _HTTPErrorX extends ErrorX {
1083
+ /**
1084
+ * HTTP status code presets for all standard codes.
1085
+ * Keys are numeric status codes (400, 401, 404, 500, etc.)
1086
+ */
1087
+ static presets = httpPresets;
1088
+ /** Default to 500 Internal Server Error when no preset specified */
1089
+ static defaultPreset = 500;
1090
+ /** Default httpStatus for all HTTPErrorXs */
1091
+ static defaults = { httpStatus: 500 };
1092
+ /**
1093
+ * Transform that automatically sets httpStatus from the preset key.
1094
+ * Only sets httpStatus from presetKey if it matches a known preset.
1095
+ */
1096
+ static transform = (opts, { presetKey }) => ({
1097
+ ...opts,
1098
+ httpStatus: typeof presetKey === "number" && presetKey in httpPresets ? presetKey : opts.httpStatus
1099
+ });
1100
+ static create(statusCodeOrOverrides, overrides) {
1101
+ return ErrorX.create.call(
1102
+ _HTTPErrorX,
1103
+ statusCodeOrOverrides,
1104
+ overrides
1105
+ );
1106
+ }
1107
+ };
1108
+
1109
+ // src/presets/validation-error.ts
1110
+ var ValidationErrorX = class _ValidationErrorX extends ErrorX {
1111
+ /** Default httpStatus for validation errors (400 = bad request) */
1112
+ static defaults = {
1113
+ httpStatus: 400,
1114
+ name: "ValidationErrorX",
1115
+ code: "VALIDATION_ERROR",
1116
+ message: "Validation failed.",
1117
+ uiMessage: "The provided input is invalid. Please check your data."
1118
+ };
1119
+ /**
1120
+ * Transform that maps Zod issue codes to VALIDATION_ prefixed codes.
1121
+ * Converts Zod's snake_case codes to SCREAMING_SNAKE_CASE.
1122
+ */
1123
+ static transform = (opts, _ctx) => {
1124
+ const code = String(opts.code ?? "ERROR").toUpperCase();
1125
+ return {
1126
+ ...opts,
1127
+ code: code.startsWith("VALIDATION_") ? code : `VALIDATION_${code}`
1128
+ };
1129
+ };
1130
+ /**
1131
+ * Creates a ValidationErrorX from a Zod error.
1132
+ *
1133
+ * Maps Zod's error structure to ErrorX:
1134
+ * - Uses first issue's message as the error message
1135
+ * - Converts Zod issue code to ErrorX code (e.g., 'invalid_type' → 'VALIDATION_INVALID_TYPE')
1136
+ * - Captures all issues in metadata for multi-error handling
1137
+ *
1138
+ * @param zodError - The Zod error object (or any object with `issues` array)
1139
+ * @param overrides - Optional overrides for any ErrorX options
1140
+ * @returns ValidationErrorX instance
1141
+ *
1142
+ * @example
1143
+ * ```typescript
1144
+ * // Basic usage
1145
+ * try {
1146
+ * schema.parse(data)
1147
+ * } catch (err) {
1148
+ * if (err instanceof ZodError) {
1149
+ * throw ValidationErrorX.fromZodError(err)
1150
+ * }
1151
+ * }
1152
+ *
1153
+ * // With custom uiMessage
1154
+ * ValidationErrorX.fromZodError(zodError, {
1155
+ * uiMessage: 'Please fix the form errors',
1156
+ * })
1157
+ *
1158
+ * // Access all issues
1159
+ * const error = ValidationErrorX.fromZodError(zodError)
1160
+ * error.metadata?.issues?.forEach(issue => {
1161
+ * console.log(`${issue.path.join('.')}: ${issue.message}`)
1162
+ * })
1163
+ * ```
1164
+ */
1165
+ static fromZodError(zodError, overrides) {
1166
+ const firstIssue = zodError.issues[0];
1167
+ const fieldPath = firstIssue?.path.join(".");
1168
+ const zodCode = firstIssue?.code ?? "unknown";
1169
+ const errorCode = zodCode.toUpperCase().replace(/-/g, "_");
1170
+ const metadata = {
1171
+ zodCode,
1172
+ issueCount: zodError.issues.length,
1173
+ issues: zodError.issues
1174
+ };
1175
+ if (fieldPath) metadata.field = fieldPath;
1176
+ if (firstIssue?.path) metadata.path = firstIssue.path;
1177
+ if (firstIssue?.expected) metadata.expected = firstIssue.expected;
1178
+ if (firstIssue?.received) metadata.received = firstIssue.received;
1179
+ const mergedOpts = {
1180
+ ..._ValidationErrorX.defaults,
1181
+ code: errorCode,
1182
+ message: firstIssue?.message ?? "Validation failed",
1183
+ metadata,
1184
+ ...overrides
1185
+ };
1186
+ const transformedOpts = _ValidationErrorX.transform(mergedOpts, { presetKey: void 0 });
1187
+ return new _ValidationErrorX(transformedOpts);
1188
+ }
1189
+ /**
1190
+ * Creates a ValidationErrorX for a specific field.
1191
+ * Convenience method for manual validation errors.
1192
+ *
1193
+ * @param field - The field name that failed validation
1194
+ * @param message - The error message
1195
+ * @param options - Additional options
1196
+ * @returns ValidationErrorX instance
1197
+ *
1198
+ * @example
1199
+ * ```typescript
1200
+ * // Simple field error
1201
+ * throw ValidationErrorX.forField('email', 'Invalid email format')
1202
+ *
1203
+ * // With code
1204
+ * throw ValidationErrorX.forField('age', 'Must be 18 or older', {
1205
+ * code: 'TOO_YOUNG',
1206
+ * })
1207
+ * ```
1208
+ */
1209
+ static forField(field, message, options) {
1210
+ const mergedOpts = {
1211
+ ..._ValidationErrorX.defaults,
1212
+ message,
1213
+ code: options?.code ?? "INVALID_FIELD",
1214
+ metadata: {
1215
+ field,
1216
+ path: field.split("."),
1217
+ ...options?.metadata
1218
+ },
1219
+ ...options
1220
+ };
1221
+ const transformedOpts = _ValidationErrorX.transform(mergedOpts, { presetKey: void 0 });
1222
+ return new _ValidationErrorX(transformedOpts);
874
1223
  }
875
1224
  };
876
1225
 
1226
+ exports.DBErrorX = DBErrorX;
877
1227
  exports.ErrorX = ErrorX;
878
- exports.http = http;
1228
+ exports.HTTPErrorX = HTTPErrorX;
1229
+ exports.ValidationErrorX = ValidationErrorX;
879
1230
  //# sourceMappingURL=index.cjs.map
880
1231
  //# sourceMappingURL=index.cjs.map