@devp0nt/error0 1.0.0-next.47 → 1.0.0-next.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +165 -85
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +47 -16
- package/dist/cjs/plugins/cause-serialize.cjs +4 -1
- package/dist/cjs/plugins/cause-serialize.cjs.map +1 -1
- package/dist/cjs/plugins/cause-serialize.d.cts +3 -1
- package/dist/cjs/plugins/expected.cjs +7 -2
- package/dist/cjs/plugins/expected.cjs.map +1 -1
- package/dist/cjs/plugins/expected.d.cts +3 -1
- package/dist/cjs/plugins/message-merge.cjs +5 -2
- package/dist/cjs/plugins/message-merge.cjs.map +1 -1
- package/dist/cjs/plugins/message-merge.d.cts +4 -1
- package/dist/cjs/plugins/meta.cjs +7 -2
- package/dist/cjs/plugins/meta.cjs.map +1 -1
- package/dist/cjs/plugins/meta.d.cts +3 -1
- package/dist/cjs/plugins/stack-merge.cjs +6 -3
- package/dist/cjs/plugins/stack-merge.cjs.map +1 -1
- package/dist/cjs/plugins/stack-merge.d.cts +4 -1
- package/dist/cjs/plugins/tags.cjs +7 -2
- package/dist/cjs/plugins/tags.cjs.map +1 -1
- package/dist/cjs/plugins/tags.d.cts +3 -1
- package/dist/esm/index.d.ts +47 -16
- package/dist/esm/index.js +165 -85
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/plugins/cause-serialize.d.ts +3 -1
- package/dist/esm/plugins/cause-serialize.js +4 -1
- package/dist/esm/plugins/cause-serialize.js.map +1 -1
- package/dist/esm/plugins/expected.d.ts +3 -1
- package/dist/esm/plugins/expected.js +7 -2
- package/dist/esm/plugins/expected.js.map +1 -1
- package/dist/esm/plugins/message-merge.d.ts +4 -1
- package/dist/esm/plugins/message-merge.js +5 -2
- package/dist/esm/plugins/message-merge.js.map +1 -1
- package/dist/esm/plugins/meta.d.ts +3 -1
- package/dist/esm/plugins/meta.js +7 -2
- package/dist/esm/plugins/meta.js.map +1 -1
- package/dist/esm/plugins/stack-merge.d.ts +4 -1
- package/dist/esm/plugins/stack-merge.js +6 -3
- package/dist/esm/plugins/stack-merge.js.map +1 -1
- package/dist/esm/plugins/tags.d.ts +3 -1
- package/dist/esm/plugins/tags.js +7 -2
- package/dist/esm/plugins/tags.js.map +1 -1
- package/package.json +1 -1
- package/src/index.test.ts +96 -23
- package/src/index.ts +229 -101
- package/src/plugins/cause-serialize.test.ts +6 -4
- package/src/plugins/cause-serialize.ts +13 -9
- package/src/plugins/expected.test.ts +4 -4
- package/src/plugins/expected.ts +16 -10
- package/src/plugins/message-merge.test.ts +3 -3
- package/src/plugins/message-merge.ts +17 -13
- package/src/plugins/meta.test.ts +2 -2
- package/src/plugins/meta.ts +28 -22
- package/src/plugins/stack-merge.test.ts +4 -4
- package/src/plugins/stack-merge.ts +18 -14
- package/src/plugins/tags.test.ts +2 -2
- package/src/plugins/tags.ts +24 -18
package/src/index.ts
CHANGED
|
@@ -12,19 +12,31 @@ type InferPluginPropInput<TProp extends ErrorPluginPropOptions<any, any, any, an
|
|
|
12
12
|
? NormalizeUnknownToUndefined<InferFirstArg<TInit>>
|
|
13
13
|
: undefined
|
|
14
14
|
type ErrorPluginPropInit<TInputValue, TOutputValue> = ((input: TInputValue) => TOutputValue) | (() => TOutputValue)
|
|
15
|
-
type
|
|
16
|
-
|
|
15
|
+
type ErrorPluginPropSerializeOptions<
|
|
16
|
+
TOutputValue,
|
|
17
|
+
TError extends Error0,
|
|
18
|
+
TResolveValue extends TOutputValue | undefined,
|
|
19
|
+
> = {
|
|
20
|
+
own: TOutputValue | undefined
|
|
21
|
+
flow: Array<TOutputValue | undefined>
|
|
22
|
+
resolved: TResolveValue
|
|
23
|
+
error: TError
|
|
24
|
+
isPublic: boolean
|
|
25
|
+
}
|
|
26
|
+
type ErrorPluginPropSerialize<TOutputValue, TError extends Error0, TResolveValue extends TOutputValue | undefined> =
|
|
27
|
+
| ((options: ErrorPluginPropSerializeOptions<TOutputValue, TError, TResolveValue>) => unknown)
|
|
17
28
|
| false
|
|
18
29
|
type ErrorPluginPropDeserialize<TOutputValue> =
|
|
19
|
-
| ((options: { value: unknown;
|
|
30
|
+
| ((options: { value: unknown; record: Record<string, unknown> }) => TOutputValue | undefined)
|
|
20
31
|
| false
|
|
32
|
+
type ErrorPluginPropOptionsResolveOptions<TOutputValue, TError extends Error0> = {
|
|
33
|
+
own: TOutputValue | undefined
|
|
34
|
+
flow: Array<TOutputValue | undefined>
|
|
35
|
+
error: TError
|
|
36
|
+
}
|
|
21
37
|
type ErrorPluginPropOptionsBase<TOutputValue, TError extends Error0, TResolveValue extends TOutputValue | undefined> = {
|
|
22
|
-
resolve: (options:
|
|
23
|
-
|
|
24
|
-
flow: Array<TOutputValue | undefined>
|
|
25
|
-
error: TError
|
|
26
|
-
}) => TResolveValue
|
|
27
|
-
serialize: ErrorPluginPropSerialize<TResolveValue, TError>
|
|
38
|
+
resolve: (options: ErrorPluginPropOptionsResolveOptions<TOutputValue, TError>) => TResolveValue
|
|
39
|
+
serialize: ErrorPluginPropSerialize<TOutputValue, TError, TResolveValue>
|
|
28
40
|
deserialize: ErrorPluginPropDeserialize<TOutputValue>
|
|
29
41
|
}
|
|
30
42
|
type ErrorPluginPropOptionsWithInit<
|
|
@@ -180,6 +192,9 @@ type ErrorPluginResolved = {
|
|
|
180
192
|
stack?: ErrorPluginStack
|
|
181
193
|
cause?: ErrorPluginCause
|
|
182
194
|
message?: ErrorPluginMessage
|
|
195
|
+
propKeys: string[]
|
|
196
|
+
propEntries: Array<[string, ErrorPluginPropOptions<unknown>]>
|
|
197
|
+
methodEntries: Array<[string, ErrorPluginMethodFn<unknown>]>
|
|
183
198
|
}
|
|
184
199
|
const RESERVED_STACK_PROP_ERROR = 'Error0: "stack" is a reserved prop key. Use .stack(...) plugin API instead'
|
|
185
200
|
const RESERVED_MESSAGE_PROP_ERROR = 'Error0: "message" is a reserved prop key. Use .message(...) plugin API instead'
|
|
@@ -249,9 +264,16 @@ type PluginsMapFromParts<
|
|
|
249
264
|
TProps extends ErrorPluginProps,
|
|
250
265
|
TMethods extends ErrorPluginMethods,
|
|
251
266
|
> = ErrorPluginsMapOfPlugin<ErrorPlugin<TProps, TMethods>>
|
|
252
|
-
type ErrorInstanceOfMap<TMap extends ErrorPluginsMap> = Error0 &
|
|
267
|
+
type ErrorInstanceOfMap<TMap extends ErrorPluginsMap> = Error0 &
|
|
268
|
+
ErrorResolved<TMap> &
|
|
269
|
+
ErrorOwnMethods<TMap> &
|
|
270
|
+
ErrorResolveMethods<TMap> & { readonly __pluginsMap?: TMap }
|
|
253
271
|
type BuilderError0<TProps extends ErrorPluginProps, TMethods extends ErrorPluginMethods> = Error0 &
|
|
254
|
-
ErrorResolved<PluginsMapFromParts<TProps, TMethods>>
|
|
272
|
+
ErrorResolved<PluginsMapFromParts<TProps, TMethods>> &
|
|
273
|
+
ErrorOwnMethods<PluginsMapFromParts<TProps, TMethods>> &
|
|
274
|
+
ErrorResolveMethods<PluginsMapFromParts<TProps, TMethods>> & {
|
|
275
|
+
readonly __pluginsMap?: PluginsMapFromParts<TProps, TMethods>
|
|
276
|
+
}
|
|
255
277
|
|
|
256
278
|
type PluginOfBuilder<TBuilder> =
|
|
257
279
|
TBuilder extends PluginError0<infer TProps, infer TMethods> ? ErrorPlugin<TProps, TMethods> : never
|
|
@@ -386,7 +408,11 @@ export class PluginError0<
|
|
|
386
408
|
}
|
|
387
409
|
}
|
|
388
410
|
|
|
411
|
+
const OWN_SYMBOL: unique symbol = Symbol('Error0.own')
|
|
412
|
+
type ErrorOwnStore = Record<string, unknown>
|
|
413
|
+
|
|
389
414
|
export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> = {
|
|
415
|
+
MAX_CAUSES_DEPTH: number
|
|
390
416
|
new (
|
|
391
417
|
message: string,
|
|
392
418
|
input?: ErrorInput<TPluginsMap>,
|
|
@@ -404,6 +430,17 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
|
|
|
404
430
|
from: (
|
|
405
431
|
error: unknown,
|
|
406
432
|
) => Error0 & ErrorResolved<TPluginsMap> & ErrorOwnMethods<TPluginsMap> & ErrorResolveMethods<TPluginsMap>
|
|
433
|
+
round: (
|
|
434
|
+
error: unknown,
|
|
435
|
+
isPublic?: boolean,
|
|
436
|
+
) => Error0 & ErrorResolved<TPluginsMap> & ErrorOwnMethods<TPluginsMap> & ErrorResolveMethods<TPluginsMap>
|
|
437
|
+
causes: {
|
|
438
|
+
(error: unknown, instancesOnly?: false): unknown[]
|
|
439
|
+
(
|
|
440
|
+
error: unknown,
|
|
441
|
+
instancesOnly: true,
|
|
442
|
+
): Array<Error0 & ErrorResolved<TPluginsMap> & ErrorOwnMethods<TPluginsMap> & ErrorResolveMethods<TPluginsMap>>
|
|
443
|
+
}
|
|
407
444
|
resolve: (error: unknown) => ErrorResolvedProps<TPluginsMap>
|
|
408
445
|
serialize: (error: unknown, isPublic?: boolean) => Record<string, unknown>
|
|
409
446
|
own: {
|
|
@@ -465,7 +502,9 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
|
|
|
465
502
|
export class Error0 extends Error {
|
|
466
503
|
static readonly __pluginsMap?: EmptyPluginsMap
|
|
467
504
|
readonly __pluginsMap?: EmptyPluginsMap
|
|
505
|
+
static MAX_CAUSES_DEPTH = 99
|
|
468
506
|
protected static _plugins: ErrorPlugin[] = []
|
|
507
|
+
protected static _resolvedPlugin?: ErrorPluginResolved
|
|
469
508
|
|
|
470
509
|
private static readonly _emptyPlugin: ErrorPluginResolved = {
|
|
471
510
|
props: {},
|
|
@@ -474,35 +513,86 @@ export class Error0 extends Error {
|
|
|
474
513
|
stack: undefined,
|
|
475
514
|
cause: undefined,
|
|
476
515
|
message: undefined,
|
|
516
|
+
propKeys: [],
|
|
517
|
+
propEntries: [],
|
|
518
|
+
methodEntries: [],
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private static _indexResolvedPlugin(
|
|
522
|
+
resolved: Omit<ErrorPluginResolved, 'propKeys' | 'propEntries' | 'methodEntries'>,
|
|
523
|
+
): ErrorPluginResolved {
|
|
524
|
+
return {
|
|
525
|
+
...resolved,
|
|
526
|
+
propKeys: Object.keys(resolved.props),
|
|
527
|
+
propEntries: Object.entries(resolved.props),
|
|
528
|
+
methodEntries: Object.entries(resolved.methods),
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private static _applyPlugin(
|
|
533
|
+
resolved: Omit<ErrorPluginResolved, 'propKeys' | 'propEntries' | 'methodEntries'>,
|
|
534
|
+
plugin: ErrorPlugin,
|
|
535
|
+
): void {
|
|
536
|
+
if (plugin.props && 'stack' in plugin.props) {
|
|
537
|
+
throw new Error(RESERVED_STACK_PROP_ERROR)
|
|
538
|
+
}
|
|
539
|
+
if (plugin.props && 'message' in plugin.props) {
|
|
540
|
+
throw new Error(RESERVED_MESSAGE_PROP_ERROR)
|
|
541
|
+
}
|
|
542
|
+
Object.assign(resolved.props, plugin.props ?? this._emptyPlugin.props)
|
|
543
|
+
Object.assign(resolved.methods, plugin.methods ?? this._emptyPlugin.methods)
|
|
544
|
+
resolved.adapt.push(...(plugin.adapt ?? this._emptyPlugin.adapt))
|
|
545
|
+
if (typeof plugin.stack !== 'undefined') {
|
|
546
|
+
resolved.stack = plugin.stack
|
|
547
|
+
}
|
|
548
|
+
if (typeof plugin.cause !== 'undefined') {
|
|
549
|
+
resolved.cause = plugin.cause
|
|
550
|
+
}
|
|
551
|
+
if (typeof plugin.message !== 'undefined') {
|
|
552
|
+
resolved.message = plugin.message
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private static _mergeResolvedPlugin(
|
|
557
|
+
this: typeof Error0,
|
|
558
|
+
base: ErrorPluginResolved,
|
|
559
|
+
plugin: ErrorPlugin,
|
|
560
|
+
): ErrorPluginResolved {
|
|
561
|
+
const merged: Omit<ErrorPluginResolved, 'propKeys' | 'propEntries' | 'methodEntries'> = {
|
|
562
|
+
props: { ...base.props },
|
|
563
|
+
methods: { ...base.methods },
|
|
564
|
+
adapt: [...base.adapt],
|
|
565
|
+
stack: base.stack,
|
|
566
|
+
cause: base.cause,
|
|
567
|
+
message: base.message,
|
|
568
|
+
}
|
|
569
|
+
this._applyPlugin(merged, plugin)
|
|
570
|
+
return this._indexResolvedPlugin(merged)
|
|
477
571
|
}
|
|
478
572
|
|
|
479
573
|
private static _getResolvedPlugin(this: typeof Error0): ErrorPluginResolved {
|
|
574
|
+
if (Object.prototype.hasOwnProperty.call(this, '_resolvedPlugin') && this._resolvedPlugin) {
|
|
575
|
+
return this._resolvedPlugin
|
|
576
|
+
}
|
|
480
577
|
const resolved: ErrorPluginResolved = {
|
|
481
578
|
props: {},
|
|
482
579
|
methods: {},
|
|
483
580
|
adapt: [],
|
|
581
|
+
propKeys: [],
|
|
582
|
+
propEntries: [],
|
|
583
|
+
methodEntries: [],
|
|
484
584
|
}
|
|
485
585
|
for (const plugin of this._plugins) {
|
|
486
|
-
|
|
487
|
-
throw new Error(RESERVED_STACK_PROP_ERROR)
|
|
488
|
-
}
|
|
489
|
-
if (plugin.props && 'message' in plugin.props) {
|
|
490
|
-
throw new Error(RESERVED_MESSAGE_PROP_ERROR)
|
|
491
|
-
}
|
|
492
|
-
Object.assign(resolved.props, plugin.props ?? this._emptyPlugin.props)
|
|
493
|
-
Object.assign(resolved.methods, plugin.methods ?? this._emptyPlugin.methods)
|
|
494
|
-
resolved.adapt.push(...(plugin.adapt ?? this._emptyPlugin.adapt))
|
|
495
|
-
if (typeof plugin.stack !== 'undefined') {
|
|
496
|
-
resolved.stack = plugin.stack
|
|
497
|
-
}
|
|
498
|
-
if (typeof plugin.cause !== 'undefined') {
|
|
499
|
-
resolved.cause = plugin.cause
|
|
500
|
-
}
|
|
501
|
-
if (typeof plugin.message !== 'undefined') {
|
|
502
|
-
resolved.message = plugin.message
|
|
503
|
-
}
|
|
586
|
+
this._applyPlugin(resolved, plugin)
|
|
504
587
|
}
|
|
505
|
-
|
|
588
|
+
const indexed = this._indexResolvedPlugin(resolved)
|
|
589
|
+
Object.defineProperty(this, '_resolvedPlugin', {
|
|
590
|
+
value: indexed,
|
|
591
|
+
writable: true,
|
|
592
|
+
enumerable: false,
|
|
593
|
+
configurable: true,
|
|
594
|
+
})
|
|
595
|
+
return indexed
|
|
506
596
|
}
|
|
507
597
|
|
|
508
598
|
constructor(message: string, input?: ErrorInput<EmptyPluginsMap>)
|
|
@@ -520,37 +610,47 @@ export class Error0 extends Error {
|
|
|
520
610
|
|
|
521
611
|
const ctor = this.constructor as typeof Error0
|
|
522
612
|
const plugin = ctor._getResolvedPlugin()
|
|
613
|
+
const ownStore = Object.create(null) as ErrorOwnStore
|
|
614
|
+
Object.defineProperty(this, OWN_SYMBOL, { value: ownStore, writable: true, enumerable: false, configurable: true })
|
|
523
615
|
|
|
524
|
-
for (const [key, prop] of
|
|
616
|
+
for (const [key, prop] of plugin.propEntries) {
|
|
525
617
|
if (key === 'stack') {
|
|
526
618
|
continue
|
|
527
619
|
}
|
|
620
|
+
Object.defineProperty(this, key, {
|
|
621
|
+
get: () =>
|
|
622
|
+
prop.resolve({
|
|
623
|
+
own: ownStore[key],
|
|
624
|
+
flow: this.flow(key as never),
|
|
625
|
+
error: this,
|
|
626
|
+
}),
|
|
627
|
+
set: (value) => {
|
|
628
|
+
ownStore[key] = value
|
|
629
|
+
},
|
|
630
|
+
enumerable: true,
|
|
631
|
+
configurable: true,
|
|
632
|
+
})
|
|
528
633
|
if (key in input) {
|
|
529
634
|
const ownValue = (input as Record<string, unknown>)[key]
|
|
530
|
-
|
|
531
|
-
;(this as Record<string, unknown>)[key] = prop.init(ownValue)
|
|
532
|
-
} else {
|
|
533
|
-
;(this as Record<string, unknown>)[key] = ownValue
|
|
534
|
-
}
|
|
535
|
-
} else {
|
|
536
|
-
Object.defineProperty(this, key, {
|
|
537
|
-
get: () => prop.resolve({ own: undefined, flow: this.flow(key), error: this }),
|
|
538
|
-
set: (value) => {
|
|
539
|
-
Object.defineProperty(this, key, {
|
|
540
|
-
value,
|
|
541
|
-
writable: true,
|
|
542
|
-
enumerable: true,
|
|
543
|
-
configurable: true,
|
|
544
|
-
})
|
|
545
|
-
},
|
|
546
|
-
enumerable: true,
|
|
547
|
-
configurable: true,
|
|
548
|
-
})
|
|
635
|
+
ownStore[key] = typeof prop.init === 'function' ? prop.init(ownValue) : ownValue
|
|
549
636
|
}
|
|
550
637
|
}
|
|
551
638
|
}
|
|
552
639
|
|
|
553
|
-
private static
|
|
640
|
+
private static _getOwnStore(object: object): ErrorOwnStore | undefined {
|
|
641
|
+
const record = object as Record<string | symbol, unknown>
|
|
642
|
+
const existing = record[OWN_SYMBOL]
|
|
643
|
+
if (existing && typeof existing === 'object') {
|
|
644
|
+
return existing as ErrorOwnStore
|
|
645
|
+
}
|
|
646
|
+
return undefined
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private static readonly isOwnProperty = (object: object, key: string): boolean => {
|
|
650
|
+
const ownStore = this._getOwnStore(object)
|
|
651
|
+
if (ownStore && Object.prototype.hasOwnProperty.call(ownStore, key)) {
|
|
652
|
+
return true
|
|
653
|
+
}
|
|
554
654
|
const d = Object.getOwnPropertyDescriptor(object, key)
|
|
555
655
|
if (!d) return false
|
|
556
656
|
if (typeof d.get === 'function' || typeof d.set === 'function') {
|
|
@@ -563,15 +663,22 @@ export class Error0 extends Error {
|
|
|
563
663
|
return true
|
|
564
664
|
}
|
|
565
665
|
private static _ownByKey(error: object, key: string): unknown {
|
|
566
|
-
|
|
666
|
+
const ownStore = this._getOwnStore(error)
|
|
667
|
+
if (ownStore && Object.prototype.hasOwnProperty.call(ownStore, key)) {
|
|
668
|
+
return ownStore[key]
|
|
669
|
+
}
|
|
670
|
+
if (this.isOwnProperty(error, key)) {
|
|
567
671
|
return (error as Record<string, unknown>)[key]
|
|
568
672
|
}
|
|
569
673
|
return undefined
|
|
570
674
|
}
|
|
571
675
|
private static _flowByKey(error: object, key: string): unknown[] {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
676
|
+
const causes = this.causes(error, true)
|
|
677
|
+
const values = new Array<unknown>(causes.length)
|
|
678
|
+
for (let i = 0; i < causes.length; i += 1) {
|
|
679
|
+
values[i] = this._ownByKey(causes[i], key)
|
|
680
|
+
}
|
|
681
|
+
return values
|
|
575
682
|
}
|
|
576
683
|
|
|
577
684
|
static own<TThis extends typeof Error0>(this: TThis, error: unknown): ErrorOwnProps<PluginsMapOf<TThis>>
|
|
@@ -585,7 +692,7 @@ export class Error0 extends Error {
|
|
|
585
692
|
if (key === undefined) {
|
|
586
693
|
const ownValues: Record<string, unknown> = {}
|
|
587
694
|
const plugin = this._getResolvedPlugin()
|
|
588
|
-
for (const ownKey of
|
|
695
|
+
for (const ownKey of plugin.propKeys) {
|
|
589
696
|
ownValues[ownKey] = this._ownByKey(error0, ownKey)
|
|
590
697
|
}
|
|
591
698
|
return ownValues
|
|
@@ -610,7 +717,6 @@ export class Error0 extends Error {
|
|
|
610
717
|
error: unknown,
|
|
611
718
|
key: TKey,
|
|
612
719
|
): Array<ErrorOwnProps<PluginsMapOf<TThis>>[TKey]>
|
|
613
|
-
static flow(error: unknown, key: string): unknown[]
|
|
614
720
|
static flow(error: unknown, key: string): unknown[] {
|
|
615
721
|
const error0 = this.from(error)
|
|
616
722
|
return this._flowByKey(error0, key)
|
|
@@ -619,45 +725,48 @@ export class Error0 extends Error {
|
|
|
619
725
|
this: TThis,
|
|
620
726
|
key: TKey,
|
|
621
727
|
): Array<ErrorOwnProps<PluginsMapOfInstance<TThis>>[TKey]>
|
|
622
|
-
flow(key: string): unknown[]
|
|
623
728
|
flow(key: string): unknown[] {
|
|
624
729
|
const ctor = this.constructor as typeof Error0
|
|
625
730
|
return ctor._flowByKey(this, key)
|
|
626
731
|
}
|
|
627
732
|
|
|
733
|
+
static _resolveByKey(error: Error0, key: string, plugin: ErrorPluginResolved): unknown {
|
|
734
|
+
try {
|
|
735
|
+
const options = {
|
|
736
|
+
get own() {
|
|
737
|
+
return error.own(key as never)
|
|
738
|
+
},
|
|
739
|
+
get flow() {
|
|
740
|
+
return error.flow(key as never)
|
|
741
|
+
},
|
|
742
|
+
error,
|
|
743
|
+
}
|
|
744
|
+
const prop = plugin.props[key]
|
|
745
|
+
const resolver = prop.resolve
|
|
746
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
747
|
+
if (!resolver) {
|
|
748
|
+
return (error as any)[key]
|
|
749
|
+
}
|
|
750
|
+
return resolver(options as ErrorPluginPropOptionsResolveOptions<any, any>)
|
|
751
|
+
} catch {
|
|
752
|
+
// eslint-disable-next-line no-console
|
|
753
|
+
console.error(`Error0: failed to resolve property ${key}`, error)
|
|
754
|
+
return undefined
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
628
758
|
static resolve<TThis extends typeof Error0>(this: TThis, error: unknown): ErrorResolvedProps<PluginsMapOf<TThis>>
|
|
629
759
|
static resolve(error: unknown): Record<string, unknown>
|
|
630
760
|
static resolve(error: unknown): Record<string, unknown> {
|
|
631
761
|
const error0 = this.from(error)
|
|
632
762
|
const resolved: Record<string, unknown> = {}
|
|
633
763
|
const plugin = this._getResolvedPlugin()
|
|
634
|
-
for (const
|
|
635
|
-
|
|
636
|
-
// resolved[key] = (error0 as unknown as Record<string, unknown>)[key]
|
|
637
|
-
const options = Object.defineProperties(
|
|
638
|
-
{
|
|
639
|
-
error: error0,
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
own: {
|
|
643
|
-
get: () => error0.own(key as never),
|
|
644
|
-
},
|
|
645
|
-
flow: {
|
|
646
|
-
get: () => error0.flow(key),
|
|
647
|
-
},
|
|
648
|
-
},
|
|
649
|
-
)
|
|
650
|
-
resolved[key] = prop.resolve(options as never)
|
|
651
|
-
// resolved[key] = prop.resolve({ own: error0.own(key as never), flow: error0.flow(key), error: error0 })
|
|
652
|
-
} catch {
|
|
653
|
-
// eslint-disable-next-line no-console
|
|
654
|
-
console.error(`Error0: failed to resolve property ${key}`, error0)
|
|
655
|
-
}
|
|
764
|
+
for (const key of plugin.propKeys) {
|
|
765
|
+
resolved[key] = this._resolveByKey(error0, key, plugin)
|
|
656
766
|
}
|
|
657
767
|
return resolved
|
|
658
768
|
}
|
|
659
769
|
resolve<TThis extends Error0>(this: TThis): ErrorResolvedProps<PluginsMapOfInstance<TThis>>
|
|
660
|
-
resolve(): Record<string, unknown>
|
|
661
770
|
resolve(): Record<string, unknown> {
|
|
662
771
|
const ctor = this.constructor as typeof Error0
|
|
663
772
|
return ctor.resolve(this)
|
|
@@ -668,9 +777,9 @@ export class Error0 extends Error {
|
|
|
668
777
|
static causes(error: unknown, instancesOnly?: boolean): unknown[] {
|
|
669
778
|
const causes: unknown[] = []
|
|
670
779
|
let current: unknown = error
|
|
671
|
-
const maxDepth = 99
|
|
672
780
|
const seen = new Set<unknown>()
|
|
673
|
-
|
|
781
|
+
let depth = 0
|
|
782
|
+
while (depth < this.MAX_CAUSES_DEPTH) {
|
|
674
783
|
if (seen.has(current)) {
|
|
675
784
|
break
|
|
676
785
|
}
|
|
@@ -682,6 +791,7 @@ export class Error0 extends Error {
|
|
|
682
791
|
break
|
|
683
792
|
}
|
|
684
793
|
current = (current as { cause?: unknown }).cause
|
|
794
|
+
depth += 1
|
|
685
795
|
}
|
|
686
796
|
return causes
|
|
687
797
|
}
|
|
@@ -713,6 +823,10 @@ export class Error0 extends Error {
|
|
|
713
823
|
return this._fromNonError0(error)
|
|
714
824
|
}
|
|
715
825
|
|
|
826
|
+
static round(error: unknown, isPublic = false): Error0 {
|
|
827
|
+
return this.from(this.serialize(error, isPublic))
|
|
828
|
+
}
|
|
829
|
+
|
|
716
830
|
private static _applyAdapt(error: Error0): Error0 {
|
|
717
831
|
const plugin = this._getResolvedPlugin()
|
|
718
832
|
for (const adapt of plugin.adapt) {
|
|
@@ -732,8 +846,7 @@ export class Error0 extends Error {
|
|
|
732
846
|
const errorRecord = error as Record<string, unknown>
|
|
733
847
|
const recreated = new this(message)
|
|
734
848
|
const plugin = this._getResolvedPlugin()
|
|
735
|
-
const
|
|
736
|
-
for (const [key, prop] of propsEntries) {
|
|
849
|
+
for (const [key, prop] of plugin.propEntries) {
|
|
737
850
|
if (prop.deserialize === false) {
|
|
738
851
|
continue
|
|
739
852
|
}
|
|
@@ -741,7 +854,7 @@ export class Error0 extends Error {
|
|
|
741
854
|
continue
|
|
742
855
|
}
|
|
743
856
|
try {
|
|
744
|
-
const value = prop.deserialize({ value: errorRecord[key],
|
|
857
|
+
const value = prop.deserialize({ value: errorRecord[key], record: errorRecord })
|
|
745
858
|
;(recreated as unknown as Record<string, unknown>)[key] = value
|
|
746
859
|
} catch {
|
|
747
860
|
// eslint-disable-next-line no-console
|
|
@@ -798,9 +911,9 @@ export class Error0 extends Error {
|
|
|
798
911
|
const Base = this as unknown as typeof Error0
|
|
799
912
|
const Error0Extended = class Error0 extends Base {}
|
|
800
913
|
;(Error0Extended as typeof Error0)._plugins = [...Base._plugins, plugin]
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
for (const [key, method] of
|
|
914
|
+
const resolved = this._mergeResolvedPlugin(Base._getResolvedPlugin(), plugin)
|
|
915
|
+
;(Error0Extended as typeof Error0)._resolvedPlugin = resolved
|
|
916
|
+
for (const [key, method] of resolved.methodEntries) {
|
|
804
917
|
Object.defineProperty((Error0Extended as typeof Error0).prototype, key, {
|
|
805
918
|
value: function (...args: unknown[]) {
|
|
806
919
|
return method(this as Error0, ...args)
|
|
@@ -997,9 +1110,9 @@ export class Error0 extends Error {
|
|
|
997
1110
|
|
|
998
1111
|
static serialize(error: unknown, isPublic = true): Record<string, unknown> {
|
|
999
1112
|
const error0 = this.from(error)
|
|
1000
|
-
const resolvedProps = this.resolve(error0)
|
|
1001
|
-
const resolvedRecord = resolvedProps as Record<string, unknown>
|
|
1002
1113
|
const plugin = this._getResolvedPlugin()
|
|
1114
|
+
const resolveByKey = (targetError: Error0, key: string, targetPlugin: ErrorPluginResolved): unknown =>
|
|
1115
|
+
this._resolveByKey(targetError, key, targetPlugin)
|
|
1003
1116
|
const messagePlugin = plugin.message
|
|
1004
1117
|
let serializedMessage: unknown = error0.message
|
|
1005
1118
|
try {
|
|
@@ -1013,25 +1126,38 @@ export class Error0 extends Error {
|
|
|
1013
1126
|
}
|
|
1014
1127
|
const json: Record<string, unknown> = {
|
|
1015
1128
|
name: error0.name,
|
|
1016
|
-
message: serializedMessage,
|
|
1017
1129
|
// we do not serialize causes, it is enough that we have floated props and adapt helper
|
|
1018
1130
|
// cause: error0.cause,
|
|
1019
1131
|
}
|
|
1132
|
+
if (serializedMessage !== undefined) {
|
|
1133
|
+
json.message = serializedMessage
|
|
1134
|
+
}
|
|
1020
1135
|
|
|
1021
|
-
const
|
|
1022
|
-
for (const [key, prop] of propsEntries) {
|
|
1136
|
+
for (const [key, prop] of plugin.propEntries) {
|
|
1023
1137
|
if (prop.serialize === false) {
|
|
1024
1138
|
continue
|
|
1025
1139
|
}
|
|
1026
1140
|
try {
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1141
|
+
const options = {
|
|
1142
|
+
get own() {
|
|
1143
|
+
return error0.own(key as never)
|
|
1144
|
+
},
|
|
1145
|
+
get flow() {
|
|
1146
|
+
return error0.flow(key as never)
|
|
1147
|
+
},
|
|
1148
|
+
get resolved() {
|
|
1149
|
+
return resolveByKey(error0, key, plugin)
|
|
1150
|
+
},
|
|
1151
|
+
error: error0,
|
|
1152
|
+
isPublic,
|
|
1153
|
+
}
|
|
1154
|
+
const jsonValue = prop.serialize(options as ErrorPluginPropSerializeOptions<any, any, any>)
|
|
1029
1155
|
if (jsonValue !== undefined) {
|
|
1030
1156
|
json[key] = jsonValue
|
|
1031
1157
|
}
|
|
1032
1158
|
} catch {
|
|
1033
1159
|
// eslint-disable-next-line no-console
|
|
1034
|
-
console.error(`Error0: failed to serialize property ${key}`,
|
|
1160
|
+
console.error(`Error0: failed to serialize property ${key}`, error0)
|
|
1035
1161
|
}
|
|
1036
1162
|
}
|
|
1037
1163
|
const stackPlugin = plugin.stack
|
|
@@ -1065,14 +1191,16 @@ export class Error0 extends Error {
|
|
|
1065
1191
|
console.error('Error0: failed to serialize cause', error0)
|
|
1066
1192
|
}
|
|
1067
1193
|
}
|
|
1068
|
-
return
|
|
1069
|
-
string,
|
|
1070
|
-
unknown
|
|
1071
|
-
>
|
|
1194
|
+
return json
|
|
1072
1195
|
}
|
|
1073
1196
|
|
|
1074
1197
|
serialize(isPublic = true): Record<string, unknown> {
|
|
1075
1198
|
const ctor = this.constructor as typeof Error0
|
|
1076
1199
|
return ctor.serialize(this, isPublic)
|
|
1077
1200
|
}
|
|
1201
|
+
|
|
1202
|
+
round<TThis extends Error0>(this: TThis, isPublic = true): TThis {
|
|
1203
|
+
const ctor = this.constructor as typeof Error0
|
|
1204
|
+
return ctor.round(this, isPublic) as TThis
|
|
1205
|
+
}
|
|
1078
1206
|
}
|
|
@@ -6,7 +6,7 @@ describe('causeSerializePlugin', () => {
|
|
|
6
6
|
const statusPlugin = Error0.plugin().use('prop', 'status', {
|
|
7
7
|
init: (input: number) => input,
|
|
8
8
|
resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
|
|
9
|
-
serialize: ({
|
|
9
|
+
serialize: ({ resolved }) => resolved,
|
|
10
10
|
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
11
11
|
})
|
|
12
12
|
|
|
@@ -14,14 +14,16 @@ describe('causeSerializePlugin', () => {
|
|
|
14
14
|
type Code = (typeof codes)[number]
|
|
15
15
|
const codePlugin = Error0.plugin().use('prop', 'code', {
|
|
16
16
|
init: (input: Code) => input,
|
|
17
|
-
resolve: ({ flow }) => flow.find((value) => typeof value === 'string' && codes.includes(value
|
|
18
|
-
serialize: ({
|
|
17
|
+
resolve: ({ flow }) => flow.find((value) => typeof value === 'string' && codes.includes(value)),
|
|
18
|
+
serialize: ({ resolved, isPublic }) => (isPublic ? undefined : resolved),
|
|
19
19
|
deserialize: ({ value }) =>
|
|
20
20
|
typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
it('serializes and deserializes nested Error0 causes', () => {
|
|
24
|
-
const AppError = Error0.use(statusPlugin)
|
|
24
|
+
const AppError = Error0.use(statusPlugin)
|
|
25
|
+
.use(codePlugin)
|
|
26
|
+
.use(causeSerializePlugin({ hideWhenPublic: false }))
|
|
25
27
|
const deepCauseError = new AppError('deep cause')
|
|
26
28
|
const causeError = new AppError('cause', { status: 409, code: 'NOT_FOUND', cause: deepCauseError })
|
|
27
29
|
const error = new AppError('root', { status: 500, cause: causeError })
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Error0 } from '../index.js'
|
|
2
2
|
|
|
3
|
-
export const causeSerializePlugin =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
export const causeSerializePlugin = ({ hideWhenPublic = true }: { hideWhenPublic?: boolean } = {}) =>
|
|
4
|
+
Error0.plugin().use('cause', {
|
|
5
|
+
serialize: ({ value, error, isPublic }) => {
|
|
6
|
+
if (hideWhenPublic && isPublic) {
|
|
7
|
+
return undefined
|
|
8
|
+
}
|
|
9
|
+
const ctor = error.constructor as typeof Error0
|
|
10
|
+
if (ctor.is(value)) {
|
|
11
|
+
return ctor.serialize(value, isPublic)
|
|
12
|
+
}
|
|
13
|
+
return undefined
|
|
14
|
+
},
|
|
15
|
+
})
|
|
@@ -6,12 +6,12 @@ describe('expectedPlugin', () => {
|
|
|
6
6
|
const statusPlugin = Error0.plugin().use('prop', 'status', {
|
|
7
7
|
init: (input: number) => input,
|
|
8
8
|
resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
|
|
9
|
-
serialize: ({
|
|
9
|
+
serialize: ({ resolved }) => resolved,
|
|
10
10
|
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
it('can be used to control error tracker behavior', () => {
|
|
14
|
-
const AppError = Error0.use(statusPlugin).use(expectedPlugin)
|
|
14
|
+
const AppError = Error0.use(statusPlugin).use(expectedPlugin({ hideWhenPublic: false }))
|
|
15
15
|
const errorExpected = new AppError('test', { status: 400, expected: true })
|
|
16
16
|
const errorUnexpected = new AppError('test', { status: 400, expected: false })
|
|
17
17
|
const usualError = new Error('test')
|
|
@@ -30,7 +30,7 @@ describe('expectedPlugin', () => {
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
it('resolves to false when any cause has false', () => {
|
|
33
|
-
const AppError = Error0.use(expectedPlugin)
|
|
33
|
+
const AppError = Error0.use(expectedPlugin({ hideWhenPublic: false }))
|
|
34
34
|
const root = new AppError('root', { expected: true })
|
|
35
35
|
const middle = new AppError('middle', { expected: false, cause: root })
|
|
36
36
|
const leaf = new AppError('leaf', { expected: false, cause: middle })
|
|
@@ -39,7 +39,7 @@ describe('expectedPlugin', () => {
|
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
it('treats undefined expected as unexpected', () => {
|
|
42
|
-
const AppError = Error0.use(expectedPlugin)
|
|
42
|
+
const AppError = Error0.use(expectedPlugin({ hideWhenPublic: false }))
|
|
43
43
|
const error = new AppError('without expected')
|
|
44
44
|
expect(error.expected).toBe(false)
|
|
45
45
|
expect(error.isExpected()).toBe(false)
|
package/src/plugins/expected.ts
CHANGED
|
@@ -13,13 +13,19 @@ const isExpected = (flow: unknown[]) => {
|
|
|
13
13
|
return expected
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const expectedPlugin =
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
export const expectedPlugin = ({ hideWhenPublic = true }: { hideWhenPublic?: boolean } = {}) =>
|
|
17
|
+
Error0.plugin()
|
|
18
|
+
.use('prop', 'expected', {
|
|
19
|
+
init: (input: boolean) => input,
|
|
20
|
+
resolve: ({ flow }) => isExpected(flow),
|
|
21
|
+
serialize: ({ resolved, isPublic }) => {
|
|
22
|
+
if (hideWhenPublic && isPublic) {
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
return resolved
|
|
26
|
+
},
|
|
27
|
+
deserialize: ({ value }) => (typeof value === 'boolean' ? value : undefined),
|
|
28
|
+
})
|
|
29
|
+
.use('method', 'isExpected', (error) => {
|
|
30
|
+
return isExpected(error.flow('expected'))
|
|
31
|
+
})
|
|
@@ -6,7 +6,7 @@ describe('messageMergePlugin', () => {
|
|
|
6
6
|
const statusPlugin = Error0.plugin().use('prop', 'status', {
|
|
7
7
|
init: (input: number) => input,
|
|
8
8
|
resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
|
|
9
|
-
serialize: ({
|
|
9
|
+
serialize: ({ resolved }) => resolved,
|
|
10
10
|
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
11
11
|
})
|
|
12
12
|
|
|
@@ -15,13 +15,13 @@ describe('messageMergePlugin', () => {
|
|
|
15
15
|
const codePlugin = Error0.plugin().use('prop', 'code', {
|
|
16
16
|
init: (input: Code) => input,
|
|
17
17
|
resolve: ({ flow }) => flow.find((value) => typeof value === 'string' && codes.includes(value)),
|
|
18
|
-
serialize: ({
|
|
18
|
+
serialize: ({ resolved, isPublic }) => (isPublic ? undefined : resolved),
|
|
19
19
|
deserialize: ({ value }) =>
|
|
20
20
|
typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
it('can merge message across causes in one serialized value', () => {
|
|
24
|
-
const AppError = Error0.use(statusPlugin).use(codePlugin).use(messageMergePlugin)
|
|
24
|
+
const AppError = Error0.use(statusPlugin).use(codePlugin).use(messageMergePlugin())
|
|
25
25
|
const error1 = new AppError('test1', { status: 400, code: 'NOT_FOUND' })
|
|
26
26
|
const error2 = new AppError('test2', { status: 401, cause: error1 })
|
|
27
27
|
expect(error1.message).toBe('test1')
|