@devp0nt/error0 1.0.0-next.4 → 1.0.0-next.41

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