@devp0nt/error0 1.0.0-next.38 → 1.0.0-next.39

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
@@ -1,615 +1,717 @@
1
- import { Meta0 } from '@devp0nt/meta0'
2
- import { meta0PluginTag } from '@devp0nt/meta0/plugins/meta0-plugin-tag'
3
- import { type AxiosError, HttpStatusCode, isAxiosError } from 'axios'
4
- import get from 'lodash/get.js'
5
- import { ZodError } from 'zod'
6
-
7
- // TODO: .getMessage, .getClientMessage, .getDescription
8
- // TODO: clientDescription
9
- // TODO: Эррор 0 можно передать и вторым аргументом в логгер, тогда её месадж попадёт в мету, а первый месадж в сам месадж логера
10
- // TODO: Зод, аксиос, это всё плагины
11
- // TODO: В эррор0 добавить ориджинал
12
- // TODO: store tags as array from all causes
13
- // TODO: not use self stack if toError0
14
- // TODO: fix default message in extended error0, should be used in constuctor of Error0
15
- // TODO: remove defaults prop from getPropsFromUnknown
16
- // TODO: code has enum type, fn to check if code exists
17
-
18
- const isFilled = <T>(value: T): value is NonNullable<T> => value !== null && value !== undefined && value !== ''
19
- const toStringOrUndefined = (value: unknown): string | undefined => {
20
- return typeof value === 'string' ? value : undefined
1
+ export type ErrorExtensionPropOptions<TInputValue, TOutputValue, TError extends Error0 = Error0> = {
2
+ input: (value: TInputValue) => TOutputValue
3
+ output: (error: TError) => TOutputValue
4
+ // TODO: in serialize we should get full error0 not just value
5
+ serialize: (value: TOutputValue, isPublic: boolean) => unknown
21
6
  }
22
- const toNumberOrUndefined = (value: unknown): number | undefined => {
23
- if (typeof value === 'number') {
24
- return value
25
- }
26
- const number = Number(value)
27
- if (typeof value === 'string' && !Number.isNaN(number)) {
28
- return number
7
+ export type ErrorExtensionCopmputedFn<TOutputValue, TError extends Error0 = Error0> = (error: TError) => TOutputValue
8
+ export type ErrorExtensionMethodFn<
9
+ TOutputValue,
10
+ TArgs extends unknown[] = unknown[],
11
+ TError extends Error0 = Error0,
12
+ > = (error: TError, ...args: TArgs) => TOutputValue
13
+ export type ErrorExtensionRefineResult<TOutputProps extends Record<string, unknown>> = Partial<TOutputProps> | undefined
14
+ export type ErrorExtensionRefineFn<
15
+ TError extends Error0 = Error0,
16
+ TOutputProps extends Record<string, unknown> = Record<never, never>,
17
+ > = ((error: TError) => void) | ((error: TError) => ErrorExtensionRefineResult<TOutputProps>)
18
+ type ErrorMethodRecord = {
19
+ args: unknown[]
20
+ output: unknown
21
+ }
22
+
23
+ export type ErrorExtensionProps = { [key: string]: ErrorExtensionPropOptions<any, any> }
24
+ export type ErrorExtensionComputed = { [key: string]: ErrorExtensionCopmputedFn<any> }
25
+ export type ErrorExtensionMethods = { [key: string]: ErrorExtensionMethodFn<any, any[]> }
26
+
27
+ export type ErrorExtension<
28
+ TProps extends ErrorExtensionProps = Record<never, never>,
29
+ TComputed extends ErrorExtensionComputed = Record<never, never>,
30
+ TMethods extends ErrorExtensionMethods = Record<never, never>,
31
+ > = {
32
+ props?: TProps
33
+ computed?: TComputed
34
+ methods?: TMethods
35
+ refine?: Array<ErrorExtensionRefineFn<Error0, ExtensionOutputProps<TProps>>>
36
+ }
37
+ type AddPropToExtensionProps<
38
+ TProps extends ErrorExtensionProps,
39
+ TKey extends string,
40
+ TInputValue,
41
+ TOutputValue,
42
+ > = TProps & Record<TKey, ErrorExtensionPropOptions<TInputValue, TOutputValue>>
43
+ type AddComputedToExtensionComputed<
44
+ TComputed extends ErrorExtensionComputed,
45
+ TKey extends string,
46
+ TOutputValue,
47
+ > = TComputed & Record<TKey, ErrorExtensionCopmputedFn<TOutputValue>>
48
+ type AddMethodToExtensionMethods<
49
+ TMethods extends ErrorExtensionMethods,
50
+ TKey extends string,
51
+ TArgs extends unknown[],
52
+ TOutputValue,
53
+ > = TMethods & Record<TKey, ErrorExtensionMethodFn<TOutputValue, TArgs>>
54
+ type ExtensionOutputProps<TProps extends ErrorExtensionProps> = {
55
+ [TKey in keyof TProps]: TProps[TKey] extends ErrorExtensionPropOptions<any, infer TOutputValue> ? TOutputValue : never
56
+ }
57
+ export type ErrorExtensionsMap = {
58
+ props: Record<string, { input: unknown; output: unknown }>
59
+ computed: Record<string, unknown>
60
+ methods: Record<string, ErrorMethodRecord>
61
+ }
62
+ export type IsEmptyObject<T> = keyof T extends never ? true : false
63
+ export type ErrorInputBase = {
64
+ cause?: unknown
65
+ }
66
+ export type ErrorInput<TExtensionsMap extends ErrorExtensionsMap> =
67
+ IsEmptyObject<TExtensionsMap['props']> extends true
68
+ ? ErrorInputBase
69
+ : ErrorInputBase &
70
+ Partial<{
71
+ [TKey in keyof TExtensionsMap['props']]: TExtensionsMap['props'][TKey]['input']
72
+ }>
73
+
74
+ type ErrorOutputProps<TExtensionsMap extends ErrorExtensionsMap> = {
75
+ [TKey in keyof TExtensionsMap['props']]: TExtensionsMap['props'][TKey]['output']
76
+ }
77
+ type ErrorOutputComputed<TExtensionsMap extends ErrorExtensionsMap> = {
78
+ [TKey in keyof TExtensionsMap['computed']]: TExtensionsMap['computed'][TKey]
79
+ }
80
+ type ErrorOutputMethods<TExtensionsMap extends ErrorExtensionsMap> = {
81
+ [TKey in keyof TExtensionsMap['methods']]: TExtensionsMap['methods'][TKey] extends {
82
+ args: infer TArgs extends unknown[]
83
+ output: infer TOutput
29
84
  }
30
- return undefined
85
+ ? (...args: TArgs) => TOutput
86
+ : never
31
87
  }
32
- const toBooleanOrUndefined = (value: unknown): boolean | undefined => {
33
- if (typeof value === 'boolean') {
34
- return value
88
+ export type ErrorOutput<TExtensionsMap extends ErrorExtensionsMap> = ErrorOutputProps<TExtensionsMap> &
89
+ ErrorOutputComputed<TExtensionsMap> &
90
+ ErrorOutputMethods<TExtensionsMap>
91
+
92
+ type ErrorStaticMethods<TExtensionsMap extends ErrorExtensionsMap> = {
93
+ [TKey in keyof TExtensionsMap['methods']]: TExtensionsMap['methods'][TKey] extends {
94
+ args: infer TArgs extends unknown[]
95
+ output: infer TOutput
35
96
  }
36
- return undefined
97
+ ? (error: unknown, ...args: TArgs) => TOutput
98
+ : never
37
99
  }
38
100
 
39
- export class Error0 extends Error {
40
- public readonly __I_AM_ERROR_0 = true as const
41
-
42
- public readonly tag?: Error0.GeneralProps['tag']
43
- public readonly code?: Error0.GeneralProps['code']
44
- public readonly httpStatus?: Error0.GeneralProps['httpStatus']
45
- public readonly expected?: Error0.GeneralProps['expected']
46
- public readonly clientMessage?: Error0.GeneralProps['clientMessage']
47
- public readonly anyMessage?: Error0.GeneralProps['anyMessage']
48
- public override readonly cause?: Error0.GeneralProps['cause']
49
- public readonly meta?: Meta0.ValueTypeNullish
50
- public readonly zodError?: Error0.GeneralProps['zodError']
51
- public readonly axiosError?: Error0.GeneralProps['axiosError']
52
-
53
- static defaultMessage = 'Unknown error'
54
- static defaultCode?: Error0.GeneralProps['code']
55
- static defaultHttpStatus?: Error0.GeneralProps['httpStatus']
56
- static defaultExpected?: Error0.GeneralProps['expected']
57
- static defaultClientMessage?: Error0.GeneralProps['clientMessage']
58
- static defaultMeta?: Meta0.Meta0OrValueTypeNullish
59
-
60
- public readonly propsOriginal: Error0.GeneralProps
61
-
62
- constructor(message: string)
63
- constructor(input: Error0.Input)
64
- constructor(message: string, input: Error0.Input)
65
- constructor(error: Error)
66
- constructor(error: Error, input: Error0.Input)
67
- constructor(value: unknown)
68
- constructor(value: unknown, input: Error0.Input)
69
- constructor(...args: unknown[]) {
70
- const input: Partial<Error0.Input> = {}
71
- if (args[0] instanceof Error) {
72
- input.cause = args[0]
73
- } else if (typeof args[0] === 'object' && args[0] !== null) {
74
- Object.assign(input, args[0])
75
- } else if (typeof args[0] === 'string') {
76
- input.message = args[0]
77
- }
78
- if (typeof args[1] === 'object' && args[1] !== null) {
79
- Object.assign(input, args[1])
80
- }
81
- const safeInput = Error0._safeParseInput(input)
101
+ type EmptyExtensionsMap = {
102
+ props: Record<never, { input: never; output: never }>
103
+ computed: Record<never, never>
104
+ methods: Record<never, ErrorMethodRecord>
105
+ }
82
106
 
83
- const message = safeInput.message || Error0.defaultMessage
84
- super(message)
85
- Object.setPrototypeOf(this, (this.constructor as typeof Error0).prototype)
86
- this.name = 'Error0'
107
+ type ErrorExtensionResolved = {
108
+ props: Record<string, ErrorExtensionPropOptions<unknown, unknown>>
109
+ computed: Record<string, ErrorExtensionCopmputedFn<unknown>>
110
+ methods: Record<string, ErrorExtensionMethodFn<unknown>>
111
+ refine: Array<ErrorExtensionRefineFn<Error0, Record<string, unknown>>>
112
+ }
87
113
 
88
- this.propsOriginal = (this.constructor as typeof Error0)._getSelfGeneralProps({
89
- error0Input: safeInput,
90
- message,
91
- stack: safeInput.stack || this.stack,
92
- })
93
- const causesProps = (this.constructor as typeof Error0)._getCausesPropsFromError0Props(
94
- this.propsOriginal,
95
- (this.constructor as typeof Error0).defaultMaxLevel,
96
- )
97
- const propsFloated = (this.constructor as typeof Error0)._getSelfPropsFloated(causesProps)
98
- this.tag = propsFloated.tag
99
- this.code = propsFloated.code
100
- this.httpStatus = propsFloated.httpStatus
101
- this.expected = propsFloated.expected
102
- this.clientMessage = propsFloated.clientMessage
103
- this.cause = propsFloated.cause
104
- this.stack = propsFloated.stack
105
- this.meta = propsFloated.meta
106
- this.zodError = propsFloated.zodError
107
- this.axiosError = propsFloated.axiosError
108
- }
109
-
110
- // settings
111
-
112
- static defaultMaxLevel = 10
113
-
114
- // props
115
-
116
- public static _safeParseInput(error0Input: Record<string, unknown>): Error0.Input {
117
- const result: Error0.Input = {}
118
- result.message = typeof error0Input.message === 'string' ? error0Input.message : undefined
119
- result.tag = typeof error0Input.tag === 'string' ? error0Input.tag : undefined
120
- result.code = typeof error0Input.code === 'string' ? error0Input.code : undefined
121
- result.httpStatus =
122
- typeof error0Input.httpStatus === 'number' || typeof error0Input.httpStatus === 'string'
123
- ? (error0Input.httpStatus as never)
124
- : undefined
125
- result.expected =
126
- typeof error0Input.expected === 'function' || typeof error0Input.expected === 'boolean'
127
- ? (error0Input.expected as never)
128
- : undefined
129
- result.clientMessage = typeof error0Input.clientMessage === 'string' ? error0Input.clientMessage : undefined
130
- result.cause = error0Input.cause
131
- result.stack = typeof error0Input.stack === 'string' ? error0Input.stack : undefined
132
- // result.meta0 =
133
- // error0Input.meta0 instanceof Meta0 ? error0Input.meta0 : undefined
134
- // result.meta =
135
- // typeof error0Input.meta === "object" && error0Input.meta !== null
136
- // ? error0Input.meta
137
- // : undefined
138
- result.meta =
139
- error0Input.meta instanceof Meta0
140
- ? error0Input.meta
141
- : typeof error0Input.meta === 'object' && error0Input.meta !== null
142
- ? (error0Input.meta as Meta0.ValueType)
143
- : undefined
144
- result.zodError = error0Input.zodError instanceof ZodError ? error0Input.zodError : undefined
145
- result.axiosError = isAxiosError(error0Input.axiosError) ? error0Input.axiosError : undefined
146
- return result
147
- }
148
-
149
- public static _getSelfGeneralProps({
150
- error0Input,
151
- message,
152
- stack,
153
- }: {
154
- error0Input: Error0.Input
155
- message: string
156
- stack: Error0.GeneralProps['stack']
157
- }): Error0.GeneralProps {
158
- // const meta = Meta0.merge(error0Input.meta0, error0Input.meta).value
159
-
160
- // const meta0 = Meta0.extend(error0Input.meta, this.defaultMeta)
161
- // const defaultMetaValue =
162
- // this.defaultMeta && typeof this.defaultMeta === 'object' && 'getValue' in this.defaultMeta
163
- // ? (this.defaultMeta as any).getValue()
164
- // : this.defaultMeta
165
-
166
- const meta0 = Meta0.extend(this.defaultMeta, error0Input.meta)
167
- const meta = meta0.getValue()
168
- const finalTag = meta0PluginTag.public.getFullTag(meta0, error0Input.tag)
169
- delete meta.tagPrefix
170
- const clientMessage = error0Input.clientMessage || this.defaultClientMessage
171
- const result: Error0.GeneralProps = {
172
- message: error0Input.message || this.defaultMessage,
173
- tag: finalTag,
174
- code: error0Input.code || toStringOrUndefined(meta.code) || this.defaultCode,
175
- httpStatus:
176
- typeof error0Input.httpStatus === 'number'
177
- ? error0Input.httpStatus
178
- : error0Input.httpStatus &&
179
- typeof error0Input.httpStatus === 'string' &&
180
- error0Input.httpStatus in HttpStatusCode
181
- ? HttpStatusCode[error0Input.httpStatus]
182
- : toNumberOrUndefined(meta.httpStatus) || this.defaultHttpStatus,
183
- expected: undefined,
184
- clientMessage,
185
- anyMessage: clientMessage || message,
186
- cause: error0Input.cause,
187
- stack: undefined,
188
- meta,
189
- zodError: error0Input.zodError,
190
- axiosError: error0Input.axiosError,
191
- }
192
- result.expected = this._normalizeSelfExpected(
193
- result,
194
- typeof error0Input.expected === 'boolean' || typeof error0Input.expected === 'function'
195
- ? error0Input.expected
196
- : toBooleanOrUndefined(meta.expected) || this.defaultExpected,
197
- )
198
- result.stack = this._removeConstructorStackPart(stack)
199
- return result
200
- }
201
-
202
- public static _getSelfPropsFloated(causesProps: Error0.GeneralProps[]): Error0.GeneralProps {
203
- const cause = this._getClosestPropValue(causesProps, 'cause')
204
- const stack = this._mergeStack(causesProps[1]?.stack, causesProps[0]?.stack)
205
- const closestTag = this._getClosestPropValue(causesProps, 'tag')
206
- const meta = this._getMergedMetaValue(causesProps)
207
- const tag = meta0PluginTag.public.getFullTag(meta, closestTag)
208
- const propsFloated: Error0.GeneralProps = {
209
- message: this._getClosestPropValue(causesProps, 'message'),
210
- tag,
211
- code: this._getClosestPropValue(causesProps, 'code'),
212
- httpStatus: this._getClosestPropValue(causesProps, 'httpStatus'),
213
- expected: this._isExpected(causesProps),
214
- clientMessage: this._getClosestPropValue(causesProps, 'clientMessage'),
215
- cause,
216
- stack,
217
- anyMessage: causesProps[0].anyMessage,
218
- meta,
219
- zodError: this._getClosestPropValue(causesProps, 'zodError'),
220
- axiosError: this._getClosestPropValue(causesProps, 'axiosError'),
221
- }
222
- return propsFloated
114
+ type ExtensionPropsMapOf<TExtension extends ErrorExtension> = {
115
+ [TKey in keyof NonNullable<TExtension['props']>]: NonNullable<
116
+ TExtension['props']
117
+ >[TKey] extends ErrorExtensionPropOptions<infer TInputValue, infer TOutputValue>
118
+ ? { input: TInputValue; output: TOutputValue }
119
+ : never
120
+ }
121
+ type ExtensionComputedMapOf<TExtension extends ErrorExtension> = {
122
+ [TKey in keyof NonNullable<TExtension['computed']>]: NonNullable<
123
+ TExtension['computed']
124
+ >[TKey] extends ErrorExtensionCopmputedFn<infer TOutputValue>
125
+ ? TOutputValue
126
+ : never
127
+ }
128
+ type ExtensionMethodsMapOf<TExtension extends ErrorExtension> = {
129
+ [TKey in keyof NonNullable<TExtension['methods']>]: NonNullable<TExtension['methods']>[TKey] extends (
130
+ error: Error0,
131
+ ...args: infer TArgs extends unknown[]
132
+ ) => infer TOutput
133
+ ? { args: TArgs; output: TOutput }
134
+ : never
135
+ }
136
+ type ErrorExtensionsMapOfExtension<TExtension extends ErrorExtension> = {
137
+ props: ExtensionPropsMapOf<TExtension>
138
+ computed: ExtensionComputedMapOf<TExtension>
139
+ methods: ExtensionMethodsMapOf<TExtension>
140
+ }
141
+ type ExtendErrorExtensionsMap<TMap extends ErrorExtensionsMap, TExtension extends ErrorExtension> = {
142
+ props: TMap['props'] & ErrorExtensionsMapOfExtension<TExtension>['props']
143
+ computed: TMap['computed'] & ErrorExtensionsMapOfExtension<TExtension>['computed']
144
+ methods: TMap['methods'] & ErrorExtensionsMapOfExtension<TExtension>['methods']
145
+ }
146
+ type ExtendErrorExtensionsMapWithProp<
147
+ TMap extends ErrorExtensionsMap,
148
+ TKey extends string,
149
+ TInputValue,
150
+ TOutputValue,
151
+ > = ExtendErrorExtensionsMap<TMap, ErrorExtension<Record<TKey, ErrorExtensionPropOptions<TInputValue, TOutputValue>>>>
152
+ type ExtendErrorExtensionsMapWithComputed<
153
+ TMap extends ErrorExtensionsMap,
154
+ TKey extends string,
155
+ TOutputValue,
156
+ > = ExtendErrorExtensionsMap<
157
+ TMap,
158
+ ErrorExtension<Record<never, never>, Record<TKey, ErrorExtensionCopmputedFn<TOutputValue>>>
159
+ >
160
+ type ExtendErrorExtensionsMapWithMethod<
161
+ TMap extends ErrorExtensionsMap,
162
+ TKey extends string,
163
+ TArgs extends unknown[],
164
+ TOutputValue,
165
+ > = ExtendErrorExtensionsMap<
166
+ TMap,
167
+ ErrorExtension<Record<never, never>, Record<never, never>, Record<TKey, ErrorExtensionMethodFn<TOutputValue, TArgs>>>
168
+ >
169
+
170
+ type ExtensionsMapOf<TClass> = TClass extends { __extensionsMap?: infer TExtensionsMap }
171
+ ? TExtensionsMap extends ErrorExtensionsMap
172
+ ? TExtensionsMap
173
+ : EmptyExtensionsMap
174
+ : EmptyExtensionsMap
175
+
176
+ type ExtensionsMapFromParts<
177
+ TProps extends ErrorExtensionProps,
178
+ TComputed extends ErrorExtensionComputed,
179
+ TMethods extends ErrorExtensionMethods,
180
+ > = ErrorExtensionsMapOfExtension<ErrorExtension<TProps, TComputed, TMethods>>
181
+ type ErrorInstanceOfMap<TMap extends ErrorExtensionsMap> = Error0 & ErrorOutput<TMap>
182
+ type BuilderError0<
183
+ TProps extends ErrorExtensionProps,
184
+ TComputed extends ErrorExtensionComputed,
185
+ TMethods extends ErrorExtensionMethods,
186
+ > = Error0 & ErrorOutput<ExtensionsMapFromParts<TProps, TComputed, TMethods>>
187
+
188
+ type ExtensionOfBuilder<TBuilder> =
189
+ TBuilder extends ExtensionError0<infer TProps, infer TComputed, infer TMethods>
190
+ ? ErrorExtension<TProps, TComputed, TMethods>
191
+ : never
192
+
193
+ export type ErrorFlowPolicy = 'instance' | 'error0' | 'likeError0' | 'all'
194
+
195
+ export class ExtensionError0<
196
+ TProps extends ErrorExtensionProps = Record<never, never>,
197
+ TComputed extends ErrorExtensionComputed = Record<never, never>,
198
+ TMethods extends ErrorExtensionMethods = Record<never, never>,
199
+ > {
200
+ private readonly _extension: ErrorExtension<ErrorExtensionProps, ErrorExtensionComputed, ErrorExtensionMethods>
201
+
202
+ readonly Infer = undefined as unknown as {
203
+ props: TProps
204
+ computed: TComputed
205
+ methods: TMethods
223
206
  }
224
207
 
225
- // sepcial
226
-
227
- public static _getExtraError0PropsByZodError(zodError: ZodError): Partial<Error0.GeneralProps> {
228
- return {
229
- message: `Zod Validation Error: ${zodError.message}`,
208
+ constructor(extension?: ErrorExtension<ErrorExtensionProps, ErrorExtensionComputed, ErrorExtensionMethods>) {
209
+ this._extension = {
210
+ props: { ...(extension?.props ?? {}) },
211
+ computed: { ...(extension?.computed ?? {}) },
212
+ methods: { ...(extension?.methods ?? {}) },
213
+ refine: [...(extension?.refine ?? [])],
230
214
  }
231
215
  }
232
216
 
233
- public static _getExtraError0PropsByAxiosError(axiosError: AxiosError): Partial<Error0.GeneralProps> {
234
- return {
235
- message: 'Axios Error',
236
- meta: {
237
- axiosData: (() => {
238
- try {
239
- return JSON.stringify(axiosError.response?.data)
240
- } catch {
241
- return undefined
242
- }
243
- })(),
244
- axiosStatus: axiosError.response?.status,
245
- },
246
- }
217
+ prop<TKey extends string, TInputValue, TOutputValue>(
218
+ key: TKey,
219
+ value: ErrorExtensionPropOptions<TInputValue, TOutputValue, BuilderError0<TProps, TComputed, TMethods>>,
220
+ ): ExtensionError0<AddPropToExtensionProps<TProps, TKey, TInputValue, TOutputValue>, TComputed, TMethods> {
221
+ return this.extend('prop', key, value)
247
222
  }
248
223
 
249
- public static _assignError0Props(
250
- error0Props: Error0.GeneralProps,
251
- extraError0Props: Partial<Error0.GeneralProps>,
252
- ): void {
253
- const metaValue = Meta0.mergeValues(error0Props.meta, extraError0Props.meta)
254
- Object.assign(error0Props, extraError0Props, { meta: metaValue })
224
+ computed<TKey extends string, TOutputValue>(
225
+ key: TKey,
226
+ value: ErrorExtensionCopmputedFn<TOutputValue, BuilderError0<TProps, TComputed, TMethods>>,
227
+ ): ExtensionError0<TProps, AddComputedToExtensionComputed<TComputed, TKey, TOutputValue>, TMethods> {
228
+ return this.extend('computed', key, value)
255
229
  }
256
230
 
257
- // expected
231
+ method<TKey extends string, TArgs extends unknown[], TOutputValue>(
232
+ key: TKey,
233
+ value: ErrorExtensionMethodFn<TOutputValue, TArgs, BuilderError0<TProps, TComputed, TMethods>>,
234
+ ): ExtensionError0<TProps, TComputed, AddMethodToExtensionMethods<TMethods, TKey, TArgs, TOutputValue>> {
235
+ return this.extend('method', key, value)
236
+ }
258
237
 
259
- public static _normalizeSelfExpected(
260
- error0Props: Error0.GeneralProps,
261
- expectedProvided: Error0.Input['expected'],
262
- ): boolean | undefined {
263
- if (typeof expectedProvided === 'function') {
264
- return expectedProvided(error0Props)
265
- }
266
- return expectedProvided
238
+ refine(
239
+ value: ErrorExtensionRefineFn<BuilderError0<TProps, TComputed, TMethods>, ExtensionOutputProps<TProps>>,
240
+ ): ExtensionError0<TProps, TComputed, TMethods> {
241
+ return this.extend('refine', value)
267
242
  }
268
243
 
269
- public static _isExpected(causesProps: Error0.GeneralProps[]): boolean {
270
- let hasExpectedTrue = false
271
- for (const causeProps of causesProps) {
272
- if (causeProps.expected === false) {
273
- return false
244
+ extend<TKey extends string, TInputValue, TOutputValue>(
245
+ kind: 'prop',
246
+ key: TKey,
247
+ value: ErrorExtensionPropOptions<TInputValue, TOutputValue, BuilderError0<TProps, TComputed, TMethods>>,
248
+ ): ExtensionError0<AddPropToExtensionProps<TProps, TKey, TInputValue, TOutputValue>, TComputed, TMethods>
249
+ extend<TKey extends string, TOutputValue>(
250
+ kind: 'computed',
251
+ key: TKey,
252
+ value: ErrorExtensionCopmputedFn<TOutputValue, BuilderError0<TProps, TComputed, TMethods>>,
253
+ ): ExtensionError0<TProps, AddComputedToExtensionComputed<TComputed, TKey, TOutputValue>, TMethods>
254
+ extend<TKey extends string, TArgs extends unknown[], TOutputValue>(
255
+ kind: 'method',
256
+ key: TKey,
257
+ value: ErrorExtensionMethodFn<TOutputValue, TArgs, BuilderError0<TProps, TComputed, TMethods>>,
258
+ ): ExtensionError0<TProps, TComputed, AddMethodToExtensionMethods<TMethods, TKey, TArgs, TOutputValue>>
259
+ extend(
260
+ kind: 'refine',
261
+ value: ErrorExtensionRefineFn<BuilderError0<TProps, TComputed, TMethods>, ExtensionOutputProps<TProps>>,
262
+ ): ExtensionError0<TProps, TComputed, TMethods>
263
+ extend(
264
+ kind: 'prop' | 'computed' | 'method' | 'refine',
265
+ keyOrValue: string | ErrorExtensionRefineFn<any, any>,
266
+ value?:
267
+ | ErrorExtensionPropOptions<unknown, unknown, any>
268
+ | ErrorExtensionCopmputedFn<unknown, any>
269
+ | ErrorExtensionMethodFn<unknown, unknown[], any>,
270
+ ): ExtensionError0<any, any, any> {
271
+ const nextProps: ErrorExtensionProps = { ...(this._extension.props ?? {}) }
272
+ const nextComputed: ErrorExtensionComputed = { ...(this._extension.computed ?? {}) }
273
+ const nextMethods: ErrorExtensionMethods = { ...(this._extension.methods ?? {}) }
274
+ const nextRefine: Array<ErrorExtensionRefineFn<Error0, Record<string, unknown>>> = [
275
+ ...(this._extension.refine ?? []),
276
+ ]
277
+ if (kind === 'prop') {
278
+ const key = keyOrValue as string
279
+ if (value === undefined) {
280
+ throw new Error('ExtensionError0.extend("prop", key, value) requires value')
274
281
  }
275
- if (causeProps.expected === true) {
276
- hasExpectedTrue = true
282
+ nextProps[key] = value as ErrorExtensionPropOptions<any, any>
283
+ } else if (kind === 'computed') {
284
+ const key = keyOrValue as string
285
+ if (value === undefined) {
286
+ throw new Error('ExtensionError0.extend("computed", key, value) requires value')
277
287
  }
288
+ nextComputed[key] = value as ErrorExtensionCopmputedFn<any>
289
+ } else if (kind === 'method') {
290
+ const key = keyOrValue as string
291
+ if (value === undefined) {
292
+ throw new Error('ExtensionError0.extend("method", key, value) requires value')
293
+ }
294
+ nextMethods[key] = value as ErrorExtensionMethodFn<any, any[]>
295
+ } else {
296
+ nextRefine.push(keyOrValue as ErrorExtensionRefineFn<Error0, Record<string, unknown>>)
278
297
  }
279
- return hasExpectedTrue
298
+ return new ExtensionError0({
299
+ props: nextProps,
300
+ computed: nextComputed,
301
+ methods: nextMethods,
302
+ refine: nextRefine,
303
+ })
280
304
  }
305
+ }
281
306
 
282
- // getters
307
+ export type ClassError0<TExtensionsMap extends ErrorExtensionsMap = EmptyExtensionsMap> = {
308
+ new (message: string, input?: ErrorInput<TExtensionsMap>): Error0 & ErrorOutput<TExtensionsMap>
309
+ new (input: { message: string } & ErrorInput<TExtensionsMap>): Error0 & ErrorOutput<TExtensionsMap>
310
+ readonly __extensionsMap?: TExtensionsMap
311
+ from: (error: unknown) => Error0 & ErrorOutput<TExtensionsMap>
312
+ serialize: (error: unknown, isPublic?: boolean) => object
313
+ extend: {
314
+ <TBuilder extends ExtensionError0>(
315
+ extension: TBuilder,
316
+ ): ClassError0<ExtendErrorExtensionsMap<TExtensionsMap, ExtensionOfBuilder<TBuilder>>>
317
+ <TKey extends string, TInputValue, TOutputValue>(
318
+ kind: 'prop',
319
+ key: TKey,
320
+ value: ErrorExtensionPropOptions<TInputValue, TOutputValue, ErrorInstanceOfMap<TExtensionsMap>>,
321
+ ): ClassError0<ExtendErrorExtensionsMapWithProp<TExtensionsMap, TKey, TInputValue, TOutputValue>>
322
+ <TKey extends string, TOutputValue>(
323
+ kind: 'computed',
324
+ key: TKey,
325
+ value: ErrorExtensionCopmputedFn<TOutputValue, ErrorInstanceOfMap<TExtensionsMap>>,
326
+ ): ClassError0<ExtendErrorExtensionsMapWithComputed<TExtensionsMap, TKey, TOutputValue>>
327
+ <TKey extends string, TArgs extends unknown[], TOutputValue>(
328
+ kind: 'method',
329
+ key: TKey,
330
+ value: ErrorExtensionMethodFn<TOutputValue, TArgs, ErrorInstanceOfMap<TExtensionsMap>>,
331
+ ): ClassError0<ExtendErrorExtensionsMapWithMethod<TExtensionsMap, TKey, TArgs, TOutputValue>>
332
+ (
333
+ kind: 'refine',
334
+ value: ErrorExtensionRefineFn<ErrorInstanceOfMap<TExtensionsMap>, ErrorOutputProps<TExtensionsMap>>,
335
+ ): ClassError0<TExtensionsMap>
336
+ }
337
+ extension: () => ExtensionError0
338
+ } & ErrorStaticMethods<TExtensionsMap>
283
339
 
284
- public static _getPropsFromUnknown(error: unknown, defaults?: Error0.Input): Error0.GeneralProps {
285
- if (typeof error !== 'object' || error === null) {
286
- return {
287
- message: undefined,
288
- tag: undefined,
289
- code: undefined,
290
- httpStatus: undefined,
291
- expected: undefined,
292
- clientMessage: undefined,
293
- anyMessage: this.defaultMessage,
294
- cause: undefined,
295
- stack: undefined,
296
- zodError: undefined,
297
- axiosError: undefined,
298
- meta: {},
299
- }
300
- }
301
- const message = 'message' in error && typeof error.message === 'string' ? error.message : undefined
302
- const clientMessage =
303
- 'clientMessage' in error && typeof error.clientMessage === 'string'
304
- ? error.clientMessage
305
- : defaults?.clientMessage || undefined
306
- const result: Error0.GeneralProps = {
307
- message,
308
- code: 'code' in error && typeof error.code === 'string' ? error.code : defaults?.code || undefined,
309
- clientMessage,
310
- anyMessage: clientMessage || message || this.defaultMessage,
311
- expected: undefined,
312
- stack: 'stack' in error && typeof error.stack === 'string' ? error.stack : undefined,
313
- tag: 'tag' in error && typeof error.tag === 'string' ? error.tag : defaults?.tag || undefined,
314
- cause: 'cause' in error ? error.cause : defaults?.cause || undefined,
315
- meta:
316
- 'meta' in error && typeof error.meta === 'object' && error.meta !== null
317
- ? Meta0.getValue(error.meta as Meta0.ValueType)
318
- : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
319
- Meta0.getValue(defaults?.meta) || {},
320
- httpStatus:
321
- 'httpStatus' in error && typeof error.httpStatus === 'number' && error.httpStatus in HttpStatusCode
322
- ? error.httpStatus
323
- : typeof defaults?.httpStatus === 'string'
324
- ? HttpStatusCode[defaults.httpStatus]
325
- : defaults?.httpStatus,
326
- zodError:
327
- 'zodError' in error && error.zodError instanceof ZodError
328
- ? error.zodError
329
- : error instanceof ZodError
330
- ? error
331
- : defaults?.zodError,
332
- axiosError:
333
- 'axiosError' in error && isAxiosError(error.axiosError)
334
- ? error.axiosError
335
- : isAxiosError(error)
336
- ? error
337
- : defaults?.axiosError,
338
- }
339
- result.expected = this._normalizeSelfExpected(
340
- result,
341
- 'expected' in error && (typeof error.expected === 'boolean' || typeof error.expected === 'function')
342
- ? (error.expected as Error0.ExpectedFn)
343
- : defaults?.expected || undefined,
344
- )
345
- if (result.zodError) {
346
- this._assignError0Props(result, this._getExtraError0PropsByZodError(result.zodError))
347
- }
348
- if (result.axiosError) {
349
- this._assignError0Props(result, this._getExtraError0PropsByAxiosError(result.axiosError))
350
- }
351
- return result
340
+ export class Error0 extends Error {
341
+ static readonly __extensionsMap?: EmptyExtensionsMap
342
+ protected static _extensions: ErrorExtension[] = []
343
+
344
+ private static readonly _emptyExtension: ErrorExtensionResolved = {
345
+ props: {},
346
+ computed: {},
347
+ methods: {},
348
+ refine: [],
352
349
  }
353
350
 
354
- public static _getCausesPropsFromUnknown(error: unknown, maxLevel: number): Error0.GeneralProps[] {
355
- if (!error) {
356
- return []
357
- }
358
- const causeProps = this._getPropsFromUnknown(error)
359
- const causesProps: Error0.GeneralProps[] = [causeProps]
360
- if (!causeProps.cause) {
361
- return causesProps
351
+ private static _getResolvedExtension(this: typeof Error0): ErrorExtensionResolved {
352
+ const resolved: ErrorExtensionResolved = {
353
+ props: {},
354
+ computed: {},
355
+ methods: {},
356
+ refine: [],
362
357
  }
363
- if (maxLevel > 0) {
364
- causesProps.push(...this._getCausesPropsFromUnknown(this._getPropsFromUnknown(causeProps.cause), maxLevel - 1))
358
+ for (const extension of this._extensions) {
359
+ Object.assign(resolved.props, extension.props ?? this._emptyExtension.props)
360
+ Object.assign(resolved.computed, extension.computed ?? this._emptyExtension.computed)
361
+ Object.assign(resolved.methods, extension.methods ?? this._emptyExtension.methods)
362
+ resolved.refine.push(...(extension.refine ?? this._emptyExtension.refine))
365
363
  }
366
- return causesProps
364
+ return resolved
367
365
  }
368
366
 
369
- public static _getCausesPropsFromError0Props(
370
- error0Props: Error0.GeneralProps,
371
- maxLevel: number,
372
- ): Error0.GeneralProps[] {
373
- return [error0Props, ...this._getCausesPropsFromUnknown(error0Props.cause, maxLevel - 1)]
374
- }
367
+ constructor(message: string, input?: ErrorInput<EmptyExtensionsMap>)
368
+ constructor(input: { message: string } & ErrorInput<EmptyExtensionsMap>)
369
+ constructor(
370
+ ...args:
371
+ | [message: string, input?: ErrorInput<EmptyExtensionsMap>]
372
+ | [{ message: string } & ErrorInput<EmptyExtensionsMap>]
373
+ ) {
374
+ const [first, second] = args
375
+ const input = typeof first === 'string' ? { message: first, ...(second ?? {}) } : first
376
+
377
+ super(input.message, { cause: input.cause })
378
+ this.name = 'Error0'
375
379
 
376
- public static _getClosestPropValue<TPropKey extends keyof Error0.GeneralProps>(
377
- causesProps: Error0.GeneralProps[],
378
- propKey: TPropKey,
379
- ): NonNullable<Error0.GeneralProps[TPropKey]> | undefined {
380
- for (const causeProps of causesProps) {
381
- const propValue = causeProps[propKey]
382
- if (isFilled(propValue)) {
383
- return propValue
380
+ const ctor = this.constructor as typeof Error0
381
+ const extension = ctor._getResolvedExtension()
382
+
383
+ for (const [key, prop] of Object.entries(extension.props)) {
384
+ if (key in input) {
385
+ const ownValue = (input as Record<string, unknown>)[key]
386
+ ;(this as Record<string, unknown>)[key] = prop.input(ownValue)
387
+ } else {
388
+ Object.defineProperty(this, key, {
389
+ get: () => prop.output(this),
390
+ set: (value) => {
391
+ Object.defineProperty(this, key, {
392
+ value,
393
+ writable: true,
394
+ enumerable: true,
395
+ configurable: true,
396
+ })
397
+ },
398
+ enumerable: true,
399
+ configurable: true,
400
+ })
384
401
  }
385
402
  }
386
- return undefined
403
+
404
+ for (const [key, computed] of Object.entries(extension.computed)) {
405
+ Object.defineProperty(this, key, {
406
+ get: () => computed(this),
407
+ set: (value) => {
408
+ Object.defineProperty(this, key, {
409
+ value,
410
+ writable: true,
411
+ enumerable: true,
412
+ configurable: true,
413
+ })
414
+ },
415
+ enumerable: true,
416
+ configurable: true,
417
+ })
418
+ }
387
419
  }
388
420
 
389
- // private static getClosestByGetter<TResult>(
390
- // causesProps: Error0.GeneralProps[],
391
- // getter: (props: Error0.GeneralProps) => TResult,
392
- // ): NonNullable<TResult> | undefined {
393
- // for (const causeProps of causesProps) {
394
- // const result = getter(causeProps)
395
- // if (isFilled(result)) {
396
- // return result
397
- // }
398
- // }
399
- // return undefined
400
- // }
401
-
402
- public static _getFilledPropValues<TPropKey extends keyof Error0.Input>(
403
- causesProps: Error0.GeneralProps[],
404
- propKey: TPropKey,
405
- ): Array<NonNullable<Error0.GeneralProps[TPropKey]>> {
406
- const values: Array<NonNullable<Error0.GeneralProps[TPropKey]>> = []
407
- for (const causeProps of causesProps) {
408
- const propValue = causeProps[propKey]
409
- if (isFilled(propValue)) {
410
- values.push(propValue)
421
+ private static readonly isSelfProperty = (object: object, key: string): boolean => {
422
+ const d = Object.getOwnPropertyDescriptor(object, key)
423
+ if (!d) return false
424
+ if (typeof d.get === 'function' || typeof d.set === 'function') {
425
+ if ('name' in object && object.name === 'Error0') {
426
+ return false
427
+ } else {
428
+ return true
411
429
  }
412
430
  }
413
- return values
431
+ return true
414
432
  }
415
433
 
416
- public static _getMergedMetaValue(causesProps: Error0.GeneralProps[]): Meta0.ValueType {
417
- const metas = this._getFilledPropValues(causesProps, 'meta')
418
- if (metas.length === 0) {
419
- return {}
420
- } else if (metas.length === 1) {
421
- return metas[0]
422
- } else {
423
- return Meta0.mergeValues(metas[0], ...metas.slice(1))
434
+ static own(error: object, key: string): unknown {
435
+ if (this.isSelfProperty(error, key)) {
436
+ return (error as Record<string, unknown>)[key]
424
437
  }
438
+ return undefined
439
+ }
440
+ own(key: string): unknown {
441
+ const ctor = this.constructor as typeof Error0
442
+ return ctor.own(this, key)
425
443
  }
426
444
 
427
- // stack
445
+ static flow(error: object, key: string, policy: ErrorFlowPolicy = 'likeError0'): unknown[] {
446
+ return this.causes(error, policy).map((cause) => {
447
+ const causeRecord = cause as Record<string, unknown>
448
+ if (this.isSelfProperty(causeRecord, key)) {
449
+ return causeRecord[key]
450
+ }
451
+ return undefined
452
+ })
453
+ }
454
+ flow(key: string, policy: ErrorFlowPolicy = 'likeError0'): unknown[] {
455
+ const ctor = this.constructor as typeof Error0
456
+ return ctor.flow(this, key, policy)
457
+ }
428
458
 
429
- public static _removeConstructorStackPart(stack: Error0.GeneralProps['stack']): Error0.GeneralProps['stack'] {
430
- if (!stack) {
431
- return stack
432
- }
433
- let lines = stack.split('\n')
434
- const removeAllLinesContains = (search: string) => {
435
- lines = lines.filter((line) => !line.includes(search))
459
+ static causes(error: object, policy: ErrorFlowPolicy = 'all'): object[] {
460
+ const causes: object[] = []
461
+ let current: unknown = error
462
+ const maxDepth = 99
463
+ const seen = new Set<unknown>()
464
+
465
+ for (let depth = 0; depth < maxDepth; depth += 1) {
466
+ if (!current || typeof current !== 'object') {
467
+ break
468
+ }
469
+ if (seen.has(current)) {
470
+ break
471
+ }
472
+ if (policy === 'instance') {
473
+ if (!this.is(current)) {
474
+ break
475
+ }
476
+ } else if (policy === 'error0') {
477
+ if (!this.isError0(current)) {
478
+ break
479
+ }
480
+ } else if (policy === 'likeError0') {
481
+ if (!this.isLikeError0(current)) {
482
+ break
483
+ }
484
+ }
485
+ seen.add(current)
486
+ causes.push(current)
487
+ current = (current as { cause?: unknown }).cause
436
488
  }
437
- removeAllLinesContains('at new Error0')
438
- removeAllLinesContains('at _toError0')
439
- removeAllLinesContains('at Error0.from')
440
- removeAllLinesContains('at Error0._toError0')
441
- return lines.join('\n')
442
- }
443
489
 
444
- public static _mergeStack(
445
- prevStack: Error0.GeneralProps['stack'],
446
- nextStack: Error0.GeneralProps['stack'],
447
- ): Error0.GeneralProps['stack'] {
448
- return [nextStack, prevStack].filter(Boolean).join('\n\n') || undefined
490
+ return causes
491
+ }
492
+ causes(policy: ErrorFlowPolicy = 'all'): object[] {
493
+ const ctor = this.constructor as typeof Error0
494
+ return ctor.causes(this, policy)
449
495
  }
450
496
 
451
- // transformations
497
+ static is(error: unknown): error is Error0 {
498
+ return error instanceof this
499
+ }
452
500
 
453
501
  static isError0(error: unknown): error is Error0 {
454
502
  return error instanceof Error0
455
503
  }
456
504
 
457
- static isLikelyError0(error: unknown): error is Error0 {
458
- if (error instanceof Error0) {
459
- return true
505
+ static isLikeError0(error: unknown): error is Error0 | object {
506
+ return (
507
+ error instanceof Error0 ||
508
+ (typeof error === 'object' && error !== null && 'name' in error && error.name === 'Error0')
509
+ )
510
+ }
511
+
512
+ static from(error: unknown): Error0 {
513
+ if (this.is(error)) {
514
+ return error
460
515
  }
516
+ if (this.isLikeError0(error)) {
517
+ return this._fromLikeError0(error)
518
+ }
519
+ return this._fromNonError0(error)
520
+ }
461
521
 
462
- if (typeof error === 'object' && error !== null) {
463
- if ('__I_AM_ERROR_0' in error && error.__I_AM_ERROR_0 === true) {
464
- return true
522
+ private static _applyRefine(error: Error0): Error0 {
523
+ const extension = this._getResolvedExtension()
524
+ for (const refine of extension.refine) {
525
+ const refined = refine(error as any)
526
+ if (refined && typeof refined === 'object') {
527
+ Object.assign(error as unknown as Record<string, unknown>, refined)
465
528
  }
466
529
  }
467
-
468
- return false
530
+ return error
469
531
  }
470
532
 
471
- public static _toError0(error: unknown, inputOverride: Error0.Input = {}): Error0 {
472
- if (error instanceof Error0) {
473
- return error
533
+ private static _fromLikeError0(error: unknown): Error0 {
534
+ const message = this._extractMessage(error)
535
+ if (typeof error !== 'object' || error === null) {
536
+ return this._applyRefine(new this(message, { cause: error }))
474
537
  }
475
538
 
476
- if (typeof error === 'string') {
477
- return new Error0(error, inputOverride)
539
+ const errorRecord = error as Record<string, unknown>
540
+ const recreated = new this(message)
541
+ const temp = new this(message, { cause: errorRecord })
542
+ const extension = this._getResolvedExtension()
543
+ for (const [key, prop] of Object.entries(extension.props)) {
544
+ const value = prop.output(temp)
545
+ if (value !== undefined) {
546
+ ;(recreated as unknown as Record<string, unknown>)[key] = value
547
+ }
478
548
  }
549
+ ;(recreated as unknown as { cause?: unknown }).cause = errorRecord.cause
550
+ if (typeof errorRecord.stack === 'string') {
551
+ recreated.stack = errorRecord.stack
552
+ }
553
+ return this._applyRefine(recreated)
554
+ }
479
555
 
480
- if (typeof error !== 'object' || error === null) {
481
- return new Error0({
482
- message: this.defaultMessage,
483
- ...inputOverride,
556
+ private static _fromNonError0(error: unknown): Error0 {
557
+ const message = this._extractMessage(error)
558
+ return this._applyRefine(new this(message, { cause: error }))
559
+ }
560
+
561
+ private static _extractMessage(error: unknown): string {
562
+ return (
563
+ (typeof error === 'string'
564
+ ? error
565
+ : typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string'
566
+ ? error.message
567
+ : undefined) || 'Unknown error'
568
+ )
569
+ }
570
+
571
+ private static _extendWithExtension(
572
+ this: typeof Error0,
573
+ extension: ErrorExtension<ErrorExtensionProps, ErrorExtensionComputed, ErrorExtensionMethods>,
574
+ ): ClassError0 {
575
+ const Base = this as unknown as typeof Error0
576
+ const Error0Extended = class Error0 extends Base {}
577
+ ;(Error0Extended as typeof Error0)._extensions = [...Base._extensions, extension]
578
+
579
+ const resolved = (Error0Extended as typeof Error0)._getResolvedExtension()
580
+ for (const [key, method] of Object.entries(resolved.methods)) {
581
+ Object.defineProperty((Error0Extended as typeof Error0).prototype, key, {
582
+ value: function (...args: unknown[]) {
583
+ return method(this as Error0, ...args)
584
+ },
585
+ writable: true,
586
+ enumerable: true,
587
+ configurable: true,
588
+ })
589
+ Object.defineProperty(Error0Extended, key, {
590
+ value: function (error: unknown, ...args: unknown[]) {
591
+ return method(this.from(error), ...args)
592
+ },
593
+ writable: true,
594
+ enumerable: true,
595
+ configurable: true,
484
596
  })
485
597
  }
486
598
 
487
- // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
488
- const inputFromData = get(error, 'data', undefined)
489
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
490
- if (inputFromData) {
491
- if (Error0.isLikelyError0(inputFromData)) {
492
- return this._toError0(inputFromData, inputOverride)
493
- }
599
+ return Error0Extended as unknown as ClassError0
600
+ }
601
+
602
+ private static _extensionFromBuilder(
603
+ extension: ExtensionError0,
604
+ ): ErrorExtension<ErrorExtensionProps, ErrorExtensionComputed, ErrorExtensionMethods> {
605
+ const extensionRecord = extension as unknown as {
606
+ _extension: ErrorExtension<ErrorExtensionProps, ErrorExtensionComputed, ErrorExtensionMethods>
607
+ }
608
+ return {
609
+ props: { ...(extensionRecord._extension.props ?? {}) },
610
+ computed: { ...(extensionRecord._extension.computed ?? {}) },
611
+ methods: { ...(extensionRecord._extension.methods ?? {}) },
612
+ refine: [...(extensionRecord._extension.refine ?? [])],
494
613
  }
614
+ }
495
615
 
496
- // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
497
- const inputFromDataError0 = get(error, 'data.error0', undefined)
498
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
499
- if (inputFromDataError0) {
500
- if (Error0.isLikelyError0(inputFromDataError0)) {
501
- return this._toError0(inputFromDataError0, inputOverride)
616
+ static extend<TThis extends typeof Error0, TBuilder extends ExtensionError0>(
617
+ this: TThis,
618
+ extension: TBuilder,
619
+ ): ClassError0<ExtendErrorExtensionsMap<ExtensionsMapOf<TThis>, ExtensionOfBuilder<TBuilder>>>
620
+ static extend<TThis extends typeof Error0, TKey extends string, TInputValue, TOutputValue>(
621
+ this: TThis,
622
+ kind: 'prop',
623
+ key: TKey,
624
+ value: ErrorExtensionPropOptions<TInputValue, TOutputValue, ErrorInstanceOfMap<ExtensionsMapOf<TThis>>>,
625
+ ): ClassError0<ExtendErrorExtensionsMapWithProp<ExtensionsMapOf<TThis>, TKey, TInputValue, TOutputValue>>
626
+ static extend<TThis extends typeof Error0, TKey extends string, TOutputValue>(
627
+ this: TThis,
628
+ kind: 'computed',
629
+ key: TKey,
630
+ value: ErrorExtensionCopmputedFn<TOutputValue, ErrorInstanceOfMap<ExtensionsMapOf<TThis>>>,
631
+ ): ClassError0<ExtendErrorExtensionsMapWithComputed<ExtensionsMapOf<TThis>, TKey, TOutputValue>>
632
+ static extend<TThis extends typeof Error0, TKey extends string, TArgs extends unknown[], TOutputValue>(
633
+ this: TThis,
634
+ kind: 'method',
635
+ key: TKey,
636
+ value: ErrorExtensionMethodFn<TOutputValue, TArgs, ErrorInstanceOfMap<ExtensionsMapOf<TThis>>>,
637
+ ): ClassError0<ExtendErrorExtensionsMapWithMethod<ExtensionsMapOf<TThis>, TKey, TArgs, TOutputValue>>
638
+ static extend<TThis extends typeof Error0>(
639
+ this: TThis,
640
+ kind: 'refine',
641
+ value: ErrorExtensionRefineFn<ErrorInstanceOfMap<ExtensionsMapOf<TThis>>, ErrorOutputProps<ExtensionsMapOf<TThis>>>,
642
+ ): ClassError0<ExtensionsMapOf<TThis>>
643
+ static extend(
644
+ this: typeof Error0,
645
+ first: ExtensionError0 | 'prop' | 'computed' | 'method' | 'refine',
646
+ key?: string | ErrorExtensionRefineFn<any, any>,
647
+ value?:
648
+ | ErrorExtensionPropOptions<unknown, unknown>
649
+ | ErrorExtensionCopmputedFn<unknown>
650
+ | ErrorExtensionMethodFn<unknown>,
651
+ ): ClassError0 {
652
+ if (first instanceof ExtensionError0) {
653
+ return this._extendWithExtension(this._extensionFromBuilder(first))
654
+ }
655
+ if (first === 'refine') {
656
+ if (typeof key !== 'function') {
657
+ throw new Error('Error0.extend("refine", value) requires refine function')
502
658
  }
659
+ return this._extendWithExtension({
660
+ refine: [key],
661
+ })
662
+ }
663
+ if (typeof key !== 'string' || value === undefined) {
664
+ throw new Error('Error0.extend(kind, key, value) requires key and value')
503
665
  }
504
666
 
505
- return new Error0(this._getPropsFromUnknown(error, inputOverride))
506
- }
507
-
508
- static from(error: unknown, inputOverride?: Error0.Input): Error0 {
509
- return this._toError0(error, inputOverride)
510
- }
511
-
512
- static extend(props: {
513
- defaultMessage?: Error0.GeneralProps['message']
514
- defaultCode?: Error0.GeneralProps['code']
515
- defaultHttpStatus?: Error0.GeneralProps['httpStatus']
516
- defaultExpected?: Error0.GeneralProps['expected']
517
- defaultClientMessage?: Error0.GeneralProps['clientMessage']
518
- defaultMeta?: Meta0.Meta0OrValueTypeNullish
519
- }) {
520
- // eslint-disable-next-line consistent-this, @typescript-eslint/no-this-alias
521
- const parent = this
522
- return class Error0 extends parent {
523
- static override defaultMessage = props.defaultMessage ?? parent.defaultMessage
524
- static override defaultCode = props.defaultCode ?? parent.defaultCode
525
- static override defaultHttpStatus = props.defaultHttpStatus ?? parent.defaultHttpStatus
526
- static override defaultExpected = props.defaultExpected ?? parent.defaultExpected
527
- static override defaultClientMessage = props.defaultClientMessage ?? parent.defaultClientMessage
528
- static override defaultMeta = Meta0.extend(parent.defaultMeta, props.defaultMeta)
667
+ if (first === 'prop') {
668
+ return this._extendWithExtension({
669
+ props: { [key]: value as ErrorExtensionPropOptions<unknown, unknown> },
670
+ })
671
+ }
672
+ if (first === 'computed') {
673
+ return this._extendWithExtension({
674
+ computed: { [key]: value as ErrorExtensionCopmputedFn<unknown> },
675
+ })
529
676
  }
677
+ return this._extendWithExtension({
678
+ methods: { [key]: value as ErrorExtensionMethodFn<unknown> },
679
+ })
530
680
  }
531
681
 
532
- static extendCollection<T extends Record<string, typeof Error0>>(
533
- classes: T,
534
- props: {
535
- defaultMessage?: Error0.GeneralProps['message']
536
- defaultCode?: Error0.GeneralProps['code']
537
- defaultHttpStatus?: Error0.GeneralProps['httpStatus']
538
- defaultExpected?: Error0.GeneralProps['expected']
539
- defaultClientMessage?: Error0.GeneralProps['clientMessage']
540
- defaultMeta?: Meta0.Meta0OrValueTypeNullish
541
- },
542
- ): T {
543
- return Object.fromEntries(Object.entries(classes).map(([name, Class]) => [name, Class.extend(props)])) as T
682
+ static extension(): ExtensionError0 {
683
+ return new ExtensionError0()
544
684
  }
545
685
 
546
- toJSON() {
547
- return {
548
- message: this.message,
549
- tag: this.tag,
550
- code: this.code,
551
- httpStatus: this.httpStatus,
552
- expected: this.expected,
553
- clientMessage: this.clientMessage,
554
- anyMessage: this.anyMessage,
555
- cause: this.cause,
556
- meta: Meta0.getValue(this.meta),
557
- stack: this.stack,
558
- __I_AM_ERROR_0: this.__I_AM_ERROR_0,
686
+ static serialize(error: unknown, isPublic = true): object {
687
+ const error0 = this.from(error)
688
+ const jsonWithUndefined: Record<string, unknown> = {
689
+ name: error0.name,
690
+ message: error0.message,
691
+ cause: error0.cause,
692
+ stack: error0.stack,
559
693
  }
694
+
695
+ const extension = this._getResolvedExtension()
696
+ for (const [key, prop] of Object.entries(extension.props)) {
697
+ const value = prop.output(error0)
698
+ const jsonValue = prop.serialize(value, isPublic)
699
+ if (jsonValue !== undefined) {
700
+ jsonWithUndefined[key] = jsonValue
701
+ } else {
702
+ try {
703
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
704
+ delete jsonWithUndefined[key]
705
+ } catch {
706
+ // ignore
707
+ }
708
+ }
709
+ }
710
+ return Object.fromEntries(Object.entries(jsonWithUndefined).filter(([, value]) => value !== undefined)) as object
560
711
  }
561
- static toJSON(error: unknown, inputOverride?: Error0.Input) {
562
- const error0 = this.from(error, inputOverride)
563
- return error0.toJSON()
564
- }
565
-
566
- toResponse(data?: Record<string, unknown>) {
567
- return Response.json(
568
- {
569
- ...this.toJSON(),
570
- ...data,
571
- },
572
- {
573
- status: this.httpStatus,
574
- statusText: this.message,
575
- },
576
- )
577
- }
578
- }
579
712
 
580
- export namespace Error0 {
581
- export interface Input {
582
- message?: string
583
- tag?: string
584
- code?: string
585
- httpStatus?: HttpStatusCode | HttpStatusCodeString
586
- expected?: boolean | ExpectedFn
587
- clientMessage?: string
588
- cause?: Error0Cause
589
- stack?: string
590
- meta?: Meta0.Meta0OrValueTypeNullish
591
- zodError?: ZodError
592
- axiosError?: AxiosError
593
- }
594
-
595
- export interface GeneralProps {
596
- message: Input['message']
597
- tag: Input['tag']
598
- code: Input['code']
599
- httpStatus: number | undefined
600
- expected: boolean | undefined
601
- clientMessage: Input['clientMessage']
602
- anyMessage: string | undefined
603
- cause: Input['cause']
604
- stack: Error['stack']
605
- meta: Meta0.ValueType
606
- zodError?: ZodError
607
- axiosError?: AxiosError
608
- }
609
-
610
- export type HttpStatusCodeString = keyof typeof HttpStatusCode
611
- export type Error0Cause = unknown
612
- export type ExpectedFn = (error: GeneralProps) => boolean | undefined
613
- export type JSON = ReturnType<Error0['toJSON']>
614
- export type Collection = Record<string, typeof Error0>
713
+ serialize(isPublic = true): object {
714
+ const ctor = this.constructor as typeof Error0
715
+ return ctor.serialize(this, isPublic)
716
+ }
615
717
  }