@devp0nt/error0 1.0.0-next.48 → 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 +158 -85
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +44 -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 +44 -16
- package/dist/esm/index.js +158 -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 +79 -21
- package/src/index.ts +216 -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>,
|
|
@@ -408,6 +434,13 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
|
|
|
408
434
|
error: unknown,
|
|
409
435
|
isPublic?: boolean,
|
|
410
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
|
+
}
|
|
411
444
|
resolve: (error: unknown) => ErrorResolvedProps<TPluginsMap>
|
|
412
445
|
serialize: (error: unknown, isPublic?: boolean) => Record<string, unknown>
|
|
413
446
|
own: {
|
|
@@ -469,7 +502,9 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
|
|
|
469
502
|
export class Error0 extends Error {
|
|
470
503
|
static readonly __pluginsMap?: EmptyPluginsMap
|
|
471
504
|
readonly __pluginsMap?: EmptyPluginsMap
|
|
505
|
+
static MAX_CAUSES_DEPTH = 99
|
|
472
506
|
protected static _plugins: ErrorPlugin[] = []
|
|
507
|
+
protected static _resolvedPlugin?: ErrorPluginResolved
|
|
473
508
|
|
|
474
509
|
private static readonly _emptyPlugin: ErrorPluginResolved = {
|
|
475
510
|
props: {},
|
|
@@ -478,35 +513,86 @@ export class Error0 extends Error {
|
|
|
478
513
|
stack: undefined,
|
|
479
514
|
cause: undefined,
|
|
480
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)
|
|
481
571
|
}
|
|
482
572
|
|
|
483
573
|
private static _getResolvedPlugin(this: typeof Error0): ErrorPluginResolved {
|
|
574
|
+
if (Object.prototype.hasOwnProperty.call(this, '_resolvedPlugin') && this._resolvedPlugin) {
|
|
575
|
+
return this._resolvedPlugin
|
|
576
|
+
}
|
|
484
577
|
const resolved: ErrorPluginResolved = {
|
|
485
578
|
props: {},
|
|
486
579
|
methods: {},
|
|
487
580
|
adapt: [],
|
|
581
|
+
propKeys: [],
|
|
582
|
+
propEntries: [],
|
|
583
|
+
methodEntries: [],
|
|
488
584
|
}
|
|
489
585
|
for (const plugin of this._plugins) {
|
|
490
|
-
|
|
491
|
-
throw new Error(RESERVED_STACK_PROP_ERROR)
|
|
492
|
-
}
|
|
493
|
-
if (plugin.props && 'message' in plugin.props) {
|
|
494
|
-
throw new Error(RESERVED_MESSAGE_PROP_ERROR)
|
|
495
|
-
}
|
|
496
|
-
Object.assign(resolved.props, plugin.props ?? this._emptyPlugin.props)
|
|
497
|
-
Object.assign(resolved.methods, plugin.methods ?? this._emptyPlugin.methods)
|
|
498
|
-
resolved.adapt.push(...(plugin.adapt ?? this._emptyPlugin.adapt))
|
|
499
|
-
if (typeof plugin.stack !== 'undefined') {
|
|
500
|
-
resolved.stack = plugin.stack
|
|
501
|
-
}
|
|
502
|
-
if (typeof plugin.cause !== 'undefined') {
|
|
503
|
-
resolved.cause = plugin.cause
|
|
504
|
-
}
|
|
505
|
-
if (typeof plugin.message !== 'undefined') {
|
|
506
|
-
resolved.message = plugin.message
|
|
507
|
-
}
|
|
586
|
+
this._applyPlugin(resolved, plugin)
|
|
508
587
|
}
|
|
509
|
-
|
|
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
|
|
510
596
|
}
|
|
511
597
|
|
|
512
598
|
constructor(message: string, input?: ErrorInput<EmptyPluginsMap>)
|
|
@@ -524,37 +610,47 @@ export class Error0 extends Error {
|
|
|
524
610
|
|
|
525
611
|
const ctor = this.constructor as typeof Error0
|
|
526
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 })
|
|
527
615
|
|
|
528
|
-
for (const [key, prop] of
|
|
616
|
+
for (const [key, prop] of plugin.propEntries) {
|
|
529
617
|
if (key === 'stack') {
|
|
530
618
|
continue
|
|
531
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
|
+
})
|
|
532
633
|
if (key in input) {
|
|
533
634
|
const ownValue = (input as Record<string, unknown>)[key]
|
|
534
|
-
|
|
535
|
-
;(this as Record<string, unknown>)[key] = prop.init(ownValue)
|
|
536
|
-
} else {
|
|
537
|
-
;(this as Record<string, unknown>)[key] = ownValue
|
|
538
|
-
}
|
|
539
|
-
} else {
|
|
540
|
-
Object.defineProperty(this, key, {
|
|
541
|
-
get: () => prop.resolve({ own: undefined, flow: this.flow(key), error: this }),
|
|
542
|
-
set: (value) => {
|
|
543
|
-
Object.defineProperty(this, key, {
|
|
544
|
-
value,
|
|
545
|
-
writable: true,
|
|
546
|
-
enumerable: true,
|
|
547
|
-
configurable: true,
|
|
548
|
-
})
|
|
549
|
-
},
|
|
550
|
-
enumerable: true,
|
|
551
|
-
configurable: true,
|
|
552
|
-
})
|
|
635
|
+
ownStore[key] = typeof prop.init === 'function' ? prop.init(ownValue) : ownValue
|
|
553
636
|
}
|
|
554
637
|
}
|
|
555
638
|
}
|
|
556
639
|
|
|
557
|
-
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
|
+
}
|
|
558
654
|
const d = Object.getOwnPropertyDescriptor(object, key)
|
|
559
655
|
if (!d) return false
|
|
560
656
|
if (typeof d.get === 'function' || typeof d.set === 'function') {
|
|
@@ -567,15 +663,22 @@ export class Error0 extends Error {
|
|
|
567
663
|
return true
|
|
568
664
|
}
|
|
569
665
|
private static _ownByKey(error: object, key: string): unknown {
|
|
570
|
-
|
|
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)) {
|
|
571
671
|
return (error as Record<string, unknown>)[key]
|
|
572
672
|
}
|
|
573
673
|
return undefined
|
|
574
674
|
}
|
|
575
675
|
private static _flowByKey(error: object, key: string): unknown[] {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
|
579
682
|
}
|
|
580
683
|
|
|
581
684
|
static own<TThis extends typeof Error0>(this: TThis, error: unknown): ErrorOwnProps<PluginsMapOf<TThis>>
|
|
@@ -589,7 +692,7 @@ export class Error0 extends Error {
|
|
|
589
692
|
if (key === undefined) {
|
|
590
693
|
const ownValues: Record<string, unknown> = {}
|
|
591
694
|
const plugin = this._getResolvedPlugin()
|
|
592
|
-
for (const ownKey of
|
|
695
|
+
for (const ownKey of plugin.propKeys) {
|
|
593
696
|
ownValues[ownKey] = this._ownByKey(error0, ownKey)
|
|
594
697
|
}
|
|
595
698
|
return ownValues
|
|
@@ -614,7 +717,6 @@ export class Error0 extends Error {
|
|
|
614
717
|
error: unknown,
|
|
615
718
|
key: TKey,
|
|
616
719
|
): Array<ErrorOwnProps<PluginsMapOf<TThis>>[TKey]>
|
|
617
|
-
static flow(error: unknown, key: string): unknown[]
|
|
618
720
|
static flow(error: unknown, key: string): unknown[] {
|
|
619
721
|
const error0 = this.from(error)
|
|
620
722
|
return this._flowByKey(error0, key)
|
|
@@ -623,45 +725,48 @@ export class Error0 extends Error {
|
|
|
623
725
|
this: TThis,
|
|
624
726
|
key: TKey,
|
|
625
727
|
): Array<ErrorOwnProps<PluginsMapOfInstance<TThis>>[TKey]>
|
|
626
|
-
flow(key: string): unknown[]
|
|
627
728
|
flow(key: string): unknown[] {
|
|
628
729
|
const ctor = this.constructor as typeof Error0
|
|
629
730
|
return ctor._flowByKey(this, key)
|
|
630
731
|
}
|
|
631
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
|
+
|
|
632
758
|
static resolve<TThis extends typeof Error0>(this: TThis, error: unknown): ErrorResolvedProps<PluginsMapOf<TThis>>
|
|
633
759
|
static resolve(error: unknown): Record<string, unknown>
|
|
634
760
|
static resolve(error: unknown): Record<string, unknown> {
|
|
635
761
|
const error0 = this.from(error)
|
|
636
762
|
const resolved: Record<string, unknown> = {}
|
|
637
763
|
const plugin = this._getResolvedPlugin()
|
|
638
|
-
for (const
|
|
639
|
-
|
|
640
|
-
// resolved[key] = (error0 as unknown as Record<string, unknown>)[key]
|
|
641
|
-
const options = Object.defineProperties(
|
|
642
|
-
{
|
|
643
|
-
error: error0,
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
own: {
|
|
647
|
-
get: () => error0.own(key as never),
|
|
648
|
-
},
|
|
649
|
-
flow: {
|
|
650
|
-
get: () => error0.flow(key),
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
)
|
|
654
|
-
resolved[key] = prop.resolve(options as never)
|
|
655
|
-
// resolved[key] = prop.resolve({ own: error0.own(key as never), flow: error0.flow(key), error: error0 })
|
|
656
|
-
} catch {
|
|
657
|
-
// eslint-disable-next-line no-console
|
|
658
|
-
console.error(`Error0: failed to resolve property ${key}`, error0)
|
|
659
|
-
}
|
|
764
|
+
for (const key of plugin.propKeys) {
|
|
765
|
+
resolved[key] = this._resolveByKey(error0, key, plugin)
|
|
660
766
|
}
|
|
661
767
|
return resolved
|
|
662
768
|
}
|
|
663
769
|
resolve<TThis extends Error0>(this: TThis): ErrorResolvedProps<PluginsMapOfInstance<TThis>>
|
|
664
|
-
resolve(): Record<string, unknown>
|
|
665
770
|
resolve(): Record<string, unknown> {
|
|
666
771
|
const ctor = this.constructor as typeof Error0
|
|
667
772
|
return ctor.resolve(this)
|
|
@@ -672,9 +777,9 @@ export class Error0 extends Error {
|
|
|
672
777
|
static causes(error: unknown, instancesOnly?: boolean): unknown[] {
|
|
673
778
|
const causes: unknown[] = []
|
|
674
779
|
let current: unknown = error
|
|
675
|
-
const maxDepth = 99
|
|
676
780
|
const seen = new Set<unknown>()
|
|
677
|
-
|
|
781
|
+
let depth = 0
|
|
782
|
+
while (depth < this.MAX_CAUSES_DEPTH) {
|
|
678
783
|
if (seen.has(current)) {
|
|
679
784
|
break
|
|
680
785
|
}
|
|
@@ -686,6 +791,7 @@ export class Error0 extends Error {
|
|
|
686
791
|
break
|
|
687
792
|
}
|
|
688
793
|
current = (current as { cause?: unknown }).cause
|
|
794
|
+
depth += 1
|
|
689
795
|
}
|
|
690
796
|
return causes
|
|
691
797
|
}
|
|
@@ -740,8 +846,7 @@ export class Error0 extends Error {
|
|
|
740
846
|
const errorRecord = error as Record<string, unknown>
|
|
741
847
|
const recreated = new this(message)
|
|
742
848
|
const plugin = this._getResolvedPlugin()
|
|
743
|
-
const
|
|
744
|
-
for (const [key, prop] of propsEntries) {
|
|
849
|
+
for (const [key, prop] of plugin.propEntries) {
|
|
745
850
|
if (prop.deserialize === false) {
|
|
746
851
|
continue
|
|
747
852
|
}
|
|
@@ -749,7 +854,7 @@ export class Error0 extends Error {
|
|
|
749
854
|
continue
|
|
750
855
|
}
|
|
751
856
|
try {
|
|
752
|
-
const value = prop.deserialize({ value: errorRecord[key],
|
|
857
|
+
const value = prop.deserialize({ value: errorRecord[key], record: errorRecord })
|
|
753
858
|
;(recreated as unknown as Record<string, unknown>)[key] = value
|
|
754
859
|
} catch {
|
|
755
860
|
// eslint-disable-next-line no-console
|
|
@@ -806,9 +911,9 @@ export class Error0 extends Error {
|
|
|
806
911
|
const Base = this as unknown as typeof Error0
|
|
807
912
|
const Error0Extended = class Error0 extends Base {}
|
|
808
913
|
;(Error0Extended as typeof Error0)._plugins = [...Base._plugins, plugin]
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
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) {
|
|
812
917
|
Object.defineProperty((Error0Extended as typeof Error0).prototype, key, {
|
|
813
918
|
value: function (...args: unknown[]) {
|
|
814
919
|
return method(this as Error0, ...args)
|
|
@@ -1005,9 +1110,9 @@ export class Error0 extends Error {
|
|
|
1005
1110
|
|
|
1006
1111
|
static serialize(error: unknown, isPublic = true): Record<string, unknown> {
|
|
1007
1112
|
const error0 = this.from(error)
|
|
1008
|
-
const resolvedProps = this.resolve(error0)
|
|
1009
|
-
const resolvedRecord = resolvedProps as Record<string, unknown>
|
|
1010
1113
|
const plugin = this._getResolvedPlugin()
|
|
1114
|
+
const resolveByKey = (targetError: Error0, key: string, targetPlugin: ErrorPluginResolved): unknown =>
|
|
1115
|
+
this._resolveByKey(targetError, key, targetPlugin)
|
|
1011
1116
|
const messagePlugin = plugin.message
|
|
1012
1117
|
let serializedMessage: unknown = error0.message
|
|
1013
1118
|
try {
|
|
@@ -1021,25 +1126,38 @@ export class Error0 extends Error {
|
|
|
1021
1126
|
}
|
|
1022
1127
|
const json: Record<string, unknown> = {
|
|
1023
1128
|
name: error0.name,
|
|
1024
|
-
message: serializedMessage,
|
|
1025
1129
|
// we do not serialize causes, it is enough that we have floated props and adapt helper
|
|
1026
1130
|
// cause: error0.cause,
|
|
1027
1131
|
}
|
|
1132
|
+
if (serializedMessage !== undefined) {
|
|
1133
|
+
json.message = serializedMessage
|
|
1134
|
+
}
|
|
1028
1135
|
|
|
1029
|
-
const
|
|
1030
|
-
for (const [key, prop] of propsEntries) {
|
|
1136
|
+
for (const [key, prop] of plugin.propEntries) {
|
|
1031
1137
|
if (prop.serialize === false) {
|
|
1032
1138
|
continue
|
|
1033
1139
|
}
|
|
1034
1140
|
try {
|
|
1035
|
-
const
|
|
1036
|
-
|
|
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>)
|
|
1037
1155
|
if (jsonValue !== undefined) {
|
|
1038
1156
|
json[key] = jsonValue
|
|
1039
1157
|
}
|
|
1040
1158
|
} catch {
|
|
1041
1159
|
// eslint-disable-next-line no-console
|
|
1042
|
-
console.error(`Error0: failed to serialize property ${key}`,
|
|
1160
|
+
console.error(`Error0: failed to serialize property ${key}`, error0)
|
|
1043
1161
|
}
|
|
1044
1162
|
}
|
|
1045
1163
|
const stackPlugin = plugin.stack
|
|
@@ -1073,10 +1191,7 @@ export class Error0 extends Error {
|
|
|
1073
1191
|
console.error('Error0: failed to serialize cause', error0)
|
|
1074
1192
|
}
|
|
1075
1193
|
}
|
|
1076
|
-
return
|
|
1077
|
-
string,
|
|
1078
|
-
unknown
|
|
1079
|
-
>
|
|
1194
|
+
return json
|
|
1080
1195
|
}
|
|
1081
1196
|
|
|
1082
1197
|
serialize(isPublic = true): Record<string, unknown> {
|
|
@@ -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')
|