@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/README.md +473 -195
- package/dist/index.cjs +597 -246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1074 -436
- package/dist/index.d.ts +1074 -436
- package/dist/index.js +595 -246
- package/dist/index.js.map +1 -1
- package/package.json +3 -16
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
|
-
"
|
|
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
|
-
/**
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
|
|
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.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
|
126
|
-
* @param value - Value to convert to
|
|
127
|
-
* @returns
|
|
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
|
|
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
|
|
144
|
+
const snapshot = {
|
|
135
145
|
message: value.message
|
|
136
146
|
};
|
|
137
147
|
if (value.name) {
|
|
138
|
-
|
|
148
|
+
snapshot.name = value.name;
|
|
139
149
|
}
|
|
140
150
|
if (value.stack) {
|
|
141
|
-
|
|
151
|
+
snapshot.stack = value.stack;
|
|
142
152
|
}
|
|
143
|
-
return
|
|
153
|
+
return snapshot;
|
|
144
154
|
}
|
|
145
155
|
if (typeof value === "object") {
|
|
146
156
|
const obj = value;
|
|
147
|
-
const
|
|
157
|
+
const snapshot = {
|
|
148
158
|
message: String(obj.message || obj)
|
|
149
159
|
};
|
|
150
160
|
if (obj.name) {
|
|
151
|
-
|
|
161
|
+
snapshot.name = String(obj.name);
|
|
152
162
|
}
|
|
153
163
|
if (obj.stack) {
|
|
154
|
-
|
|
164
|
+
snapshot.stack = String(obj.stack);
|
|
155
165
|
}
|
|
156
|
-
return
|
|
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
|
-
*
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
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.
|
|
537
|
-
serialized.
|
|
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.
|
|
549
|
-
serialized.
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
1077
|
+
name: "NetworkAuthenticationRequiredError",
|
|
871
1078
|
message: "Network authentication required.",
|
|
872
|
-
uiMessage: "Network authentication is required to access this resource."
|
|
873
|
-
|
|
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.
|
|
1228
|
+
exports.HTTPErrorX = HTTPErrorX;
|
|
1229
|
+
exports.ValidationErrorX = ValidationErrorX;
|
|
879
1230
|
//# sourceMappingURL=index.cjs.map
|
|
880
1231
|
//# sourceMappingURL=index.cjs.map
|