@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/README.md +445 -278
- package/dist/index.cjs +566 -234
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1072 -431
- package/dist/index.d.ts +1072 -431
- package/dist/index.js +564 -234
- package/dist/index.js.map +1 -1
- package/package.json +3 -16
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
|
-
"
|
|
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
|
-
/**
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
|
|
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.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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 ("
|
|
423
|
-
|
|
393
|
+
if ("metadata" in error && typeof error.metadata === "object" && error.metadata !== null) {
|
|
394
|
+
metadata = error.metadata;
|
|
424
395
|
}
|
|
425
|
-
if ("
|
|
426
|
-
|
|
427
|
-
} else if ("
|
|
428
|
-
|
|
429
|
-
} else if ("
|
|
430
|
-
|
|
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 (
|
|
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(
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
|
|
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.
|
|
529
|
-
serialized.
|
|
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.
|
|
541
|
-
serialized.
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
1050
|
+
name: "NetworkAuthenticationRequiredError",
|
|
863
1051
|
message: "Network authentication required.",
|
|
864
|
-
uiMessage: "Network authentication is required to access this resource."
|
|
865
|
-
|
|
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,
|
|
1199
|
+
export { DBErrorX, ErrorX, HTTPErrorX, ValidationErrorX };
|
|
870
1200
|
//# sourceMappingURL=index.js.map
|
|
871
1201
|
//# sourceMappingURL=index.js.map
|