@devp0nt/error0 1.0.0-next.43 → 1.0.0-next.44

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/src/index.ts CHANGED
@@ -20,7 +20,7 @@ type ErrorPluginPropDeserialize<TOutputValue> =
20
20
  | false
21
21
  type ErrorPluginPropOptionsBase<TOutputValue, TError extends Error0, TResolveValue extends TOutputValue | undefined> = {
22
22
  resolve: (options: {
23
- value: TOutputValue | undefined
23
+ own: TOutputValue | undefined
24
24
  flow: Array<TOutputValue | undefined>
25
25
  error: TError
26
26
  }) => TResolveValue
@@ -59,6 +59,15 @@ export type ErrorPluginAdaptFn<
59
59
  TError extends Error0 = Error0,
60
60
  TOutputProps extends Record<string, unknown> = Record<never, never>,
61
61
  > = ((error: TError) => void) | ((error: TError) => ErrorPluginAdaptResult<TOutputProps>)
62
+ export type ErrorPluginStackSerialize<TError extends Error0> =
63
+ | ((options: { value: string | undefined; error: TError; isPublic: boolean }) => unknown)
64
+ | boolean
65
+ | 'merge'
66
+ export type ErrorPluginStack<TError extends Error0 = Error0> = ErrorPluginStackSerialize<TError>
67
+ export type ErrorPluginCauseSerialize<TError extends Error0> =
68
+ | ((options: { value: unknown; error: TError; isPublic: boolean }) => unknown)
69
+ | boolean
70
+ export type ErrorPluginCause<TError extends Error0 = Error0> = ErrorPluginCauseSerialize<TError>
62
71
  type ErrorMethodRecord = {
63
72
  args: unknown[]
64
73
  output: unknown
@@ -74,6 +83,8 @@ export type ErrorPlugin<
74
83
  props?: TProps
75
84
  methods?: TMethods
76
85
  adapt?: Array<ErrorPluginAdaptFn<Error0, PluginOutputProps<TProps>>>
86
+ stack?: ErrorPluginStack
87
+ cause?: ErrorPluginCause
77
88
  }
78
89
  type AddPropToPluginProps<
79
90
  TProps extends ErrorPluginProps,
@@ -94,7 +105,7 @@ type PluginOutputProps<TProps extends ErrorPluginProps> = {
94
105
  : never
95
106
  }
96
107
  export type ErrorPluginsMap = {
97
- props: Record<string, { init: unknown; resolve: unknown }>
108
+ props: Record<string, { init: unknown; output: unknown; resolve: unknown }>
98
109
  methods: Record<string, ErrorMethodRecord>
99
110
  }
100
111
  export type IsEmptyObject<T> = keyof T extends never ? true : false
@@ -114,6 +125,19 @@ export type ErrorInput<TPluginsMap extends ErrorPluginsMap> =
114
125
  type ErrorResolvedProps<TPluginsMap extends ErrorPluginsMap> = {
115
126
  [TKey in keyof TPluginsMap['props']]: TPluginsMap['props'][TKey]['resolve']
116
127
  }
128
+ type ErrorOwnProps<TPluginsMap extends ErrorPluginsMap> = {
129
+ [TKey in keyof TPluginsMap['props']]: TPluginsMap['props'][TKey]['output'] | undefined
130
+ }
131
+ type ErrorOwnMethods<TPluginsMap extends ErrorPluginsMap> = {
132
+ own: {
133
+ (): ErrorOwnProps<TPluginsMap>
134
+ <TKey extends keyof TPluginsMap['props'] & string>(key: TKey): ErrorOwnProps<TPluginsMap>[TKey]
135
+ }
136
+ flow: <TKey extends keyof TPluginsMap['props'] & string>(key: TKey) => Array<ErrorOwnProps<TPluginsMap>[TKey]>
137
+ }
138
+ type ErrorResolveMethods<TPluginsMap extends ErrorPluginsMap> = {
139
+ resolve: () => ErrorResolvedProps<TPluginsMap>
140
+ }
117
141
  type ErrorMethods<TPluginsMap extends ErrorPluginsMap> = {
118
142
  [TKey in keyof TPluginsMap['methods']]: TPluginsMap['methods'][TKey] extends {
119
143
  args: infer TArgs extends unknown[]
@@ -135,7 +159,7 @@ type ErrorStaticMethods<TPluginsMap extends ErrorPluginsMap> = {
135
159
  }
136
160
 
137
161
  type EmptyPluginsMap = {
138
- props: Record<never, { init: never; resolve: never }>
162
+ props: Record<never, { init: never; output: never; resolve: never }>
139
163
  methods: Record<never, ErrorMethodRecord>
140
164
  }
141
165
 
@@ -143,16 +167,23 @@ type ErrorPluginResolved = {
143
167
  props: Record<string, ErrorPluginPropOptions<unknown>>
144
168
  methods: Record<string, ErrorPluginMethodFn<unknown>>
145
169
  adapt: Array<ErrorPluginAdaptFn<Error0, Record<string, unknown>>>
170
+ stack?: ErrorPluginStack
171
+ cause?: ErrorPluginCause
146
172
  }
173
+ const RESERVED_STACK_PROP_ERROR = 'Error0: "stack" is a reserved prop key. Use .stack(...) plugin API instead'
147
174
 
148
175
  type PluginPropsMapOf<TPlugin extends ErrorPlugin> = {
149
176
  [TKey in keyof NonNullable<TPlugin['props']>]: NonNullable<TPlugin['props']>[TKey] extends ErrorPluginPropOptions<
150
177
  any,
151
- any,
178
+ infer TOutputValue,
152
179
  any,
153
180
  infer TResolveValue
154
181
  >
155
- ? { init: InferPluginPropInput<NonNullable<TPlugin['props']>[TKey]>; resolve: TResolveValue }
182
+ ? {
183
+ init: InferPluginPropInput<NonNullable<TPlugin['props']>[TKey]>
184
+ output: TOutputValue
185
+ resolve: TResolveValue
186
+ }
156
187
  : never
157
188
  }
158
189
  type PluginMethodsMapOf<TPlugin extends ErrorPlugin> = {
@@ -196,6 +227,11 @@ type PluginsMapOf<TClass> = TClass extends { __pluginsMap?: infer TPluginsMap }
196
227
  ? TPluginsMap
197
228
  : EmptyPluginsMap
198
229
  : EmptyPluginsMap
230
+ type PluginsMapOfInstance<TInstance> = TInstance extends { constructor: { __pluginsMap?: infer TPluginsMap } }
231
+ ? TPluginsMap extends ErrorPluginsMap
232
+ ? TPluginsMap
233
+ : EmptyPluginsMap
234
+ : EmptyPluginsMap
199
235
 
200
236
  type PluginsMapFromParts<
201
237
  TProps extends ErrorPluginProps,
@@ -224,6 +260,8 @@ export class PluginError0<
224
260
  props: { ...(plugin?.props ?? {}) },
225
261
  methods: { ...(plugin?.methods ?? {}) },
226
262
  adapt: [...(plugin?.adapt ?? [])],
263
+ stack: plugin?.stack,
264
+ cause: plugin?.cause,
227
265
  }
228
266
  }
229
267
 
@@ -252,6 +290,14 @@ export class PluginError0<
252
290
  return this.use('adapt', value)
253
291
  }
254
292
 
293
+ stack(value: ErrorPluginStack<BuilderError0<TProps, TMethods>>): PluginError0<TProps, TMethods> {
294
+ return this.use('stack', value)
295
+ }
296
+
297
+ cause(value: ErrorPluginCause<BuilderError0<TProps, TMethods>>): PluginError0<TProps, TMethods> {
298
+ return this.use('cause', value)
299
+ }
300
+
255
301
  use<
256
302
  TKey extends string,
257
303
  TInputValue = undefined,
@@ -271,16 +317,23 @@ export class PluginError0<
271
317
  kind: 'adapt',
272
318
  value: ErrorPluginAdaptFn<BuilderError0<TProps, TMethods>, PluginOutputProps<TProps>>,
273
319
  ): PluginError0<TProps, TMethods>
320
+ use(kind: 'stack', value: ErrorPluginStack<BuilderError0<TProps, TMethods>>): PluginError0<TProps, TMethods>
321
+ use(kind: 'cause', value: ErrorPluginCause<BuilderError0<TProps, TMethods>>): PluginError0<TProps, TMethods>
274
322
  use(
275
- kind: 'prop' | 'method' | 'adapt',
276
- keyOrValue: string | ErrorPluginAdaptFn<any, any>,
323
+ kind: 'prop' | 'method' | 'adapt' | 'stack' | 'cause',
324
+ keyOrValue: unknown,
277
325
  value?: ErrorPluginPropOptions<unknown, unknown, any> | ErrorPluginMethodFn<unknown, unknown[], any>,
278
326
  ): PluginError0<any, any> {
279
327
  const nextProps: ErrorPluginProps = { ...(this._plugin.props ?? {}) }
280
328
  const nextMethods: ErrorPluginMethods = { ...(this._plugin.methods ?? {}) }
281
329
  const nextAdapt: Array<ErrorPluginAdaptFn<Error0, Record<string, unknown>>> = [...(this._plugin.adapt ?? [])]
330
+ let nextStack: ErrorPluginStack | undefined = this._plugin.stack
331
+ let nextCause: ErrorPluginCause | undefined = this._plugin.cause
282
332
  if (kind === 'prop') {
283
333
  const key = keyOrValue as string
334
+ if (key === 'stack') {
335
+ throw new Error(RESERVED_STACK_PROP_ERROR)
336
+ }
284
337
  if (value === undefined) {
285
338
  throw new Error('PluginError0.use("prop", key, value) requires value')
286
339
  }
@@ -291,23 +344,45 @@ export class PluginError0<
291
344
  throw new Error('PluginError0.use("method", key, value) requires value')
292
345
  }
293
346
  nextMethods[key] = value as ErrorPluginMethodFn<any, any[]>
294
- } else {
347
+ } else if (kind === 'adapt') {
295
348
  nextAdapt.push(keyOrValue as ErrorPluginAdaptFn<Error0, Record<string, unknown>>)
349
+ } else if (kind === 'stack') {
350
+ nextStack = keyOrValue as ErrorPluginStack
351
+ } else {
352
+ nextCause = keyOrValue as ErrorPluginCause
296
353
  }
297
354
  return new PluginError0({
298
355
  props: nextProps,
299
356
  methods: nextMethods,
300
357
  adapt: nextAdapt,
358
+ stack: nextStack,
359
+ cause: nextCause,
301
360
  })
302
361
  }
303
362
  }
304
363
 
305
364
  export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> = {
306
- new (message: string, input?: ErrorInput<TPluginsMap>): Error0 & ErrorResolved<TPluginsMap>
307
- new (input: { message: string } & ErrorInput<TPluginsMap>): Error0 & ErrorResolved<TPluginsMap>
365
+ new (
366
+ message: string,
367
+ input?: ErrorInput<TPluginsMap>,
368
+ ): Error0 & ErrorResolved<TPluginsMap> & ErrorOwnMethods<TPluginsMap> & ErrorResolveMethods<TPluginsMap>
369
+ new (
370
+ input: { message: string } & ErrorInput<TPluginsMap>,
371
+ ): Error0 & ErrorResolved<TPluginsMap> & ErrorOwnMethods<TPluginsMap> & ErrorResolveMethods<TPluginsMap>
308
372
  readonly __pluginsMap?: TPluginsMap
309
- from: (error: unknown) => Error0 & ErrorResolved<TPluginsMap>
373
+ from: (
374
+ error: unknown,
375
+ ) => Error0 & ErrorResolved<TPluginsMap> & ErrorOwnMethods<TPluginsMap> & ErrorResolveMethods<TPluginsMap>
376
+ resolve: (error: unknown) => ErrorResolvedProps<TPluginsMap>
310
377
  serialize: (error: unknown, isPublic?: boolean) => Record<string, unknown>
378
+ own: {
379
+ (error: object): ErrorOwnProps<TPluginsMap>
380
+ <TKey extends keyof TPluginsMap['props'] & string>(error: object, key: TKey): ErrorOwnProps<TPluginsMap>[TKey]
381
+ }
382
+ flow: <TKey extends keyof TPluginsMap['props'] & string>(
383
+ error: object,
384
+ key: TKey,
385
+ ) => Array<ErrorOwnProps<TPluginsMap>[TKey]>
311
386
  prop: <
312
387
  TKey extends string,
313
388
  TInputValue = undefined,
@@ -324,6 +399,8 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
324
399
  adapt: (
325
400
  value: ErrorPluginAdaptFn<ErrorInstanceOfMap<TPluginsMap>, ErrorResolvedProps<TPluginsMap>>,
326
401
  ) => ClassError0<TPluginsMap>
402
+ stack: (value: ErrorPluginStack<ErrorInstanceOfMap<TPluginsMap>>) => ClassError0<TPluginsMap>
403
+ cause: (value: ErrorPluginCause<ErrorInstanceOfMap<TPluginsMap>>) => ClassError0<TPluginsMap>
327
404
  use: {
328
405
  <TBuilder extends PluginError0>(
329
406
  plugin: TBuilder,
@@ -347,6 +424,8 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
347
424
  kind: 'adapt',
348
425
  value: ErrorPluginAdaptFn<ErrorInstanceOfMap<TPluginsMap>, ErrorResolvedProps<TPluginsMap>>,
349
426
  ): ClassError0<TPluginsMap>
427
+ (kind: 'stack', value: ErrorPluginStack<ErrorInstanceOfMap<TPluginsMap>>): ClassError0<TPluginsMap>
428
+ (kind: 'cause', value: ErrorPluginCause<ErrorInstanceOfMap<TPluginsMap>>): ClassError0<TPluginsMap>
350
429
  }
351
430
  plugin: () => PluginError0
352
431
  } & ErrorStaticMethods<TPluginsMap>
@@ -359,6 +438,8 @@ export class Error0 extends Error {
359
438
  props: {},
360
439
  methods: {},
361
440
  adapt: [],
441
+ stack: undefined,
442
+ cause: undefined,
362
443
  }
363
444
 
364
445
  private static _getResolvedPlugin(this: typeof Error0): ErrorPluginResolved {
@@ -368,9 +449,18 @@ export class Error0 extends Error {
368
449
  adapt: [],
369
450
  }
370
451
  for (const plugin of this._plugins) {
452
+ if (plugin.props && 'stack' in plugin.props) {
453
+ throw new Error(RESERVED_STACK_PROP_ERROR)
454
+ }
371
455
  Object.assign(resolved.props, plugin.props ?? this._emptyPlugin.props)
372
456
  Object.assign(resolved.methods, plugin.methods ?? this._emptyPlugin.methods)
373
457
  resolved.adapt.push(...(plugin.adapt ?? this._emptyPlugin.adapt))
458
+ if (typeof plugin.stack !== 'undefined') {
459
+ resolved.stack = plugin.stack
460
+ }
461
+ if (typeof plugin.cause !== 'undefined') {
462
+ resolved.cause = plugin.cause
463
+ }
374
464
  }
375
465
  return resolved
376
466
  }
@@ -392,6 +482,9 @@ export class Error0 extends Error {
392
482
  const plugin = ctor._getResolvedPlugin()
393
483
 
394
484
  for (const [key, prop] of Object.entries(plugin.props)) {
485
+ if (key === 'stack') {
486
+ continue
487
+ }
395
488
  if (key in input) {
396
489
  const ownValue = (input as Record<string, unknown>)[key]
397
490
  if (typeof prop.init === 'function') {
@@ -401,7 +494,7 @@ export class Error0 extends Error {
401
494
  }
402
495
  } else {
403
496
  Object.defineProperty(this, key, {
404
- get: () => prop.resolve({ value: undefined, flow: this.flow(key), error: this }),
497
+ get: () => prop.resolve({ own: undefined, flow: this.flow(key), error: this }),
405
498
  set: (value) => {
406
499
  Object.defineProperty(this, key, {
407
500
  value,
@@ -429,26 +522,105 @@ export class Error0 extends Error {
429
522
  }
430
523
  return true
431
524
  }
432
-
433
- static own(error: object, key: string): unknown {
525
+ private static _ownByKey(error: object, key: string): unknown {
434
526
  if (this.isSelfProperty(error, key)) {
435
527
  return (error as Record<string, unknown>)[key]
436
528
  }
437
529
  return undefined
438
530
  }
439
- own(key: string): unknown {
531
+ private static _flowByKey(error: object, key: string): unknown[] {
532
+ return this.causes(error, true).map((cause) => {
533
+ return this._ownByKey(cause, key)
534
+ })
535
+ }
536
+
537
+ static own<TThis extends typeof Error0>(this: TThis, error: unknown): ErrorOwnProps<PluginsMapOf<TThis>>
538
+ static own<TThis extends typeof Error0, TKey extends keyof PluginsMapOf<TThis>['props'] & string>(
539
+ this: TThis,
540
+ error: unknown,
541
+ key: TKey,
542
+ ): ErrorOwnProps<PluginsMapOf<TThis>>[TKey]
543
+ static own(error: unknown, key?: string): unknown {
544
+ const error0 = this.from(error)
545
+ if (key === undefined) {
546
+ const ownValues: Record<string, unknown> = {}
547
+ const plugin = this._getResolvedPlugin()
548
+ for (const ownKey of Object.keys(plugin.props)) {
549
+ ownValues[ownKey] = this._ownByKey(error0, ownKey)
550
+ }
551
+ return ownValues
552
+ }
553
+ return this._ownByKey(error0, key)
554
+ }
555
+ own<TThis extends Error0>(this: TThis): ErrorOwnProps<PluginsMapOfInstance<TThis>>
556
+ own<TThis extends Error0, TKey extends keyof PluginsMapOfInstance<TThis>['props'] & string>(
557
+ this: TThis,
558
+ key: TKey,
559
+ ): ErrorOwnProps<PluginsMapOfInstance<TThis>>[TKey]
560
+ own(key?: string): unknown {
440
561
  const ctor = this.constructor as typeof Error0
441
- return ctor.own(this, key)
562
+ if (key === undefined) {
563
+ return ctor.own(this)
564
+ }
565
+ return ctor._ownByKey(this, key)
442
566
  }
443
567
 
444
- static flow(error: object, key: string): unknown[] {
445
- return this.causes(error, true).map((cause) => {
446
- return this.own(cause, key)
447
- })
568
+ static flow<TThis extends typeof Error0, TKey extends keyof PluginsMapOf<TThis>['props'] & string>(
569
+ this: TThis,
570
+ error: unknown,
571
+ key: TKey,
572
+ ): Array<ErrorOwnProps<PluginsMapOf<TThis>>[TKey]>
573
+ static flow(error: unknown, key: string): unknown[]
574
+ static flow(error: unknown, key: string): unknown[] {
575
+ const error0 = this.from(error)
576
+ return this._flowByKey(error0, key)
448
577
  }
578
+ flow<TThis extends Error0, TKey extends keyof PluginsMapOfInstance<TThis>['props'] & string>(
579
+ this: TThis,
580
+ key: TKey,
581
+ ): Array<ErrorOwnProps<PluginsMapOfInstance<TThis>>[TKey]>
582
+ flow(key: string): unknown[]
449
583
  flow(key: string): unknown[] {
450
584
  const ctor = this.constructor as typeof Error0
451
- return ctor.flow(this, key)
585
+ return ctor._flowByKey(this, key)
586
+ }
587
+
588
+ static resolve<TThis extends typeof Error0>(this: TThis, error: unknown): ErrorResolvedProps<PluginsMapOf<TThis>>
589
+ static resolve(error: unknown): Record<string, unknown>
590
+ static resolve(error: unknown): Record<string, unknown> {
591
+ const error0 = this.from(error)
592
+ const resolved: Record<string, unknown> = {}
593
+ const plugin = this._getResolvedPlugin()
594
+ for (const [key, prop] of Object.entries(plugin.props)) {
595
+ try {
596
+ // resolved[key] = (error0 as unknown as Record<string, unknown>)[key]
597
+ const options = Object.defineProperties(
598
+ {
599
+ error: error0,
600
+ },
601
+ {
602
+ own: {
603
+ get: () => error0.own(key as never),
604
+ },
605
+ flow: {
606
+ get: () => error0.flow(key),
607
+ },
608
+ },
609
+ )
610
+ resolved[key] = prop.resolve(options as never)
611
+ // resolved[key] = prop.resolve({ own: error0.own(key as never), flow: error0.flow(key), error: error0 })
612
+ } catch {
613
+ // eslint-disable-next-line no-console
614
+ console.error(`Error0: failed to resolve property ${key}`, error0)
615
+ }
616
+ }
617
+ return resolved
618
+ }
619
+ resolve<TThis extends Error0>(this: TThis): ErrorResolvedProps<PluginsMapOfInstance<TThis>>
620
+ resolve(): Record<string, unknown>
621
+ resolve(): Record<string, unknown> {
622
+ const ctor = this.constructor as typeof Error0
623
+ return ctor.resolve(this)
452
624
  }
453
625
 
454
626
  static causes(error: unknown, instancesOnly?: false): unknown[]
@@ -473,8 +645,8 @@ export class Error0 extends Error {
473
645
  }
474
646
  return causes
475
647
  }
476
- causes<T extends typeof Error0>(this: T, instancesOnly?: false): [InstanceType<T>, ...unknown[]]
477
- causes<T extends typeof Error0>(this: T, instancesOnly: true): [InstanceType<T>, ...Array<InstanceType<T>>]
648
+ causes<TThis extends Error0>(this: TThis, instancesOnly?: false): [TThis, ...unknown[]]
649
+ causes<TThis extends Error0>(this: TThis, instancesOnly: true): [TThis, ...TThis[]]
478
650
  causes(instancesOnly?: boolean): unknown[] {
479
651
  const ctor = this.constructor as typeof Error0
480
652
  if (instancesOnly) {
@@ -532,14 +704,35 @@ export class Error0 extends Error {
532
704
  const value = prop.deserialize({ value: errorRecord[key], serialized: errorRecord })
533
705
  ;(recreated as unknown as Record<string, unknown>)[key] = value
534
706
  } catch {
535
- // ignore
707
+ // eslint-disable-next-line no-console
708
+ console.error(`Error0: failed to deserialize property ${key}`, errorRecord)
536
709
  }
537
710
  }
538
711
  // we do not serialize causes
539
712
  // ;(recreated as unknown as { cause?: unknown }).cause = errorRecord.cause
540
- const isStackInProps = propsEntries.some(([key]) => key === 'stack')
541
- if (typeof errorRecord.stack === 'string' && !isStackInProps) {
542
- recreated.stack = errorRecord.stack
713
+ const stackPlugin = plugin.stack
714
+ if (stackPlugin !== false && 'stack' in errorRecord) {
715
+ try {
716
+ if (typeof errorRecord.stack === 'string') {
717
+ recreated.stack = errorRecord.stack
718
+ }
719
+ } catch {
720
+ // eslint-disable-next-line no-console
721
+ console.error('Error0: failed to deserialize stack', errorRecord)
722
+ }
723
+ }
724
+ const causePlugin = plugin.cause ?? false
725
+ if (causePlugin && 'cause' in errorRecord) {
726
+ try {
727
+ if (this.isSerialized(errorRecord.cause)) {
728
+ ;(recreated as { cause?: unknown }).cause = this._fromSerialized(errorRecord.cause)
729
+ } else {
730
+ ;(recreated as { cause?: unknown }).cause = errorRecord.cause
731
+ }
732
+ } catch {
733
+ // eslint-disable-next-line no-console
734
+ console.error('Error0: failed to deserialize cause', errorRecord)
735
+ }
543
736
  }
544
737
  return recreated
545
738
  }
@@ -598,6 +791,8 @@ export class Error0 extends Error {
598
791
  props: { ...(pluginRecord._plugin.props ?? {}) },
599
792
  methods: { ...(pluginRecord._plugin.methods ?? {}) },
600
793
  adapt: [...(pluginRecord._plugin.adapt ?? [])],
794
+ stack: pluginRecord._plugin.stack,
795
+ cause: pluginRecord._plugin.cause,
601
796
  }
602
797
  }
603
798
 
@@ -630,6 +825,20 @@ export class Error0 extends Error {
630
825
  return this.use('adapt', value)
631
826
  }
632
827
 
828
+ static stack<TThis extends typeof Error0>(
829
+ this: TThis,
830
+ value: ErrorPluginStack<ErrorInstanceOfMap<PluginsMapOf<TThis>>>,
831
+ ): ClassError0<PluginsMapOf<TThis>> {
832
+ return this.use('stack', value)
833
+ }
834
+
835
+ static cause<TThis extends typeof Error0>(
836
+ this: TThis,
837
+ value: ErrorPluginCause<ErrorInstanceOfMap<PluginsMapOf<TThis>>>,
838
+ ): ClassError0<PluginsMapOf<TThis>> {
839
+ return this.use('cause', value)
840
+ }
841
+
633
842
  static use<TThis extends typeof Error0, TBuilder extends PluginError0>(
634
843
  this: TThis,
635
844
  plugin: TBuilder,
@@ -657,21 +866,53 @@ export class Error0 extends Error {
657
866
  kind: 'adapt',
658
867
  value: ErrorPluginAdaptFn<ErrorInstanceOfMap<PluginsMapOf<TThis>>, ErrorResolvedProps<PluginsMapOf<TThis>>>,
659
868
  ): ClassError0<PluginsMapOf<TThis>>
869
+ static use<TThis extends typeof Error0>(
870
+ this: TThis,
871
+ kind: 'stack',
872
+ value: ErrorPluginStack<ErrorInstanceOfMap<PluginsMapOf<TThis>>>,
873
+ ): ClassError0<PluginsMapOf<TThis>>
874
+ static use<TThis extends typeof Error0>(
875
+ this: TThis,
876
+ kind: 'cause',
877
+ value: ErrorPluginCause<ErrorInstanceOfMap<PluginsMapOf<TThis>>>,
878
+ ): ClassError0<PluginsMapOf<TThis>>
660
879
  static use(
661
880
  this: typeof Error0,
662
- first: PluginError0 | 'prop' | 'method' | 'adapt',
663
- key?: string | ErrorPluginAdaptFn<any, any>,
881
+ first: PluginError0 | 'prop' | 'method' | 'adapt' | 'stack' | 'cause',
882
+ key?: unknown,
664
883
  value?: ErrorPluginPropOptions<unknown> | ErrorPluginMethodFn<unknown>,
665
884
  ): ClassError0 {
666
885
  if (first instanceof PluginError0) {
667
886
  return this._useWithPlugin(this._pluginFromBuilder(first))
668
887
  }
888
+ if (first === 'stack') {
889
+ if (typeof key === 'undefined') {
890
+ throw new Error('Error0.use("stack", value) requires stack plugin value')
891
+ }
892
+ if (key !== 'merge' && typeof key !== 'boolean' && typeof key !== 'function') {
893
+ throw new Error('Error0.use("stack", value) expects function | boolean | "merge"')
894
+ }
895
+ return this._useWithPlugin({
896
+ stack: key as ErrorPluginStack,
897
+ })
898
+ }
899
+ if (first === 'cause') {
900
+ if (typeof key === 'undefined') {
901
+ throw new Error('Error0.use("cause", value) requires cause plugin value')
902
+ }
903
+ if (typeof key !== 'boolean' && typeof key !== 'function') {
904
+ throw new Error('Error0.use("cause", value) expects function | boolean')
905
+ }
906
+ return this._useWithPlugin({
907
+ cause: key as ErrorPluginCause,
908
+ })
909
+ }
669
910
  if (first === 'adapt') {
670
911
  if (typeof key !== 'function') {
671
912
  throw new Error('Error0.use("adapt", value) requires adapt function')
672
913
  }
673
914
  return this._useWithPlugin({
674
- adapt: [key],
915
+ adapt: [key as ErrorPluginAdaptFn<Error0, Record<string, unknown>>],
675
916
  })
676
917
  }
677
918
  if (typeof key !== 'string' || value === undefined) {
@@ -679,6 +920,9 @@ export class Error0 extends Error {
679
920
  }
680
921
 
681
922
  if (first === 'prop') {
923
+ if (key === 'stack') {
924
+ throw new Error(RESERVED_STACK_PROP_ERROR)
925
+ }
682
926
  return this._useWithPlugin({
683
927
  props: { [key]: value as ErrorPluginPropOptions<unknown> },
684
928
  })
@@ -694,6 +938,8 @@ export class Error0 extends Error {
694
938
 
695
939
  static serialize(error: unknown, isPublic = true): Record<string, unknown> {
696
940
  const error0 = this.from(error)
941
+ const resolvedProps = this.resolve(error0)
942
+ const resolvedRecord = resolvedProps as Record<string, unknown>
697
943
  const json: Record<string, unknown> = {
698
944
  name: error0.name,
699
945
  message: error0.message,
@@ -708,18 +954,63 @@ export class Error0 extends Error {
708
954
  continue
709
955
  }
710
956
  try {
711
- const value = prop.resolve({ value: error0.own(key), flow: error0.flow(key), error: error0 })
957
+ const value = resolvedRecord[key]
712
958
  const jsonValue = prop.serialize({ value, error: error0, isPublic })
713
959
  if (jsonValue !== undefined) {
714
960
  json[key] = jsonValue
715
961
  }
716
962
  } catch {
717
- // ignore
963
+ // eslint-disable-next-line no-console
964
+ console.error(`Error0: failed to serialize property ${key}`, resolvedRecord)
718
965
  }
719
966
  }
720
- const isStackInProps = propsEntries.some(([key]) => key === 'stack')
721
- if (!isStackInProps && typeof error0.stack === 'string') {
722
- json.stack = error0.stack
967
+ const stackSerialize = plugin.stack
968
+ if (stackSerialize !== false) {
969
+ try {
970
+ let serializedStack: unknown
971
+ if (stackSerialize === 'merge') {
972
+ serializedStack = error0
973
+ .causes()
974
+ .map((cause) => {
975
+ return cause instanceof Error ? cause.stack : undefined
976
+ })
977
+ .filter((value): value is string => typeof value === 'string')
978
+ .join('\n')
979
+ } else if (typeof stackSerialize === 'function') {
980
+ serializedStack = stackSerialize({ value: error0.stack, error: error0, isPublic })
981
+ } else {
982
+ serializedStack = error0.stack
983
+ }
984
+ if (serializedStack !== undefined) {
985
+ json.stack = serializedStack
986
+ }
987
+ } catch {
988
+ // eslint-disable-next-line no-console
989
+ console.error('Error0: failed to serialize stack', error0)
990
+ }
991
+ }
992
+ const causeSerialize = plugin.cause ?? false
993
+ if (causeSerialize) {
994
+ try {
995
+ if (causeSerialize === true) {
996
+ const causeValue = (error0 as { cause?: unknown }).cause
997
+ if (this.is(causeValue)) {
998
+ json.cause = this.serialize(causeValue, isPublic)
999
+ }
1000
+ } else {
1001
+ const serializedCause = causeSerialize({
1002
+ value: (error0 as { cause?: unknown }).cause,
1003
+ error: error0,
1004
+ isPublic,
1005
+ })
1006
+ if (serializedCause !== undefined) {
1007
+ json.cause = serializedCause
1008
+ }
1009
+ }
1010
+ } catch {
1011
+ // eslint-disable-next-line no-console
1012
+ console.error('Error0: failed to serialize cause', error0)
1013
+ }
723
1014
  }
724
1015
  return Object.fromEntries(Object.entries(json).filter(([, value]) => value !== undefined)) as Record<
725
1016
  string,
@@ -727,7 +1018,7 @@ export class Error0 extends Error {
727
1018
  >
728
1019
  }
729
1020
 
730
- serialize(isPublic = true): object {
1021
+ serialize(isPublic = true): Record<string, unknown> {
731
1022
  const ctor = this.constructor as typeof Error0
732
1023
  return ctor.serialize(this, isPublic)
733
1024
  }