@nice-code/error 0.7.0 → 0.9.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/build/index.d.ts +913 -0
- package/build/index.js +1004 -933
- package/build/index.js.map +1 -0
- package/package.json +5 -4
- package/build/types/NiceError/NiceError.d.ts +0 -148
- package/build/types/NiceError/NiceError.enums.d.ts +0 -5
- package/build/types/NiceError/NiceError.types.d.ts +0 -204
- package/build/types/NiceError/NiceErrorHydrated.d.ts +0 -51
- package/build/types/NiceError/nice_error.static.d.ts +0 -2
- package/build/types/NiceErrorDefined/NiceErrorDefined.d.ts +0 -135
- package/build/types/NiceErrorDefined/defineNiceError.d.ts +0 -7
- package/build/types/NiceErrorDefined/err.d.ts +0 -59
- package/build/types/NiceErrorHandler/NiceErrorHandler.d.ts +0 -23
- package/build/types/NiceErrorHandler/NiceErrorHandler.types.d.ts +0 -82
- package/build/types/NiceErrorHandler/handleWith.d.ts +0 -8
- package/build/types/example/error_usage_example.d.ts +0 -41
- package/build/types/index.d.ts +0 -18
- package/build/types/internal/index.d.ts +0 -1
- package/build/types/internal/nice_core_errors.d.ts +0 -49
- package/build/types/test/helpers/nice_error_testing.static.d.ts +0 -3
- package/build/types/test/helpers/test_utils.d.ts +0 -1
- package/build/types/utils/castAndHydrate.d.ts +0 -35
- package/build/types/utils/castNiceError.d.ts +0 -15
- package/build/types/utils/inspectPotentialError/inspectPotentialError.d.ts +0 -2
- package/build/types/utils/inspectPotentialError/inspectPotentialError.enums.d.ts +0 -9
- package/build/types/utils/inspectPotentialError/inspectPotentialError.types.d.ts +0 -51
- package/build/types/utils/isNiceErrorObject.d.ts +0 -12
- package/build/types/utils/isRegularErrorObject.d.ts +0 -2
- package/build/types/utils/jsErrorOrCastJsError.d.ts +0 -1
- package/build/types/utils/logger.d.ts +0 -3
- package/build/types/utils/matchFirst.d.ts +0 -35
- package/build/types/utils/packError/causePack.d.ts +0 -2
- package/build/types/utils/packError/msgPack.d.ts +0 -2
- package/build/types/utils/packError/packError.d.ts +0 -3
- package/build/types/utils/packError/packError.enums.d.ts +0 -5
package/build/index.js
CHANGED
|
@@ -1,963 +1,1034 @@
|
|
|
1
|
-
// src/internal/nice_core_errors.ts
|
|
2
1
|
import { StatusCodes } from "http-status-codes";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { Logger } from "tslog";
|
|
3
|
+
//#region src/utils/jsErrorOrCastJsError.ts
|
|
5
4
|
function jsErrorOrCastJsError(error, logMessage = true) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.error(`An unknown and unstructured error was thrown: ${message}`, error);
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
...new Error(message),
|
|
17
|
-
...error
|
|
18
|
-
};
|
|
5
|
+
if (error instanceof Error) return Object.assign(error, { message: error.message });
|
|
6
|
+
const message = error?.message ?? (typeof error === "string" ? error : "No error message found");
|
|
7
|
+
if (logMessage) console.error(`An unknown and unstructured error was thrown: ${message}`, error);
|
|
8
|
+
return {
|
|
9
|
+
...new Error(message),
|
|
10
|
+
...error
|
|
11
|
+
};
|
|
19
12
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/NiceError/nice_error.static.ts
|
|
15
|
+
const DUR_OBJ_PACK_PREFIX = "NE_DUROBJ[[";
|
|
16
|
+
const DUR_OBJ_PACK_SUFFIX = "]]NE_DUROBJ";
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/utils/packError/packError.enums.ts
|
|
19
|
+
let EErrorPackType = /* @__PURE__ */ function(EErrorPackType) {
|
|
20
|
+
EErrorPackType["no_pack"] = "no_pack";
|
|
21
|
+
EErrorPackType["msg_pack"] = "msg_pack";
|
|
22
|
+
EErrorPackType["cause_pack"] = "cause_pack";
|
|
23
|
+
return EErrorPackType;
|
|
24
|
+
}({});
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/utils/packError/causePack.ts
|
|
27
|
+
const causePack = (error) => {
|
|
28
|
+
error._packedState = {
|
|
29
|
+
cause: error.cause,
|
|
30
|
+
packedAs: "cause_pack"
|
|
31
|
+
};
|
|
32
|
+
error.cause = `${DUR_OBJ_PACK_PREFIX}${JSON.stringify(error.toJsonObject())}${DUR_OBJ_PACK_SUFFIX}`;
|
|
33
|
+
return error;
|
|
38
34
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/utils/packError/msgPack.ts
|
|
37
|
+
const msgPack = (error) => {
|
|
38
|
+
error._packedState = {
|
|
39
|
+
message: error.cleanMessage,
|
|
40
|
+
packedAs: "msg_pack"
|
|
41
|
+
};
|
|
42
|
+
error.message = `${DUR_OBJ_PACK_PREFIX}${JSON.stringify(error.toJsonObject())}${DUR_OBJ_PACK_SUFFIX}`;
|
|
43
|
+
return error;
|
|
45
44
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (packType === "msg_pack") {
|
|
53
|
-
return msgPack(error);
|
|
54
|
-
}
|
|
55
|
-
return causePack(error);
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/utils/packError/packError.ts
|
|
47
|
+
const packError = (error, packType = "msg_pack") => {
|
|
48
|
+
if (packType === "no_pack") return error;
|
|
49
|
+
if (packType === "msg_pack") return msgPack(error);
|
|
50
|
+
return causePack(error);
|
|
56
51
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
wasntNice: this.wasntNice,
|
|
294
|
-
httpStatusCode: this.httpStatusCode,
|
|
295
|
-
originError: this.originError
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// src/NiceErrorDefined/NiceErrorDefined.ts
|
|
301
|
-
class NiceErrorDomain {
|
|
302
|
-
domain;
|
|
303
|
-
allDomains;
|
|
304
|
-
defaultHttpStatusCode;
|
|
305
|
-
defaultMessage;
|
|
306
|
-
_schema;
|
|
307
|
-
_definedChildNiceErrors = [];
|
|
308
|
-
_definedParentNiceError;
|
|
309
|
-
_setPack;
|
|
310
|
-
_packAsFn;
|
|
311
|
-
constructor(definition) {
|
|
312
|
-
this.domain = definition.domain;
|
|
313
|
-
this.allDomains = definition.allDomains;
|
|
314
|
-
this._schema = definition.schema;
|
|
315
|
-
if (definition.packAs != null) {
|
|
316
|
-
this._packAsFn = definition.packAs;
|
|
317
|
-
}
|
|
318
|
-
if (definition.defaultHttpStatusCode != null) {
|
|
319
|
-
this.defaultHttpStatusCode = definition.defaultHttpStatusCode;
|
|
320
|
-
}
|
|
321
|
-
if (definition.defaultMessage != null) {
|
|
322
|
-
this.defaultMessage = definition.defaultMessage;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
createChildDomain(subErrorDef) {
|
|
326
|
-
const child = new NiceErrorDomain({
|
|
327
|
-
domain: subErrorDef.domain,
|
|
328
|
-
allDomains: [subErrorDef.domain, ...this.allDomains],
|
|
329
|
-
schema: subErrorDef.schema,
|
|
330
|
-
defaultHttpStatusCode: subErrorDef.defaultHttpStatusCode,
|
|
331
|
-
defaultMessage: subErrorDef.defaultMessage
|
|
332
|
-
});
|
|
333
|
-
this.addChildNiceErrorDefined(child);
|
|
334
|
-
child.addParentNiceErrorDefined(this);
|
|
335
|
-
if (subErrorDef.packAs != null) {
|
|
336
|
-
child._packAsFn = subErrorDef.packAs;
|
|
337
|
-
} else if (this._setPack) {
|
|
338
|
-
child.packAs(this._setPack);
|
|
339
|
-
} else if (this._packAsFn) {
|
|
340
|
-
child._packAsFn = this._packAsFn;
|
|
341
|
-
}
|
|
342
|
-
return child;
|
|
343
|
-
}
|
|
344
|
-
addParentNiceErrorDefined(parentError) {
|
|
345
|
-
if (this._definedParentNiceError?.domain === parentError.domain) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
this._definedParentNiceError = {
|
|
349
|
-
domain: parentError.domain,
|
|
350
|
-
definedError: parentError
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
addChildNiceErrorDefined(child) {
|
|
354
|
-
if (this._definedChildNiceErrors.some((linked) => linked.domain === child.domain)) {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
this._definedChildNiceErrors.push({
|
|
358
|
-
domain: child.domain,
|
|
359
|
-
definedError: child
|
|
360
|
-
});
|
|
361
|
-
if (this._definedParentNiceError) {
|
|
362
|
-
this._definedParentNiceError.definedError.addChildNiceErrorDefined(child);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
packAs(pack) {
|
|
366
|
-
this._setPack = pack;
|
|
367
|
-
return this;
|
|
368
|
-
}
|
|
369
|
-
createError(input) {
|
|
370
|
-
const err = new NiceErrorHydrated(input);
|
|
371
|
-
const packType = this._setPack ?? this._packAsFn?.();
|
|
372
|
-
if (packType != null && packType !== "no_pack") {
|
|
373
|
-
return err.pack(packType);
|
|
374
|
-
}
|
|
375
|
-
return err;
|
|
376
|
-
}
|
|
377
|
-
hydrate(error) {
|
|
378
|
-
const errDef = error.def;
|
|
379
|
-
if (errDef.domain !== this.domain) {
|
|
380
|
-
throw new Error(`[NiceErrorDefined.hydrate] Domain mismatch: this definition is "${this.domain}" ` + `but the error belongs to "${errDef.domain}". ` + `Call \`niceErrorDefined.is(error)\` before hydrating to ensure compatibility.`);
|
|
381
|
-
}
|
|
382
|
-
const finalError = error instanceof NiceError ? error : new NiceError(error);
|
|
383
|
-
const reconciledErrorData = {};
|
|
384
|
-
for (const id of finalError.getIds()) {
|
|
385
|
-
const existingData = finalError.getErrorDataForId(id);
|
|
386
|
-
if (existingData == null)
|
|
387
|
-
continue;
|
|
388
|
-
let contextState = existingData.contextState;
|
|
389
|
-
if (contextState.kind === "unhydrated") {
|
|
390
|
-
const entry = this._schema[id];
|
|
391
|
-
const deserialize = entry?.context?.serialization?.fromJsonSerializable;
|
|
392
|
-
if (deserialize != null) {
|
|
393
|
-
contextState = {
|
|
394
|
-
kind: "hydrated" /* hydrated */,
|
|
395
|
-
value: deserialize(contextState.serialized),
|
|
396
|
-
serialized: contextState.serialized
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
reconciledErrorData[id] = {
|
|
401
|
-
contextState,
|
|
402
|
-
message: existingData.cleanMessage ?? existingData.message,
|
|
403
|
-
httpStatusCode: existingData.httpStatusCode,
|
|
404
|
-
timeAdded: existingData.timeAdded
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
return new NiceErrorHydrated({
|
|
408
|
-
def: this._buildDef(),
|
|
409
|
-
niceErrorDefined: this,
|
|
410
|
-
ids: finalError.ids,
|
|
411
|
-
errorData: reconciledErrorData,
|
|
412
|
-
message: finalError.cleanMessage ?? finalError.message,
|
|
413
|
-
httpStatusCode: finalError.httpStatusCode,
|
|
414
|
-
wasntNice: finalError.wasntNice,
|
|
415
|
-
originError: finalError.originError,
|
|
416
|
-
timeCreated: finalError.timeCreated
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
fromId(...args) {
|
|
420
|
-
const [id, context] = args;
|
|
421
|
-
const reconciledData = this.reconcileErrorDataForId(id, context);
|
|
422
|
-
const errorData = {};
|
|
423
|
-
errorData[id] = reconciledData;
|
|
424
|
-
const err = this.createError({
|
|
425
|
-
def: this._buildDef(),
|
|
426
|
-
niceErrorDefined: this,
|
|
427
|
-
ids: [id],
|
|
428
|
-
errorData,
|
|
429
|
-
message: reconciledData.cleanMessage ?? reconciledData.message,
|
|
430
|
-
httpStatusCode: reconciledData.httpStatusCode
|
|
431
|
-
});
|
|
432
|
-
if (typeof Error.captureStackTrace === "function") {
|
|
433
|
-
Error.captureStackTrace(err, this.fromId);
|
|
434
|
-
}
|
|
435
|
-
return err;
|
|
436
|
-
}
|
|
437
|
-
fromContext(context) {
|
|
438
|
-
const ids = Object.keys(context);
|
|
439
|
-
if (ids.length === 0) {
|
|
440
|
-
throw new Error("[NiceErrorDefined.fromContext] context object must contain at least one error id.");
|
|
441
|
-
}
|
|
442
|
-
const errorData = {};
|
|
443
|
-
for (const id of ids) {
|
|
444
|
-
errorData[id] = this.reconcileErrorDataForId(id, context[id]);
|
|
445
|
-
}
|
|
446
|
-
const primaryId = ids[0];
|
|
447
|
-
const err = this.createError({
|
|
448
|
-
def: this._buildDef(),
|
|
449
|
-
niceErrorDefined: this,
|
|
450
|
-
ids,
|
|
451
|
-
errorData,
|
|
452
|
-
message: errorData[primaryId].cleanMessage ?? errorData[primaryId].message,
|
|
453
|
-
httpStatusCode: errorData[primaryId].httpStatusCode
|
|
454
|
-
});
|
|
455
|
-
if (typeof Error.captureStackTrace === "function") {
|
|
456
|
-
Error.captureStackTrace(err, this.fromContext);
|
|
457
|
-
}
|
|
458
|
-
return err;
|
|
459
|
-
}
|
|
460
|
-
isExact(error) {
|
|
461
|
-
if (!(error instanceof NiceError))
|
|
462
|
-
return false;
|
|
463
|
-
const errDef = error.def;
|
|
464
|
-
return errDef.domain === this.domain;
|
|
465
|
-
}
|
|
466
|
-
isThisOrChild(error) {
|
|
467
|
-
if (!(error instanceof NiceError))
|
|
468
|
-
return false;
|
|
469
|
-
const errDef = error.def;
|
|
470
|
-
return errDef.domain === this.domain || this.allDomains.includes(errDef.domain);
|
|
471
|
-
}
|
|
472
|
-
isParentOf(target) {
|
|
473
|
-
const allDomains = target instanceof NiceError ? target.def.allDomains : target.allDomains;
|
|
474
|
-
return Array.isArray(allDomains) && allDomains.includes(this.domain);
|
|
475
|
-
}
|
|
476
|
-
_buildDef() {
|
|
477
|
-
return {
|
|
478
|
-
domain: this.domain,
|
|
479
|
-
allDomains: this.allDomains,
|
|
480
|
-
schema: this._schema
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
_resolveMessage(id, context) {
|
|
484
|
-
const entry = this._schema[id];
|
|
485
|
-
if (typeof entry?.message === "function") {
|
|
486
|
-
return entry.message(context);
|
|
487
|
-
}
|
|
488
|
-
if (typeof entry?.message === "string") {
|
|
489
|
-
return entry.message;
|
|
490
|
-
}
|
|
491
|
-
return this.defaultMessage ?? `[${this.domain}::${id}] An error occurred.`;
|
|
492
|
-
}
|
|
493
|
-
_resolveHttpStatusCode(id, context) {
|
|
494
|
-
const entry = this._schema[id];
|
|
495
|
-
let httpStatusCode;
|
|
496
|
-
if (typeof entry?.httpStatusCode === "function") {
|
|
497
|
-
httpStatusCode = entry.httpStatusCode(context);
|
|
498
|
-
}
|
|
499
|
-
if (typeof entry?.httpStatusCode === "number") {
|
|
500
|
-
httpStatusCode = entry.httpStatusCode;
|
|
501
|
-
}
|
|
502
|
-
return typeof httpStatusCode === "number" ? httpStatusCode : this.defaultHttpStatusCode ?? 500;
|
|
503
|
-
}
|
|
504
|
-
reconcileErrorDataForId(id, context) {
|
|
505
|
-
const message = this._resolveMessage(id, context);
|
|
506
|
-
const httpStatusCode = this._resolveHttpStatusCode(id, context);
|
|
507
|
-
const entry = this._schema[id];
|
|
508
|
-
let contextState;
|
|
509
|
-
if (context != null && entry?.context?.serialization != null) {
|
|
510
|
-
const serialized = entry.context.serialization.toJsonSerializable(context);
|
|
511
|
-
contextState = { kind: "hydrated" /* hydrated */, value: context, serialized };
|
|
512
|
-
} else {
|
|
513
|
-
contextState = { kind: "serde_unset" /* serde_unset */, value: context };
|
|
514
|
-
}
|
|
515
|
-
return { contextState, message, httpStatusCode, timeAdded: Date.now() };
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// src/NiceErrorDefined/defineNiceError.ts
|
|
520
|
-
var defineNiceError = (definition) => {
|
|
521
|
-
return new NiceErrorDomain({
|
|
522
|
-
domain: definition.domain,
|
|
523
|
-
allDomains: [definition.domain],
|
|
524
|
-
schema: definition.schema,
|
|
525
|
-
...definition.packAs != null ? { packAs: definition.packAs } : {}
|
|
526
|
-
});
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/NiceError/NiceError.ts
|
|
54
|
+
var NiceError = class extends Error {
|
|
55
|
+
name = "NiceError";
|
|
56
|
+
def;
|
|
57
|
+
/** Primary id is first entry in ids. */
|
|
58
|
+
ids;
|
|
59
|
+
wasntNice;
|
|
60
|
+
httpStatusCode;
|
|
61
|
+
timeCreated;
|
|
62
|
+
cleanMessage;
|
|
63
|
+
originError;
|
|
64
|
+
_packedState;
|
|
65
|
+
/** Internal: all active id → reconciled data pairs. */
|
|
66
|
+
_errorDataMap;
|
|
67
|
+
constructor(options) {
|
|
68
|
+
const messagePure = options.message;
|
|
69
|
+
const prefixedMessage = `[${options.def.domain}](${options.ids.join(",")}) ${messagePure}`;
|
|
70
|
+
super(prefixedMessage);
|
|
71
|
+
this.cleanMessage = messagePure;
|
|
72
|
+
this.def = options.def;
|
|
73
|
+
this.ids = options.ids;
|
|
74
|
+
this._errorDataMap = options.errorData;
|
|
75
|
+
this.wasntNice = options.wasntNice ?? false;
|
|
76
|
+
this.httpStatusCode = options.httpStatusCode ?? 500;
|
|
77
|
+
if (options.originError != null) this.originError = options.originError;
|
|
78
|
+
this.timeCreated = options.timeCreated ?? Date.now();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Type guard: returns `true` if this error was created with (or contains) the
|
|
82
|
+
* given `id`. After the guard, `getContext(id)` will be strongly typed.
|
|
83
|
+
*/
|
|
84
|
+
hasId(id) {
|
|
85
|
+
return id in this._errorDataMap;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns `true` if this error contains **at least one** of the supplied ids.
|
|
89
|
+
* Narrows `ACTIVE_IDS` to the matching subset of `IDS`.
|
|
90
|
+
*/
|
|
91
|
+
hasOneOfIds(ids) {
|
|
92
|
+
return ids.some((id) => id in this._errorDataMap);
|
|
93
|
+
}
|
|
94
|
+
/** `true` when this error was created with more than one id (via `fromContext`). */
|
|
95
|
+
get hasMultiple() {
|
|
96
|
+
return Object.keys(this._errorDataMap).length > 1;
|
|
97
|
+
}
|
|
98
|
+
/** Returns all active error ids on this instance. */
|
|
99
|
+
getIds() {
|
|
100
|
+
return Object.keys(this._errorDataMap);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Returns the typed context value for the given error id.
|
|
104
|
+
*
|
|
105
|
+
* TypeScript will only allow you to call this with an id that is part of
|
|
106
|
+
* `ACTIVE_IDS` (i.e. an id confirmed via `hasId` / `hasOneOfIds`, or passed
|
|
107
|
+
* to `fromId` / `fromContext`).
|
|
108
|
+
*
|
|
109
|
+
* @throws If the context is in the `"unhydrated"` state — the error was
|
|
110
|
+
* reconstructed from a JSON payload and its context has a custom serializer
|
|
111
|
+
* that hasn't been run yet. Call `niceErrorDefined.hydrate(error)` first.
|
|
112
|
+
*/
|
|
113
|
+
getContext(id) {
|
|
114
|
+
const state = this._errorDataMap[id]?.contextState;
|
|
115
|
+
if (state == null) return;
|
|
116
|
+
if (state.kind === "unhydrated") throw new Error(`[NiceError.getContext] Context for id "${String(id)}" is in the "unhydrated" state. The error was reconstructed from a serialized payload but has not been deserialized yet. Call \`niceErrorDefined.hydrate(error)\` to reconstruct the typed context.`);
|
|
117
|
+
return state.value;
|
|
118
|
+
}
|
|
119
|
+
getErrorDataForId(id) {
|
|
120
|
+
return this._errorDataMap[id];
|
|
121
|
+
}
|
|
122
|
+
withOriginError(error) {
|
|
123
|
+
this.originError = jsErrorOrCastJsError(error);
|
|
124
|
+
if (this._packedState?.packedAs !== "cause_pack") this.cause = this.originError;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Returns `true` if `other` has the same domain and the exact same set of
|
|
129
|
+
* active error ids as this error (order-independent).
|
|
130
|
+
*
|
|
131
|
+
* Useful for deduplication, retry logic, and asserting that two errors
|
|
132
|
+
* represent the same "kind" of problem without comparing context values.
|
|
133
|
+
*
|
|
134
|
+
* ```ts
|
|
135
|
+
* const a = err_auth.fromId("invalid_credentials", { username: "alice" });
|
|
136
|
+
* const b = err_auth.fromId("invalid_credentials", { username: "bob" });
|
|
137
|
+
* a.matches(b); // true — same domain + same id set
|
|
138
|
+
*
|
|
139
|
+
* const c = err_auth.fromId("account_locked");
|
|
140
|
+
* a.matches(c); // false — same domain, different id
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
matches(other) {
|
|
144
|
+
const myDef = this.def;
|
|
145
|
+
const otherDef = other.def;
|
|
146
|
+
if (myDef.domain !== otherDef.domain) return false;
|
|
147
|
+
const myIds = this.getIds().map(String).sort();
|
|
148
|
+
const otherIds = other.getIds().map(String).sort();
|
|
149
|
+
if (myIds.length !== otherIds.length) return false;
|
|
150
|
+
return myIds.every((id, i) => id === otherIds[i]);
|
|
151
|
+
}
|
|
152
|
+
toJsonObject() {
|
|
153
|
+
const originError = this.originError ? {
|
|
154
|
+
name: this.originError.name,
|
|
155
|
+
message: this.originError.cleanMessage ?? this.originError.message,
|
|
156
|
+
stack: this.originError.stack,
|
|
157
|
+
cause: this.originError.cause
|
|
158
|
+
} : void 0;
|
|
159
|
+
const def = {
|
|
160
|
+
domain: this.def.domain,
|
|
161
|
+
allDomains: this.def.allDomains
|
|
162
|
+
};
|
|
163
|
+
if (this.def.defaultHttpStatusCode != null) def["defaultHttpStatusCode"] = this.def.defaultHttpStatusCode;
|
|
164
|
+
if (this.def.defaultMessage != null) def["defaultMessage"] = this.def.defaultMessage;
|
|
165
|
+
const errorData = {};
|
|
166
|
+
for (const rawId of Object.keys(this._errorDataMap)) {
|
|
167
|
+
const id = rawId;
|
|
168
|
+
const data = this._errorDataMap[id];
|
|
169
|
+
if (data == null) continue;
|
|
170
|
+
let contextState;
|
|
171
|
+
if (data.contextState.kind === "hydrated") contextState = {
|
|
172
|
+
kind: "unhydrated",
|
|
173
|
+
serialized: data.contextState.serialized
|
|
174
|
+
};
|
|
175
|
+
else contextState = data.contextState;
|
|
176
|
+
errorData[id] = {
|
|
177
|
+
contextState,
|
|
178
|
+
message: data.cleanMessage ?? data.message,
|
|
179
|
+
httpStatusCode: data.httpStatusCode,
|
|
180
|
+
timeAdded: data.timeAdded
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
name: "NiceError",
|
|
185
|
+
def,
|
|
186
|
+
ids: this.ids,
|
|
187
|
+
errorData,
|
|
188
|
+
wasntNice: this.wasntNice,
|
|
189
|
+
message: this.cleanMessage,
|
|
190
|
+
httpStatusCode: this.httpStatusCode,
|
|
191
|
+
timeCreated: this.timeCreated,
|
|
192
|
+
...this.stack != null ? { stack: this.stack } : {},
|
|
193
|
+
originError
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
toJSON() {
|
|
197
|
+
return this.toJsonObject();
|
|
198
|
+
}
|
|
199
|
+
toJsonString() {
|
|
200
|
+
return JSON.stringify(this.toJsonObject());
|
|
201
|
+
}
|
|
202
|
+
toHttpResponse() {
|
|
203
|
+
return new Response(this.toJsonString(), {
|
|
204
|
+
status: this.httpStatusCode,
|
|
205
|
+
headers: { "Content-Type": "application/json" }
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
hydrate(definedNiceError) {
|
|
209
|
+
return definedNiceError.hydrate(this);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Iterates `cases` in order, finds the first whose domain matches this error
|
|
213
|
+
* (via `is()`), optionally further filters by active ids, hydrates the error,
|
|
214
|
+
* calls the handler, and returns `true`. Returns `false` if no case matched.
|
|
215
|
+
*
|
|
216
|
+
* Build cases with `forDomain` (any id in the domain) or `forIds` (specific
|
|
217
|
+
* id subset). Handlers are invoked synchronously — any returned Promise is
|
|
218
|
+
* not awaited. Use `handleWithAsync` when handlers are async.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* const handled = error.handleWith([
|
|
223
|
+
* forIds(err_feature, ["not_found"], (h) => {
|
|
224
|
+
* res.status(404).json({ missing: h.getContext("not_found").resource });
|
|
225
|
+
* }),
|
|
226
|
+
* forDomain(err_feature, (h) => {
|
|
227
|
+
* matchFirst(h, {
|
|
228
|
+
* forbidden: ({ userId }) => res.status(403).json({ userId }),
|
|
229
|
+
* _: () => res.status(500).end(),
|
|
230
|
+
* });
|
|
231
|
+
* }),
|
|
232
|
+
* forDomain(err_service, (h) => {
|
|
233
|
+
* res.status(h.httpStatusCode).json({ error: h.message });
|
|
234
|
+
* }),
|
|
235
|
+
* ]);
|
|
236
|
+
* if (!handled) next(error);
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
handleWithSync(handlerInput, handlerOptions = {}) {
|
|
240
|
+
const handlersArray = Array.isArray(handlerInput) ? handlerInput : [handlerInput];
|
|
241
|
+
for (const handler of handlersArray) {
|
|
242
|
+
const result = handler.handleErrorWithPromiseInspection(this, handlerOptions);
|
|
243
|
+
if (result.matched) {
|
|
244
|
+
if (result.isPromise) console.warn(`[NiceError.handleWith] Handler ${result.target.identifier} returned a Promise but was called via \`handleWith\` (synchronous). The Promise will not be awaited. Use \`handleWithAsync\` for async handlers.`);
|
|
245
|
+
return result.isPromise ? void 0 : result.handlerResponse;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (handlerOptions.throwOnUnhandled === true) throw this;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Same matching logic as `handleWith`, but `await`s the handler's returned
|
|
252
|
+
* Promise before resolving. Use this when your handlers perform async work
|
|
253
|
+
* (database writes, HTTP calls, etc.).
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* const handled = await error.handleWithAsync([
|
|
258
|
+
* forDomain(err_payments, async (h) => {
|
|
259
|
+
* await db.logFailedPayment(h);
|
|
260
|
+
* await notifyOps(h.message);
|
|
261
|
+
* }),
|
|
262
|
+
* ]);
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
async handleWithAsync(handlerInput, handlerOptions = {}) {
|
|
266
|
+
const handlersArray = Array.isArray(handlerInput) ? handlerInput : [handlerInput];
|
|
267
|
+
for (const handler of handlersArray) {
|
|
268
|
+
const result = handler.handleErrorWithPromiseInspection(this, handlerOptions);
|
|
269
|
+
if (result.matched) return result.isPromise ? await result.handlerPromise : result.handlerResponse;
|
|
270
|
+
}
|
|
271
|
+
if (handlerOptions.throwOnUnhandled === true) throw this;
|
|
272
|
+
}
|
|
273
|
+
get isPacked() {
|
|
274
|
+
return this._packedState != null;
|
|
275
|
+
}
|
|
276
|
+
pack(packType = "msg_pack") {
|
|
277
|
+
if (this.isPacked) return this;
|
|
278
|
+
return packError(this, packType);
|
|
279
|
+
}
|
|
280
|
+
unpack() {
|
|
281
|
+
if (this._packedState == null) return this;
|
|
282
|
+
if (this._packedState.packedAs === "msg_pack") this.message = this._packedState.message;
|
|
283
|
+
if (this._packedState.packedAs === "cause_pack") this.cause = this._packedState.cause;
|
|
284
|
+
this._packedState = void 0;
|
|
285
|
+
delete this._packedState;
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
527
288
|
};
|
|
528
|
-
|
|
529
|
-
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/NiceError/NiceErrorHydrated.ts
|
|
291
|
+
var NiceErrorHydrated = class NiceErrorHydrated extends NiceError {
|
|
292
|
+
def;
|
|
293
|
+
niceErrorDefined;
|
|
294
|
+
constructor(options) {
|
|
295
|
+
super(options);
|
|
296
|
+
this.def = options.def;
|
|
297
|
+
this.niceErrorDefined = options.niceErrorDefined;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Returns a **new** `NiceErrorHydrated` with additional id+context entries merged in.
|
|
301
|
+
* The returned error's `ACTIVE_IDS` is the union of the original ids and the
|
|
302
|
+
* newly supplied keys.
|
|
303
|
+
*
|
|
304
|
+
* ```ts
|
|
305
|
+
* const err = errDef.fromId("id_a", { a: 1 })
|
|
306
|
+
* .addContext({ id_b: { b: "x" } });
|
|
307
|
+
* err.getIds(); // ["id_a", "id_b"]
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
addContext(context) {
|
|
311
|
+
const newIds = Object.keys(context);
|
|
312
|
+
const newErrorData = {};
|
|
313
|
+
for (const id of newIds) newErrorData[id] = this.niceErrorDefined.reconcileErrorDataForId(id, context[id]);
|
|
314
|
+
const mergedErrorData = {
|
|
315
|
+
...this._errorDataMap,
|
|
316
|
+
...newErrorData
|
|
317
|
+
};
|
|
318
|
+
const mergedIds = Array.from(new Set([...this.getIds(), ...Object.keys(context)]));
|
|
319
|
+
return new NiceErrorHydrated({
|
|
320
|
+
def: this.def,
|
|
321
|
+
niceErrorDefined: this.niceErrorDefined,
|
|
322
|
+
ids: mergedIds,
|
|
323
|
+
errorData: mergedErrorData,
|
|
324
|
+
message: this.cleanMessage,
|
|
325
|
+
wasntNice: this.wasntNice,
|
|
326
|
+
httpStatusCode: this.httpStatusCode,
|
|
327
|
+
originError: this.originError
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Returns a **new** `NiceErrorHydrated` with an additional error id (and its context,
|
|
332
|
+
* if the schema requires one). Equivalent to `addContext({ [id]: context })`
|
|
333
|
+
* but mirrors the `fromId` ergonomics for single-id additions.
|
|
334
|
+
*/
|
|
335
|
+
addId(...args) {
|
|
336
|
+
const [id, context] = args;
|
|
337
|
+
const reconciledData = this.niceErrorDefined.reconcileErrorDataForId(id, context);
|
|
338
|
+
const errorDataMap = {};
|
|
339
|
+
errorDataMap[id] = reconciledData;
|
|
340
|
+
const mergedContexts = {
|
|
341
|
+
...this._errorDataMap,
|
|
342
|
+
...errorDataMap
|
|
343
|
+
};
|
|
344
|
+
const mergedIds = Array.from(new Set([...this.getIds(), id]));
|
|
345
|
+
return new NiceErrorHydrated({
|
|
346
|
+
def: this.def,
|
|
347
|
+
niceErrorDefined: this.niceErrorDefined,
|
|
348
|
+
ids: mergedIds,
|
|
349
|
+
errorData: mergedContexts,
|
|
350
|
+
message: this.cleanMessage,
|
|
351
|
+
wasntNice: this.wasntNice,
|
|
352
|
+
httpStatusCode: this.httpStatusCode,
|
|
353
|
+
originError: this.originError
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/NiceErrorDefined/NiceErrorDefined.ts
|
|
359
|
+
var NiceErrorDomain = class NiceErrorDomain {
|
|
360
|
+
domain;
|
|
361
|
+
allDomains;
|
|
362
|
+
defaultHttpStatusCode;
|
|
363
|
+
defaultMessage;
|
|
364
|
+
/** Kept for runtime use (message resolution, httpStatusCode, context serialization, etc.). */
|
|
365
|
+
_schema;
|
|
366
|
+
_definedChildNiceErrors = [];
|
|
367
|
+
_definedParentNiceError;
|
|
368
|
+
/** Set by `.packAs()` — explicit per-instance override, takes priority over `_packAsFn`. */
|
|
369
|
+
_setPack;
|
|
370
|
+
/** Set at definition time — called dynamically each time an error is created. */
|
|
371
|
+
_packAsFn;
|
|
372
|
+
constructor(definition) {
|
|
373
|
+
this.domain = definition.domain;
|
|
374
|
+
this.allDomains = definition.allDomains;
|
|
375
|
+
this._schema = definition.schema;
|
|
376
|
+
if (definition.packAs != null) this._packAsFn = definition.packAs;
|
|
377
|
+
if (definition.defaultHttpStatusCode != null) this.defaultHttpStatusCode = definition.defaultHttpStatusCode;
|
|
378
|
+
if (definition.defaultMessage != null) this.defaultMessage = definition.defaultMessage;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Creates a child domain that inherits this domain in `allDomains`.
|
|
382
|
+
* The child has its own schema and its own domain string.
|
|
383
|
+
*/
|
|
384
|
+
createChildDomain(subErrorDef) {
|
|
385
|
+
const child = new NiceErrorDomain({
|
|
386
|
+
domain: subErrorDef.domain,
|
|
387
|
+
allDomains: [subErrorDef.domain, ...this.allDomains],
|
|
388
|
+
schema: subErrorDef.schema,
|
|
389
|
+
defaultHttpStatusCode: subErrorDef.defaultHttpStatusCode,
|
|
390
|
+
defaultMessage: subErrorDef.defaultMessage
|
|
391
|
+
});
|
|
392
|
+
this.addChildNiceErrorDefined(child);
|
|
393
|
+
child.addParentNiceErrorDefined(this);
|
|
394
|
+
if (subErrorDef.packAs != null) child._packAsFn = subErrorDef.packAs;
|
|
395
|
+
else if (this._setPack) child.packAs(this._setPack);
|
|
396
|
+
else if (this._packAsFn) child._packAsFn = this._packAsFn;
|
|
397
|
+
return child;
|
|
398
|
+
}
|
|
399
|
+
addParentNiceErrorDefined(parentError) {
|
|
400
|
+
if (this._definedParentNiceError?.domain === parentError.domain) return;
|
|
401
|
+
this._definedParentNiceError = {
|
|
402
|
+
domain: parentError.domain,
|
|
403
|
+
definedError: parentError
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
addChildNiceErrorDefined(child) {
|
|
407
|
+
if (this._definedChildNiceErrors.some((linked) => linked.domain === child.domain)) return;
|
|
408
|
+
this._definedChildNiceErrors.push({
|
|
409
|
+
domain: child.domain,
|
|
410
|
+
definedError: child
|
|
411
|
+
});
|
|
412
|
+
if (this._definedParentNiceError) this._definedParentNiceError.definedError.addChildNiceErrorDefined(child);
|
|
413
|
+
}
|
|
414
|
+
packAs(pack) {
|
|
415
|
+
this._setPack = pack;
|
|
416
|
+
return this;
|
|
417
|
+
}
|
|
418
|
+
createError(input) {
|
|
419
|
+
const err = new NiceErrorHydrated(input);
|
|
420
|
+
const packType = this._setPack ?? this._packAsFn?.();
|
|
421
|
+
if (packType != null && packType !== "no_pack") return err.pack(packType);
|
|
422
|
+
return err;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Promotes a plain `NiceError<ERR_DEF>` back into a `NiceErrorHydrated` so
|
|
426
|
+
* that builder methods (`addId`, `addContext`, etc.) are available again.
|
|
427
|
+
*
|
|
428
|
+
* For each active id, if the context is in the `"unhydrated"` state (i.e. the
|
|
429
|
+
* error was reconstructed from a JSON payload), `hydrate` calls
|
|
430
|
+
* `fromJsonSerializable` to reconstruct the typed value and advances the state
|
|
431
|
+
* to `"hydrated"`. Ids already in `"hydrated"` or `"raw_unset"` state
|
|
432
|
+
* are passed through unchanged.
|
|
433
|
+
*
|
|
434
|
+
* @throws If `error.def.domain` does not match this definition's domain. Use
|
|
435
|
+
* `niceErrorDefined.is(error)` before calling `hydrate` to ensure compatibility.
|
|
436
|
+
*
|
|
437
|
+
* ```ts
|
|
438
|
+
* const raw = castNiceError(apiResponseBody);
|
|
439
|
+
*
|
|
440
|
+
* if (err_user_auth.is(raw)) {
|
|
441
|
+
* const hydrated = err_user_auth.hydrate(raw);
|
|
442
|
+
* // hydrated.getContext("invalid_credentials") — fully typed, no throw
|
|
443
|
+
* // hydrated.addId / addContext — available again
|
|
444
|
+
* }
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
447
|
+
hydrate(error) {
|
|
448
|
+
const errDef = error.def;
|
|
449
|
+
if (errDef.domain !== this.domain) throw new Error(`[NiceErrorDefined.hydrate] Domain mismatch: this definition is "${this.domain}" but the error belongs to "${errDef.domain}". Call \`niceErrorDefined.is(error)\` before hydrating to ensure compatibility.`);
|
|
450
|
+
const finalError = error instanceof NiceError ? error : new NiceError(error);
|
|
451
|
+
const reconciledErrorData = {};
|
|
452
|
+
for (const id of finalError.getIds()) {
|
|
453
|
+
const existingData = finalError.getErrorDataForId(id);
|
|
454
|
+
if (existingData == null) continue;
|
|
455
|
+
let contextState = existingData.contextState;
|
|
456
|
+
if (contextState.kind === "unhydrated") {
|
|
457
|
+
const deserialize = this._schema[id]?.context?.serialization?.fromJsonSerializable;
|
|
458
|
+
if (deserialize != null) contextState = {
|
|
459
|
+
kind: "hydrated",
|
|
460
|
+
value: deserialize(contextState.serialized),
|
|
461
|
+
serialized: contextState.serialized
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
reconciledErrorData[id] = {
|
|
465
|
+
contextState,
|
|
466
|
+
message: existingData.cleanMessage ?? existingData.message,
|
|
467
|
+
httpStatusCode: existingData.httpStatusCode,
|
|
468
|
+
timeAdded: existingData.timeAdded
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return new NiceErrorHydrated({
|
|
472
|
+
def: this._buildDef(),
|
|
473
|
+
niceErrorDefined: this,
|
|
474
|
+
ids: finalError.ids,
|
|
475
|
+
errorData: reconciledErrorData,
|
|
476
|
+
message: finalError.cleanMessage ?? finalError.message,
|
|
477
|
+
httpStatusCode: finalError.httpStatusCode,
|
|
478
|
+
wasntNice: finalError.wasntNice,
|
|
479
|
+
originError: finalError.originError,
|
|
480
|
+
timeCreated: finalError.timeCreated
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Creates a `NiceErrorHydrated` for a single error id.
|
|
485
|
+
*
|
|
486
|
+
* - `id` autocompletes to the schema keys.
|
|
487
|
+
* - The second argument `context` is required / optional / absent based on
|
|
488
|
+
* whether the schema entry declares `context.required: true`.
|
|
489
|
+
* - The returned error has `ACTIVE_IDS` narrowed to exactly `K`, so
|
|
490
|
+
* `getContext(id)` is immediately strongly typed.
|
|
491
|
+
*/
|
|
492
|
+
fromId(...args) {
|
|
493
|
+
const [id, context] = args;
|
|
494
|
+
const reconciledData = this.reconcileErrorDataForId(id, context);
|
|
495
|
+
const errorData = {};
|
|
496
|
+
errorData[id] = reconciledData;
|
|
497
|
+
const err = this.createError({
|
|
498
|
+
def: this._buildDef(),
|
|
499
|
+
niceErrorDefined: this,
|
|
500
|
+
ids: [id],
|
|
501
|
+
errorData,
|
|
502
|
+
message: reconciledData.cleanMessage ?? reconciledData.message,
|
|
503
|
+
httpStatusCode: reconciledData.httpStatusCode
|
|
504
|
+
});
|
|
505
|
+
if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(err, this.fromId);
|
|
506
|
+
return err;
|
|
507
|
+
}
|
|
508
|
+
fromContext(context) {
|
|
509
|
+
const ids = Object.keys(context);
|
|
510
|
+
if (ids.length === 0) throw new Error("[NiceErrorDefined.fromContext] context object must contain at least one error id.");
|
|
511
|
+
const errorData = {};
|
|
512
|
+
for (const id of ids) errorData[id] = this.reconcileErrorDataForId(id, context[id]);
|
|
513
|
+
const primaryId = ids[0];
|
|
514
|
+
const err = this.createError({
|
|
515
|
+
def: this._buildDef(),
|
|
516
|
+
niceErrorDefined: this,
|
|
517
|
+
ids,
|
|
518
|
+
errorData,
|
|
519
|
+
message: errorData[primaryId].cleanMessage ?? errorData[primaryId].message,
|
|
520
|
+
httpStatusCode: errorData[primaryId].httpStatusCode
|
|
521
|
+
});
|
|
522
|
+
if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(err, this.fromContext);
|
|
523
|
+
return err;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Returns `true` if `error` is a `NiceError` whose `def.domain` exactly matches
|
|
527
|
+
* this definition's domain.
|
|
528
|
+
*
|
|
529
|
+
* Use this after `castNiceError` to narrow an unknown error to this specific
|
|
530
|
+
* domain before accessing its typed ids/context:
|
|
531
|
+
*
|
|
532
|
+
* ```ts
|
|
533
|
+
* const caught = castNiceError(e);
|
|
534
|
+
*
|
|
535
|
+
* if (err_user_auth.is(caught)) {
|
|
536
|
+
* // caught is now NiceError<typeof err_user_auth's ERR_DEF>
|
|
537
|
+
* const hydrated = err_user_auth.hydrate(caught);
|
|
538
|
+
* const { username } = hydrated.getContext("invalid_credentials");
|
|
539
|
+
* }
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
isExact(error) {
|
|
543
|
+
if (!(error instanceof NiceError)) return false;
|
|
544
|
+
return error.def.domain === this.domain;
|
|
545
|
+
}
|
|
546
|
+
isThisOrChild(error) {
|
|
547
|
+
if (!(error instanceof NiceError)) return false;
|
|
548
|
+
const errDef = error.def;
|
|
549
|
+
return errDef.domain === this.domain || this.allDomains.includes(errDef.domain);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Returns `true` if this domain appears anywhere in the target's ancestry
|
|
553
|
+
* chain (including an exact domain match).
|
|
554
|
+
*
|
|
555
|
+
* Accepts either a `NiceErrorDefined` (domain definition) or a `NiceError`
|
|
556
|
+
* instance (extracts the domain from its `def`).
|
|
557
|
+
*/
|
|
558
|
+
isParentOf(target) {
|
|
559
|
+
const allDomains = target instanceof NiceError ? target.def.allDomains : target.allDomains;
|
|
560
|
+
return Array.isArray(allDomains) && allDomains.includes(this.domain);
|
|
561
|
+
}
|
|
562
|
+
_buildDef() {
|
|
563
|
+
return {
|
|
564
|
+
domain: this.domain,
|
|
565
|
+
allDomains: this.allDomains,
|
|
566
|
+
schema: this._schema
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
_resolveMessage(id, context) {
|
|
570
|
+
const entry = this._schema[id];
|
|
571
|
+
if (typeof entry?.message === "function") return entry.message(context);
|
|
572
|
+
if (typeof entry?.message === "string") return entry.message;
|
|
573
|
+
return this.defaultMessage ?? `[${this.domain}::${id}] An error occurred.`;
|
|
574
|
+
}
|
|
575
|
+
_resolveHttpStatusCode(id, context) {
|
|
576
|
+
const entry = this._schema[id];
|
|
577
|
+
let httpStatusCode;
|
|
578
|
+
if (typeof entry?.httpStatusCode === "function") httpStatusCode = entry.httpStatusCode(context);
|
|
579
|
+
if (typeof entry?.httpStatusCode === "number") httpStatusCode = entry.httpStatusCode;
|
|
580
|
+
return typeof httpStatusCode === "number" ? httpStatusCode : this.defaultHttpStatusCode ?? 500;
|
|
581
|
+
}
|
|
582
|
+
reconcileErrorDataForId(id, context) {
|
|
583
|
+
const message = this._resolveMessage(id, context);
|
|
584
|
+
const httpStatusCode = this._resolveHttpStatusCode(id, context);
|
|
585
|
+
const entry = this._schema[id];
|
|
586
|
+
let contextState;
|
|
587
|
+
if (context != null && entry?.context?.serialization != null) contextState = {
|
|
588
|
+
kind: "hydrated",
|
|
589
|
+
value: context,
|
|
590
|
+
serialized: entry.context.serialization.toJsonSerializable(context)
|
|
591
|
+
};
|
|
592
|
+
else contextState = {
|
|
593
|
+
kind: "serde_unset",
|
|
594
|
+
value: context
|
|
595
|
+
};
|
|
596
|
+
return {
|
|
597
|
+
contextState,
|
|
598
|
+
message,
|
|
599
|
+
httpStatusCode,
|
|
600
|
+
timeAdded: Date.now()
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/NiceErrorDefined/defineNiceError.ts
|
|
606
|
+
const defineNiceError = (definition) => {
|
|
607
|
+
return new NiceErrorDomain({
|
|
608
|
+
domain: definition.domain,
|
|
609
|
+
allDomains: [definition.domain],
|
|
610
|
+
schema: definition.schema,
|
|
611
|
+
...definition.packAs != null ? { packAs: definition.packAs } : {}
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/NiceErrorDefined/err.ts
|
|
530
616
|
function err(meta) {
|
|
531
|
-
|
|
617
|
+
return meta ?? {};
|
|
532
618
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region src/internal/nice_core_errors.ts
|
|
621
|
+
const err_nice = defineNiceError({
|
|
622
|
+
domain: "err_nice",
|
|
623
|
+
schema: {}
|
|
538
624
|
});
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
},
|
|
587
|
-
message: ({ jsDataValue }) => {
|
|
588
|
-
let inspectedValue;
|
|
589
|
-
try {
|
|
590
|
-
inspectedValue = JSON.stringify(jsDataValue);
|
|
591
|
-
} catch {}
|
|
592
|
-
return `An unhandled type [${typeof jsDataValue}] with value [${inspectedValue ?? "UNSERIALIZABLE"}] was encountered during casting, which is not a valid error type`;
|
|
593
|
-
}
|
|
594
|
-
})
|
|
595
|
-
}
|
|
625
|
+
let EErrId_CastNotNice = /* @__PURE__ */ function(EErrId_CastNotNice) {
|
|
626
|
+
EErrId_CastNotNice["js_error"] = "native_error";
|
|
627
|
+
EErrId_CastNotNice["js_error_like_object"] = "js_error_like_object";
|
|
628
|
+
EErrId_CastNotNice["nullish_value"] = "nullish_value";
|
|
629
|
+
EErrId_CastNotNice["js_data_type"] = "js_data_type";
|
|
630
|
+
EErrId_CastNotNice["js_other"] = "js_other";
|
|
631
|
+
return EErrId_CastNotNice;
|
|
632
|
+
}({});
|
|
633
|
+
const err_cast_not_nice = err_nice.createChildDomain({
|
|
634
|
+
domain: "err_cast_not_nice",
|
|
635
|
+
defaultHttpStatusCode: StatusCodes.UNPROCESSABLE_ENTITY,
|
|
636
|
+
schema: {
|
|
637
|
+
["native_error"]: err({
|
|
638
|
+
context: { required: true },
|
|
639
|
+
message: ({ jsError }) => `A native JavaScript Error was encountered during casting: ${jsError.message}`,
|
|
640
|
+
httpStatusCode: StatusCodes.INTERNAL_SERVER_ERROR
|
|
641
|
+
}),
|
|
642
|
+
["js_error_like_object"]: err({
|
|
643
|
+
context: { required: true },
|
|
644
|
+
message: ({ jsErrorObject }) => `An object resembling a JavaScript Error was encountered during casting: [${jsErrorObject.name}] ${jsErrorObject.message}`,
|
|
645
|
+
httpStatusCode: StatusCodes.INTERNAL_SERVER_ERROR
|
|
646
|
+
}),
|
|
647
|
+
["nullish_value"]: err({
|
|
648
|
+
context: { required: true },
|
|
649
|
+
message: ({ value }) => `A nullish value [${value === null ? "null" : "undefined"}] was encountered during casting`
|
|
650
|
+
}),
|
|
651
|
+
["js_data_type"]: err({
|
|
652
|
+
context: { required: true },
|
|
653
|
+
message: ({ jsDataType, jsDataValue }) => {
|
|
654
|
+
let inspectedValue;
|
|
655
|
+
try {
|
|
656
|
+
inspectedValue = JSON.stringify(jsDataValue);
|
|
657
|
+
} catch {}
|
|
658
|
+
return `A value of type [${jsDataType}] with value [${inspectedValue ?? "UNSERIALIZABLE"}] was encountered during casting, which is not a valid error type`;
|
|
659
|
+
}
|
|
660
|
+
}),
|
|
661
|
+
["js_other"]: err({
|
|
662
|
+
context: { required: true },
|
|
663
|
+
message: ({ jsDataValue }) => {
|
|
664
|
+
let inspectedValue;
|
|
665
|
+
try {
|
|
666
|
+
inspectedValue = JSON.stringify(jsDataValue);
|
|
667
|
+
} catch {}
|
|
668
|
+
return `An unhandled type [${typeof jsDataValue}] with value [${inspectedValue ?? "UNSERIALIZABLE"}] was encountered during casting, which is not a valid error type`;
|
|
669
|
+
}
|
|
670
|
+
})
|
|
671
|
+
}
|
|
596
672
|
});
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
673
|
+
const err_nice_handler = err_nice.createChildDomain({
|
|
674
|
+
domain: "err_nice_handler",
|
|
675
|
+
schema: {}
|
|
600
676
|
});
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region src/NiceErrorHandler/NiceErrorHandler.types.ts
|
|
679
|
+
let EErrorHandlerTargetType = /* @__PURE__ */ function(EErrorHandlerTargetType) {
|
|
680
|
+
EErrorHandlerTargetType["ids"] = "ids";
|
|
681
|
+
EErrorHandlerTargetType["domain"] = "domain";
|
|
682
|
+
EErrorHandlerTargetType["default"] = "default";
|
|
683
|
+
return EErrorHandlerTargetType;
|
|
684
|
+
}({});
|
|
685
|
+
//#endregion
|
|
686
|
+
//#region src/NiceErrorHandler/NiceErrorHandler.ts
|
|
687
|
+
var NiceErrorHandler = class {
|
|
688
|
+
handlerConfigs = [];
|
|
689
|
+
_defaultRequester;
|
|
690
|
+
handleErrorWithPromiseInspection(error, options) {
|
|
691
|
+
for (const handlerConfig of this.handlerConfigs) {
|
|
692
|
+
if (!handlerConfig._matcher(error)) continue;
|
|
693
|
+
const errorResult = handlerConfig._requester(error);
|
|
694
|
+
if (errorResult instanceof Promise) return {
|
|
695
|
+
isPromise: true,
|
|
696
|
+
matched: true,
|
|
697
|
+
target: handlerConfig.target,
|
|
698
|
+
handlerPromise: errorResult
|
|
699
|
+
};
|
|
700
|
+
return {
|
|
701
|
+
isPromise: false,
|
|
702
|
+
matched: true,
|
|
703
|
+
target: handlerConfig.target,
|
|
704
|
+
handlerResponse: errorResult
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
if (this._defaultRequester) {
|
|
708
|
+
const defaultResult = this._defaultRequester(error);
|
|
709
|
+
if (defaultResult instanceof Promise) return {
|
|
710
|
+
isPromise: true,
|
|
711
|
+
matched: true,
|
|
712
|
+
target: {
|
|
713
|
+
type: "default",
|
|
714
|
+
identifier: "[matched:default]"
|
|
715
|
+
},
|
|
716
|
+
handlerPromise: defaultResult
|
|
717
|
+
};
|
|
718
|
+
return {
|
|
719
|
+
isPromise: false,
|
|
720
|
+
matched: true,
|
|
721
|
+
target: {
|
|
722
|
+
type: "default",
|
|
723
|
+
identifier: "[matched:default]"
|
|
724
|
+
},
|
|
725
|
+
handlerResponse: defaultResult
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
if (options?.throwOnUnhandled === true) throw error;
|
|
729
|
+
return {
|
|
730
|
+
matched: false,
|
|
731
|
+
attemptedTargets: this.handlerConfigs.map((config) => config.target)
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Register a handler that fires for **any** error whose domain matches `domain`.
|
|
736
|
+
* The handler receives a fully hydrated error — `getContext`, `addId`, and `addContext`
|
|
737
|
+
* are all available. First matching case wins.
|
|
738
|
+
*/
|
|
739
|
+
forDomain(domain, handler) {
|
|
740
|
+
this.handlerConfigs.push({
|
|
741
|
+
target: {
|
|
742
|
+
type: "domain",
|
|
743
|
+
domain: domain.domain,
|
|
744
|
+
identifier: `[matched:domain:${domain.domain}]`
|
|
745
|
+
},
|
|
746
|
+
_matcher: (error) => domain.isExact(error),
|
|
747
|
+
_requester: (error) => handler(domain.hydrate(error))
|
|
748
|
+
});
|
|
749
|
+
return this;
|
|
750
|
+
}
|
|
751
|
+
forId(domain, id, handler) {
|
|
752
|
+
this.handlerConfigs.push({
|
|
753
|
+
target: {
|
|
754
|
+
type: "ids",
|
|
755
|
+
domain: domain.domain,
|
|
756
|
+
ids: [id],
|
|
757
|
+
identifier: `[matched:ids:${domain.domain}:${id}]`
|
|
758
|
+
},
|
|
759
|
+
_matcher: (error) => domain.isExact(error) && error.hasId(id),
|
|
760
|
+
_requester: (error) => handler(domain.hydrate(error))
|
|
761
|
+
});
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
764
|
+
forIds(domain, ids, handler) {
|
|
765
|
+
this.handlerConfigs.push({
|
|
766
|
+
target: {
|
|
767
|
+
type: "ids",
|
|
768
|
+
domain: domain.domain,
|
|
769
|
+
ids,
|
|
770
|
+
identifier: `[matched:ids:${domain.domain}:${ids.join(",")}]`
|
|
771
|
+
},
|
|
772
|
+
_matcher: (error) => domain.isExact(error) && ids.some((id) => error.hasId(id)),
|
|
773
|
+
_requester: (error) => handler(domain.hydrate(error))
|
|
774
|
+
});
|
|
775
|
+
return this;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Register a fallback handler that fires when no other case matches.
|
|
779
|
+
* Only one default handler can be registered — calling this twice replaces the previous one.
|
|
780
|
+
*/
|
|
781
|
+
setDefaultHandler(handler) {
|
|
782
|
+
this._defaultRequester = handler;
|
|
783
|
+
return this;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
//#endregion
|
|
787
|
+
//#region src/NiceErrorHandler/handleWith.ts
|
|
706
788
|
function forDomain(domain, handler) {
|
|
707
|
-
|
|
789
|
+
return new NiceErrorHandler().forDomain(domain, handler);
|
|
708
790
|
}
|
|
709
791
|
function forId(domain, id, handler) {
|
|
710
|
-
|
|
792
|
+
return new NiceErrorHandler().forId(domain, id, handler);
|
|
711
793
|
}
|
|
712
794
|
function forIds(domain, ids, handler) {
|
|
713
|
-
|
|
795
|
+
return new NiceErrorHandler().forIds(domain, ids, handler);
|
|
714
796
|
}
|
|
715
|
-
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region src/utils/isNiceErrorObject.ts
|
|
799
|
+
/**
|
|
800
|
+
* Returns `true` if `obj` is a JSON-serialised `NiceError` object matching the
|
|
801
|
+
* current wire format (contextState-based errorData entries).
|
|
802
|
+
*
|
|
803
|
+
* Validates:
|
|
804
|
+
* - Top-level shape (`name`, `message`, `wasntNice`, `httpStatusCode`, `def`)
|
|
805
|
+
* - Each `errorData` entry has a `contextState` with a valid `kind` discriminant
|
|
806
|
+
* (`"no_serialization"` or `"unhydrated"`) — rejecting payloads in the old
|
|
807
|
+
* format (`context` / `serialized` fields) to prevent silent data corruption.
|
|
808
|
+
*/
|
|
716
809
|
function isNiceErrorObject(obj) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return false;
|
|
738
|
-
const e = entry;
|
|
739
|
-
const state = e["contextState"];
|
|
740
|
-
if (state == null || typeof state !== "object")
|
|
741
|
-
return false;
|
|
742
|
-
const kind = state["kind"];
|
|
743
|
-
if (kind !== "serde_unset" /* serde_unset */ && kind !== "unhydrated" /* unhydrated */)
|
|
744
|
-
return false;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
return true;
|
|
810
|
+
if (typeof obj !== "object" || obj == null) return false;
|
|
811
|
+
const o = obj;
|
|
812
|
+
if (o["name"] !== "NiceError" || typeof o["message"] !== "string" || typeof o["wasntNice"] !== "boolean" || typeof o["httpStatusCode"] !== "number") return false;
|
|
813
|
+
const def = o["def"];
|
|
814
|
+
if (typeof def !== "object" || def == null) return false;
|
|
815
|
+
const d = def;
|
|
816
|
+
if (typeof d["domain"] !== "string" || !Array.isArray(d["allDomains"])) return false;
|
|
817
|
+
const errorData = o["errorData"];
|
|
818
|
+
if (errorData != null) {
|
|
819
|
+
if (typeof errorData !== "object") return false;
|
|
820
|
+
for (const entry of Object.values(errorData)) {
|
|
821
|
+
if (entry == null) continue;
|
|
822
|
+
if (typeof entry !== "object") return false;
|
|
823
|
+
const state = entry["contextState"];
|
|
824
|
+
if (state == null || typeof state !== "object") return false;
|
|
825
|
+
const kind = state["kind"];
|
|
826
|
+
if (kind !== "serde_unset" && kind !== "unhydrated") return false;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return true;
|
|
748
830
|
}
|
|
749
|
-
|
|
750
|
-
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region src/utils/isRegularErrorObject.ts
|
|
751
833
|
function isRegularErrorJsonObject(obj) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
return typeof o["name"] === "string" && typeof o["message"] === "string";
|
|
834
|
+
if (typeof obj !== "object" || obj == null) return false;
|
|
835
|
+
const o = obj;
|
|
836
|
+
return typeof o["name"] === "string" && typeof o["message"] === "string";
|
|
756
837
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
var logger_NiceError_testing = logger_NiceError.getSubLogger({
|
|
764
|
-
name: "NiceErrorTestingLogger"
|
|
765
|
-
});
|
|
766
|
-
|
|
767
|
-
// src/utils/inspectPotentialError/inspectPotentialError.ts
|
|
838
|
+
//#endregion
|
|
839
|
+
//#region src/utils/logger.ts
|
|
840
|
+
const logger_NiceError = new Logger({ name: "NiceErrorLogger" });
|
|
841
|
+
logger_NiceError.getSubLogger({ name: "NiceErrorTestingLogger" });
|
|
842
|
+
//#endregion
|
|
843
|
+
//#region src/utils/inspectPotentialError/inspectPotentialError.ts
|
|
768
844
|
function interpretMessagePackedError(parsedError) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
niceErrorObject: errorObj
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
} catch {}
|
|
787
|
-
}
|
|
788
|
-
return null;
|
|
845
|
+
let packedErrorStr;
|
|
846
|
+
if (typeof parsedError.message === "string" && parsedError.message.includes("NE_DUROBJ[[") && parsedError.message.includes("]]NE_DUROBJ")) packedErrorStr = parsedError.message;
|
|
847
|
+
if (typeof parsedError.cause === "string" && parsedError.cause.includes("NE_DUROBJ[[") && parsedError.cause.includes("]]NE_DUROBJ")) packedErrorStr = parsedError.cause;
|
|
848
|
+
if (packedErrorStr != null) {
|
|
849
|
+
const jsonStr = packedErrorStr.split(DUR_OBJ_PACK_PREFIX)[1].split(DUR_OBJ_PACK_SUFFIX)[0];
|
|
850
|
+
try {
|
|
851
|
+
const errorObj = JSON.parse(jsonStr);
|
|
852
|
+
if (isNiceErrorObject(errorObj)) return {
|
|
853
|
+
type: "niceErrorObject",
|
|
854
|
+
niceErrorObject: errorObj
|
|
855
|
+
};
|
|
856
|
+
} catch {}
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
789
859
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
type: "jsError" /* jsError */,
|
|
860
|
-
jsError: parsedError
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
if (isRegularErrorJsonObject(parsedError)) {
|
|
864
|
-
const durObjResult = interpretMessagePackedError(parsedError);
|
|
865
|
-
if (durObjResult != null) {
|
|
866
|
-
return durObjResult;
|
|
867
|
-
}
|
|
868
|
-
return {
|
|
869
|
-
type: "jsErrorObject" /* jsErrorObject */,
|
|
870
|
-
jsErrorObject: parsedError
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
return {
|
|
874
|
-
type: "jsDataType" /* jsDataType */,
|
|
875
|
-
jsDataType: "object",
|
|
876
|
-
jsDataValue: parsedError
|
|
877
|
-
};
|
|
860
|
+
const inspectPotentialError = (potentialError) => {
|
|
861
|
+
if (potentialError == null) return {
|
|
862
|
+
type: "nullish",
|
|
863
|
+
value: potentialError
|
|
864
|
+
};
|
|
865
|
+
if (typeof potentialError === "number") return {
|
|
866
|
+
type: "jsDataType",
|
|
867
|
+
jsDataType: "number",
|
|
868
|
+
jsDataValue: potentialError
|
|
869
|
+
};
|
|
870
|
+
if (typeof potentialError === "boolean") return {
|
|
871
|
+
type: "jsDataType",
|
|
872
|
+
jsDataType: "boolean",
|
|
873
|
+
jsDataValue: potentialError
|
|
874
|
+
};
|
|
875
|
+
let parsedError = potentialError;
|
|
876
|
+
if (typeof potentialError === "string") if (potentialError.includes("{") && potentialError.includes("name")) try {
|
|
877
|
+
parsedError = JSON.parse(potentialError);
|
|
878
|
+
} catch {
|
|
879
|
+
return {
|
|
880
|
+
type: "jsDataType",
|
|
881
|
+
jsDataType: "string",
|
|
882
|
+
jsDataValue: potentialError
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
else return {
|
|
886
|
+
type: "jsDataType",
|
|
887
|
+
jsDataType: "string",
|
|
888
|
+
jsDataValue: potentialError
|
|
889
|
+
};
|
|
890
|
+
if (typeof parsedError !== "object" || parsedError == null) {
|
|
891
|
+
logger_NiceError.warn({
|
|
892
|
+
message: "Received a potential error that is a primitive data type other than string, number, or boolean. This is unexpected and may indicate an issue with error handling in the code.",
|
|
893
|
+
potentialError
|
|
894
|
+
});
|
|
895
|
+
return {
|
|
896
|
+
jsDataValue: potentialError,
|
|
897
|
+
type: "jsOther"
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
if (parsedError instanceof NiceError) return {
|
|
901
|
+
type: "niceError",
|
|
902
|
+
niceError: parsedError
|
|
903
|
+
};
|
|
904
|
+
if (isNiceErrorObject(parsedError)) return {
|
|
905
|
+
type: "niceErrorObject",
|
|
906
|
+
niceErrorObject: parsedError
|
|
907
|
+
};
|
|
908
|
+
if (parsedError instanceof Error) {
|
|
909
|
+
const durObjResult = interpretMessagePackedError(parsedError);
|
|
910
|
+
if (durObjResult != null) return durObjResult;
|
|
911
|
+
return {
|
|
912
|
+
type: "jsError",
|
|
913
|
+
jsError: parsedError
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
if (isRegularErrorJsonObject(parsedError)) {
|
|
917
|
+
const durObjResult = interpretMessagePackedError(parsedError);
|
|
918
|
+
if (durObjResult != null) return durObjResult;
|
|
919
|
+
return {
|
|
920
|
+
type: "jsErrorObject",
|
|
921
|
+
jsErrorObject: parsedError
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
type: "jsDataType",
|
|
926
|
+
jsDataType: "object",
|
|
927
|
+
jsDataValue: parsedError
|
|
928
|
+
};
|
|
878
929
|
};
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
return err_cast_not_nice.fromContext({
|
|
913
|
-
["js_other" /* js_other */]: inspected
|
|
914
|
-
});
|
|
915
|
-
}
|
|
930
|
+
//#endregion
|
|
931
|
+
//#region src/utils/castNiceError.ts
|
|
932
|
+
/**
|
|
933
|
+
* Casts any unknown value into a `NiceError`.
|
|
934
|
+
*
|
|
935
|
+
* - If the value is already a `NiceError` instance, it is returned as-is.
|
|
936
|
+
* - If the value is a plain `Error`, it is wrapped with the original as `originError`.
|
|
937
|
+
* - If the value is a JSON-serialised `NiceError` object (e.g. from an API
|
|
938
|
+
* response), a best-effort `NiceError` is re-created from it.
|
|
939
|
+
* - For all other values, a generic `NiceError` is created with a descriptive
|
|
940
|
+
* message.
|
|
941
|
+
*
|
|
942
|
+
* After casting, use `NiceErrorDefined.is(error)` to narrow the error to a
|
|
943
|
+
* specific domain and access its strongly-typed ids and context.
|
|
944
|
+
*/
|
|
945
|
+
const castNiceError = (error) => {
|
|
946
|
+
const inspected = inspectPotentialError(error);
|
|
947
|
+
switch (inspected.type) {
|
|
948
|
+
case "niceError": return inspected.niceError;
|
|
949
|
+
case "niceErrorObject": {
|
|
950
|
+
const obj = inspected.niceErrorObject;
|
|
951
|
+
return new NiceError(obj);
|
|
952
|
+
}
|
|
953
|
+
case "jsError": return err_cast_not_nice.fromContext({ ["native_error"]: inspected }).withOriginError(inspected.jsError);
|
|
954
|
+
case "jsErrorObject": {
|
|
955
|
+
const err = err_cast_not_nice.fromContext({ ["js_error_like_object"]: inspected });
|
|
956
|
+
err.cause = inspected.jsErrorObject;
|
|
957
|
+
return err;
|
|
958
|
+
}
|
|
959
|
+
case "nullish": return err_cast_not_nice.fromContext({ ["nullish_value"]: inspected });
|
|
960
|
+
case "jsDataType": return err_cast_not_nice.fromContext({ ["js_data_type"]: inspected });
|
|
961
|
+
default: return err_cast_not_nice.fromContext({ ["js_other"]: inspected });
|
|
962
|
+
}
|
|
916
963
|
};
|
|
917
|
-
|
|
918
|
-
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/utils/castAndHydrate.ts
|
|
966
|
+
/**
|
|
967
|
+
* Combines `castNiceError`, `is()`, and `hydrate()` in a single call — the
|
|
968
|
+
* idiomatic way to handle an unknown value arriving from a remote boundary
|
|
969
|
+
* (API response, message queue, IPC, etc.) when you have a specific domain in mind.
|
|
970
|
+
*
|
|
971
|
+
* - Casts `value` to a `NiceError` using `castNiceError`.
|
|
972
|
+
* - If the result belongs to `niceErrorDefined`'s domain (`is()` returns `true`),
|
|
973
|
+
* hydrates it and returns a fully-typed `NiceErrorHydrated`.
|
|
974
|
+
* - Otherwise returns the raw cast `NiceError` (which may be a `wasntNice` error
|
|
975
|
+
* if `value` was not a NiceError at all).
|
|
976
|
+
*
|
|
977
|
+
* @example
|
|
978
|
+
* ```ts
|
|
979
|
+
* // In an Express error handler:
|
|
980
|
+
* app.use((err, req, res, next) => {
|
|
981
|
+
* const error = castAndHydrate(err, err_user_auth);
|
|
982
|
+
*
|
|
983
|
+
* if (err_user_auth.is(error)) {
|
|
984
|
+
* // error is NiceErrorHydrated — getContext / addId available
|
|
985
|
+
* const result = matchFirst(error, {
|
|
986
|
+
* invalid_credentials: ({ username }) => res.status(401).json({ username }),
|
|
987
|
+
* account_locked: () => res.status(403).json({ locked: true }),
|
|
988
|
+
* });
|
|
989
|
+
* if (result) return;
|
|
990
|
+
* }
|
|
991
|
+
*
|
|
992
|
+
* next(err);
|
|
993
|
+
* });
|
|
994
|
+
* ```
|
|
995
|
+
*/
|
|
919
996
|
function castAndHydrate(value, niceErrorDefined) {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
}
|
|
924
|
-
return casted;
|
|
997
|
+
const casted = castNiceError(value);
|
|
998
|
+
if (niceErrorDefined.isExact(casted)) return niceErrorDefined.hydrate(casted);
|
|
999
|
+
return casted;
|
|
925
1000
|
}
|
|
926
|
-
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/utils/matchFirst.ts
|
|
1003
|
+
/**
|
|
1004
|
+
* Pattern-matches an error against a map of id → handler functions, returning the
|
|
1005
|
+
* result of the first handler whose id is active on the error.
|
|
1006
|
+
*
|
|
1007
|
+
* - Ids are tested in the order returned by `error.getIds()`.
|
|
1008
|
+
* - If no id-specific handler matched and `_` is provided, the fallback is called.
|
|
1009
|
+
* - Returns `undefined` when neither any id handler nor the fallback fires.
|
|
1010
|
+
*
|
|
1011
|
+
* **Requires hydrated context.** If any matched id is in the `"unhydrated"` state,
|
|
1012
|
+
* `getContext` will throw. Call `niceErrorDefined.hydrate(error)` beforehand when
|
|
1013
|
+
* working with errors deserialized from a JSON payload.
|
|
1014
|
+
*
|
|
1015
|
+
* @example
|
|
1016
|
+
* ```ts
|
|
1017
|
+
* const result = matchFirst(error, {
|
|
1018
|
+
* invalid_credentials: ({ username }) => `Wrong password for ${username}`,
|
|
1019
|
+
* account_locked: () => "Account is locked",
|
|
1020
|
+
* _: () => "Unknown auth error",
|
|
1021
|
+
* });
|
|
1022
|
+
* ```
|
|
1023
|
+
*/
|
|
927
1024
|
function matchFirst(error, handlers) {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
if (typeof handlers._ === "function") {
|
|
936
|
-
return handlers._();
|
|
937
|
-
}
|
|
938
|
-
return;
|
|
1025
|
+
for (const id of error.getIds()) {
|
|
1026
|
+
const handler = handlers[id];
|
|
1027
|
+
if (typeof handler === "function") return handler(error.getContext(id));
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof handlers._ === "function") return handlers._();
|
|
939
1030
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
isNiceErrorObject,
|
|
945
|
-
forIds,
|
|
946
|
-
forId,
|
|
947
|
-
forDomain,
|
|
948
|
-
err_nice_handler,
|
|
949
|
-
err_nice,
|
|
950
|
-
err_cast_not_nice,
|
|
951
|
-
err,
|
|
952
|
-
defineNiceError,
|
|
953
|
-
causePack,
|
|
954
|
-
castNiceError,
|
|
955
|
-
castAndHydrate,
|
|
956
|
-
NiceErrorHydrated,
|
|
957
|
-
NiceErrorHandler,
|
|
958
|
-
NiceErrorDomain,
|
|
959
|
-
NiceError,
|
|
960
|
-
EErrorPackType,
|
|
961
|
-
EErrorHandlerTargetType,
|
|
962
|
-
EErrId_CastNotNice
|
|
963
|
-
};
|
|
1031
|
+
//#endregion
|
|
1032
|
+
export { EErrId_CastNotNice, EErrorHandlerTargetType, EErrorPackType, NiceError, NiceErrorDomain, NiceErrorHandler, NiceErrorHydrated, castAndHydrate, castNiceError, causePack, defineNiceError, err, err_cast_not_nice, err_nice, err_nice_handler, forDomain, forId, forIds, isNiceErrorObject, isRegularErrorJsonObject, matchFirst, msgPack };
|
|
1033
|
+
|
|
1034
|
+
//# sourceMappingURL=index.js.map
|