@devp0nt/error0 1.0.0-next.49 → 1.0.0-next.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +5 -17
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +64 -21
- package/dist/cjs/plugins/cause-serialize.cjs +1 -1
- package/dist/cjs/plugins/cause-serialize.cjs.map +1 -1
- package/dist/cjs/plugins/code.cjs +38 -0
- package/dist/cjs/plugins/code.cjs.map +1 -0
- package/dist/cjs/plugins/code.d.cts +7 -0
- package/dist/cjs/plugins/expected.cjs +2 -2
- package/dist/cjs/plugins/expected.cjs.map +1 -1
- package/dist/cjs/plugins/expected.d.cts +31 -2
- package/dist/cjs/plugins/meta.cjs +1 -1
- package/dist/cjs/plugins/meta.cjs.map +1 -1
- package/dist/cjs/plugins/stack-merge.cjs +1 -1
- package/dist/cjs/plugins/stack-merge.cjs.map +1 -1
- package/dist/cjs/plugins/status.cjs +54 -0
- package/dist/cjs/plugins/status.cjs.map +1 -0
- package/dist/cjs/plugins/status.d.cts +8 -0
- package/dist/cjs/plugins/tags.cjs +41 -21
- package/dist/cjs/plugins/tags.cjs.map +1 -1
- package/dist/cjs/plugins/tags.d.cts +7 -2
- package/dist/esm/index.d.ts +64 -21
- package/dist/esm/index.js +5 -17
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/plugins/cause-serialize.js +1 -1
- package/dist/esm/plugins/cause-serialize.js.map +1 -1
- package/dist/esm/plugins/code.d.ts +7 -0
- package/dist/esm/plugins/code.js +14 -0
- package/dist/esm/plugins/code.js.map +1 -0
- package/dist/esm/plugins/expected.d.ts +31 -2
- package/dist/esm/plugins/expected.js +2 -2
- package/dist/esm/plugins/expected.js.map +1 -1
- package/dist/esm/plugins/meta.js +1 -1
- package/dist/esm/plugins/meta.js.map +1 -1
- package/dist/esm/plugins/stack-merge.js +1 -1
- package/dist/esm/plugins/stack-merge.js.map +1 -1
- package/dist/esm/plugins/status.d.ts +8 -0
- package/dist/esm/plugins/status.js +30 -0
- package/dist/esm/plugins/status.js.map +1 -0
- package/dist/esm/plugins/tags.d.ts +7 -2
- package/dist/esm/plugins/tags.js +41 -21
- package/dist/esm/plugins/tags.js.map +1 -1
- package/package.json +1 -1
- package/src/index.test.ts +53 -7
- package/src/index.ts +107 -67
- package/src/plugins/cause-serialize.ts +1 -1
- package/src/plugins/code.test.ts +27 -0
- package/src/plugins/code.ts +12 -0
- package/src/plugins/expected.ts +2 -2
- package/src/plugins/meta.ts +1 -1
- package/src/plugins/stack-merge.ts +1 -1
- package/src/plugins/status.test.ts +54 -0
- package/src/plugins/status.ts +29 -0
- package/src/plugins/tags.test.ts +54 -2
- package/src/plugins/tags.ts +48 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/plugins/tags.ts"],"sourcesContent":["import { Error0 } from '../index.js'\n\nexport const tagsPlugin = ({
|
|
1
|
+
{"version":3,"sources":["../../../src/plugins/tags.ts"],"sourcesContent":["import { Error0 } from '../index.js'\n\nexport const tagsPlugin = <TTag extends string>({\n hideWhenPublic = true,\n tags,\n strict = true,\n}: { hideWhenPublic?: boolean; tags?: TTag[] | readonly TTag[]; strict?: boolean } = {}) => {\n function hasTag(error: Error0, tag: TTag): boolean\n function hasTag(error: Error0, tag: TTag[], policy: 'every' | 'some'): boolean\n function hasTag(error: Error0, tag: TTag | TTag[], policy?: 'every' | 'some'): boolean {\n const tags = (error as any).tags as string[] | undefined\n if (!tags) {\n return false\n }\n if (Array.isArray(tag)) {\n if (policy === 'every') {\n return tag.every((item) => tags.includes(item))\n }\n return tag.some((item) => tags.includes(item))\n }\n return tags.includes(tag)\n }\n const isTag = (value: unknown): value is TTag =>\n typeof value === 'string' && (!tags || !strict || tags.includes(value as TTag))\n return Error0.plugin()\n .prop('tags', {\n init: (input: string[]) => input,\n resolve: ({ flow }) => {\n const merged: string[] = []\n for (const value of flow) {\n if (Array.isArray(value)) {\n merged.push(...value)\n }\n }\n return merged.length > 0 ? Array.from(new Set(merged)) : undefined\n },\n serialize: ({ resolved, isPublic }) => {\n if (hideWhenPublic && isPublic) {\n return undefined\n }\n return resolved\n },\n deserialize: ({ value }) => {\n if (!Array.isArray(value)) {\n return undefined\n }\n return value.filter((item) => isTag(item))\n },\n })\n .method('hasTag', hasTag)\n}\n"],"mappings":"AAAA,SAAS,cAAc;AAEhB,MAAM,aAAa,CAAsB;AAAA,EAC9C,iBAAiB;AAAA,EACjB;AAAA,EACA,SAAS;AACX,IAAqF,CAAC,MAAM;AAG1F,WAAS,OAAO,OAAe,KAAoB,QAAoC;AACrF,UAAMA,QAAQ,MAAc;AAC5B,QAAI,CAACA,OAAM;AACT,aAAO;AAAA,IACT;AACA,QAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,UAAI,WAAW,SAAS;AACtB,eAAO,IAAI,MAAM,CAAC,SAASA,MAAK,SAAS,IAAI,CAAC;AAAA,MAChD;AACA,aAAO,IAAI,KAAK,CAAC,SAASA,MAAK,SAAS,IAAI,CAAC;AAAA,IAC/C;AACA,WAAOA,MAAK,SAAS,GAAG;AAAA,EAC1B;AACA,QAAM,QAAQ,CAAC,UACb,OAAO,UAAU,aAAa,CAAC,QAAQ,CAAC,UAAU,KAAK,SAAS,KAAa;AAC/E,SAAO,OAAO,OAAO,EAClB,KAAK,QAAQ;AAAA,IACZ,MAAM,CAAC,UAAoB;AAAA,IAC3B,SAAS,CAAC,EAAE,KAAK,MAAM;AACrB,YAAM,SAAmB,CAAC;AAC1B,iBAAW,SAAS,MAAM;AACxB,YAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAO,KAAK,GAAG,KAAK;AAAA,QACtB;AAAA,MACF;AACA,aAAO,OAAO,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI;AAAA,IAC3D;AAAA,IACA,WAAW,CAAC,EAAE,UAAU,SAAS,MAAM;AACrC,UAAI,kBAAkB,UAAU;AAC9B,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,IACA,aAAa,CAAC,EAAE,MAAM,MAAM;AAC1B,UAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,eAAO;AAAA,MACT;AACA,aAAO,MAAM,OAAO,CAAC,SAAS,MAAM,IAAI,CAAC;AAAA,IAC3C;AAAA,EACF,CAAC,EACA,OAAO,UAAU,MAAM;AAC5B;","names":["tags"]}
|
package/package.json
CHANGED
package/src/index.test.ts
CHANGED
|
@@ -683,15 +683,61 @@ describe('Error0', () => {
|
|
|
683
683
|
expect(elapsedMs).toBeLessThan(budgetMs)
|
|
684
684
|
})
|
|
685
685
|
|
|
686
|
+
it('class can be created extended from Error0', () => {
|
|
687
|
+
class MyError extends Error0.use(statusPlugin).use(codePlugin) {
|
|
688
|
+
isStatusAndCode(status: number, code: string): boolean {
|
|
689
|
+
return this.status === status && this.code === code
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const error = new MyError('test', { status: 400, code: 'NOT_FOUND' })
|
|
693
|
+
expect(error.isStatusAndCode(400, 'NOT_FOUND')).toBe(true)
|
|
694
|
+
expect(error.isStatusAndCode(400, 'BAD_REQUEST')).toBe(false)
|
|
695
|
+
expect(error.name).toBe('Error0')
|
|
696
|
+
expectTypeOf<typeof MyError>().toExtend<ClassError0>()
|
|
697
|
+
})
|
|
698
|
+
|
|
686
699
|
it('Error0 assignable to LikeError0', () => {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
700
|
+
class MyError extends Error {
|
|
701
|
+
status?: number
|
|
702
|
+
code?: string
|
|
703
|
+
constructor(message: string, options: { cause?: unknown; status?: number; code?: string }) {
|
|
704
|
+
super(message, { cause: options.cause })
|
|
705
|
+
this.status = options.status
|
|
706
|
+
this.code = options.code
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
static from(error: unknown): MyError {
|
|
710
|
+
if (error instanceof MyError) {
|
|
711
|
+
return error
|
|
712
|
+
}
|
|
713
|
+
const object = typeof error === 'object' && error !== null ? (error as Record<string, unknown>) : {}
|
|
714
|
+
const message =
|
|
715
|
+
typeof object.message === 'string' ? object.message : typeof error === 'string' ? error : 'Unknown error'
|
|
716
|
+
const status = typeof object.status === 'number' ? object.status : undefined
|
|
717
|
+
const code = typeof object.code === 'string' ? object.code : undefined
|
|
718
|
+
return new MyError(message, {
|
|
719
|
+
cause: error,
|
|
720
|
+
status,
|
|
721
|
+
code,
|
|
722
|
+
})
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
static serialize(error: MyError): Record<string, unknown> {
|
|
726
|
+
return {
|
|
727
|
+
message: error.message,
|
|
728
|
+
status: error.status,
|
|
729
|
+
code: error.code,
|
|
730
|
+
}
|
|
731
|
+
}
|
|
690
732
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
733
|
+
|
|
734
|
+
type LikeError0<TError> = {
|
|
735
|
+
new (message: string, options: { cause?: unknown; status?: number; code?: string }): TError
|
|
736
|
+
from: (error: unknown) => TError
|
|
737
|
+
serialize: (error: TError) => Record<string, unknown>
|
|
738
|
+
}
|
|
739
|
+
expectTypeOf<typeof MyError>().toExtend<LikeError0<MyError>>()
|
|
740
|
+
expectTypeOf<typeof Error0>().toExtend<typeof MyError>()
|
|
695
741
|
})
|
|
696
742
|
|
|
697
743
|
// we will have no variants
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export type ErrorPluginMethodFn<TOutputValue, TArgs extends unknown[] = unknown[
|
|
|
66
66
|
error: TError,
|
|
67
67
|
...args: TArgs
|
|
68
68
|
) => TOutputValue
|
|
69
|
+
type ErrorPluginAnyMethodFn = (error: any, ...args: any[]) => any
|
|
69
70
|
export type ErrorPluginAdaptResult<TOutputProps extends Record<string, unknown>> = Partial<TOutputProps> | undefined
|
|
70
71
|
export type ErrorPluginAdaptFn<
|
|
71
72
|
TError extends Error0 = Error0,
|
|
@@ -89,13 +90,10 @@ export type ErrorPluginMessageSerialize<TError extends Error0> = (options: {
|
|
|
89
90
|
isPublic: boolean
|
|
90
91
|
}) => unknown
|
|
91
92
|
export type ErrorPluginMessage<TError extends Error0 = Error0> = { serialize: ErrorPluginMessageSerialize<TError> }
|
|
92
|
-
type ErrorMethodRecord = {
|
|
93
|
-
args: unknown[]
|
|
94
|
-
output: unknown
|
|
95
|
-
}
|
|
93
|
+
type ErrorMethodRecord = { fn: ErrorPluginAnyMethodFn }
|
|
96
94
|
|
|
97
95
|
export type ErrorPluginProps = { [key: string]: ErrorPluginPropOptions<any, any> }
|
|
98
|
-
export type ErrorPluginMethods = { [key: string]:
|
|
96
|
+
export type ErrorPluginMethods = { [key: string]: ErrorPluginAnyMethodFn }
|
|
99
97
|
|
|
100
98
|
export type ErrorPlugin<
|
|
101
99
|
TProps extends ErrorPluginProps = Record<never, never>,
|
|
@@ -118,9 +116,8 @@ type AddPropToPluginProps<
|
|
|
118
116
|
type AddMethodToPluginMethods<
|
|
119
117
|
TMethods extends ErrorPluginMethods,
|
|
120
118
|
TKey extends string,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
> = TMethods & Record<TKey, ErrorPluginMethodFn<TOutputValue, TArgs>>
|
|
119
|
+
TMethod extends ErrorPluginAnyMethodFn,
|
|
120
|
+
> = TMethods & Record<TKey, TMethod>
|
|
124
121
|
type PluginOutputProps<TProps extends ErrorPluginProps> = {
|
|
125
122
|
[TKey in keyof TProps]: TProps[TKey] extends ErrorPluginPropOptions<any, any, any, infer TResolveValue>
|
|
126
123
|
? TResolveValue
|
|
@@ -160,24 +157,80 @@ type ErrorOwnMethods<TPluginsMap extends ErrorPluginsMap> = {
|
|
|
160
157
|
type ErrorResolveMethods<TPluginsMap extends ErrorPluginsMap> = {
|
|
161
158
|
resolve: () => ErrorResolvedProps<TPluginsMap>
|
|
162
159
|
}
|
|
160
|
+
type BindInstanceMethod<TMethod> = TMethod extends {
|
|
161
|
+
(error: any, ...args: infer TArgs1): infer TOutput1
|
|
162
|
+
(error: any, ...args: infer TArgs2): infer TOutput2
|
|
163
|
+
(error: any, ...args: infer TArgs3): infer TOutput3
|
|
164
|
+
(error: any, ...args: infer TArgs4): infer TOutput4
|
|
165
|
+
}
|
|
166
|
+
? {
|
|
167
|
+
(...args: TArgs1): TOutput1
|
|
168
|
+
(...args: TArgs2): TOutput2
|
|
169
|
+
(...args: TArgs3): TOutput3
|
|
170
|
+
(...args: TArgs4): TOutput4
|
|
171
|
+
}
|
|
172
|
+
: TMethod extends {
|
|
173
|
+
(error: any, ...args: infer TArgs1): infer TOutput1
|
|
174
|
+
(error: any, ...args: infer TArgs2): infer TOutput2
|
|
175
|
+
(error: any, ...args: infer TArgs3): infer TOutput3
|
|
176
|
+
}
|
|
177
|
+
? {
|
|
178
|
+
(...args: TArgs1): TOutput1
|
|
179
|
+
(...args: TArgs2): TOutput2
|
|
180
|
+
(...args: TArgs3): TOutput3
|
|
181
|
+
}
|
|
182
|
+
: TMethod extends {
|
|
183
|
+
(error: any, ...args: infer TArgs1): infer TOutput1
|
|
184
|
+
(error: any, ...args: infer TArgs2): infer TOutput2
|
|
185
|
+
}
|
|
186
|
+
? {
|
|
187
|
+
(...args: TArgs1): TOutput1
|
|
188
|
+
(...args: TArgs2): TOutput2
|
|
189
|
+
}
|
|
190
|
+
: TMethod extends (error: any, ...args: infer TArgs) => infer TOutput
|
|
191
|
+
? (...args: TArgs) => TOutput
|
|
192
|
+
: never
|
|
193
|
+
type BindStaticMethod<TMethod> = TMethod extends {
|
|
194
|
+
(error: any, ...args: infer TArgs1): infer TOutput1
|
|
195
|
+
(error: any, ...args: infer TArgs2): infer TOutput2
|
|
196
|
+
(error: any, ...args: infer TArgs3): infer TOutput3
|
|
197
|
+
(error: any, ...args: infer TArgs4): infer TOutput4
|
|
198
|
+
}
|
|
199
|
+
? {
|
|
200
|
+
(error: unknown, ...args: TArgs1): TOutput1
|
|
201
|
+
(error: unknown, ...args: TArgs2): TOutput2
|
|
202
|
+
(error: unknown, ...args: TArgs3): TOutput3
|
|
203
|
+
(error: unknown, ...args: TArgs4): TOutput4
|
|
204
|
+
}
|
|
205
|
+
: TMethod extends {
|
|
206
|
+
(error: any, ...args: infer TArgs1): infer TOutput1
|
|
207
|
+
(error: any, ...args: infer TArgs2): infer TOutput2
|
|
208
|
+
(error: any, ...args: infer TArgs3): infer TOutput3
|
|
209
|
+
}
|
|
210
|
+
? {
|
|
211
|
+
(error: unknown, ...args: TArgs1): TOutput1
|
|
212
|
+
(error: unknown, ...args: TArgs2): TOutput2
|
|
213
|
+
(error: unknown, ...args: TArgs3): TOutput3
|
|
214
|
+
}
|
|
215
|
+
: TMethod extends {
|
|
216
|
+
(error: any, ...args: infer TArgs1): infer TOutput1
|
|
217
|
+
(error: any, ...args: infer TArgs2): infer TOutput2
|
|
218
|
+
}
|
|
219
|
+
? {
|
|
220
|
+
(error: unknown, ...args: TArgs1): TOutput1
|
|
221
|
+
(error: unknown, ...args: TArgs2): TOutput2
|
|
222
|
+
}
|
|
223
|
+
: TMethod extends (error: any, ...args: infer TArgs) => infer TOutput
|
|
224
|
+
? (error: unknown, ...args: TArgs) => TOutput
|
|
225
|
+
: never
|
|
163
226
|
type ErrorMethods<TPluginsMap extends ErrorPluginsMap> = {
|
|
164
|
-
[TKey in keyof TPluginsMap['methods']]: TPluginsMap['methods'][TKey]
|
|
165
|
-
args: infer TArgs extends unknown[]
|
|
166
|
-
output: infer TOutput
|
|
167
|
-
}
|
|
168
|
-
? (...args: TArgs) => TOutput
|
|
169
|
-
: never
|
|
227
|
+
[TKey in keyof TPluginsMap['methods']]: BindInstanceMethod<TPluginsMap['methods'][TKey]['fn']>
|
|
170
228
|
}
|
|
171
229
|
export type ErrorResolved<TPluginsMap extends ErrorPluginsMap> = ErrorResolvedProps<TPluginsMap> &
|
|
172
230
|
ErrorMethods<TPluginsMap>
|
|
173
231
|
|
|
174
232
|
type ErrorStaticMethods<TPluginsMap extends ErrorPluginsMap> = {
|
|
175
|
-
[TKey in keyof TPluginsMap['methods']]: TPluginsMap['methods'][TKey]
|
|
176
|
-
args: infer TArgs extends unknown[]
|
|
177
|
-
output: infer TOutput
|
|
178
|
-
}
|
|
179
|
-
? (error: unknown, ...args: TArgs) => TOutput
|
|
180
|
-
: never
|
|
233
|
+
[TKey in keyof TPluginsMap['methods']]: BindStaticMethod<TPluginsMap['methods'][TKey]['fn']>
|
|
181
234
|
}
|
|
182
235
|
|
|
183
236
|
type EmptyPluginsMap = {
|
|
@@ -214,12 +267,11 @@ type PluginPropsMapOf<TPlugin extends ErrorPlugin> = {
|
|
|
214
267
|
: never
|
|
215
268
|
}
|
|
216
269
|
type PluginMethodsMapOf<TPlugin extends ErrorPlugin> = {
|
|
217
|
-
[TKey in keyof NonNullable<TPlugin['methods']>]:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
: never
|
|
270
|
+
[TKey in keyof NonNullable<TPlugin['methods']>]: {
|
|
271
|
+
fn: NonNullable<TPlugin['methods']>[TKey] extends ErrorPluginAnyMethodFn
|
|
272
|
+
? NonNullable<TPlugin['methods']>[TKey]
|
|
273
|
+
: never
|
|
274
|
+
}
|
|
223
275
|
}
|
|
224
276
|
type ErrorPluginsMapOfPlugin<TPlugin extends ErrorPlugin> = {
|
|
225
277
|
props: PluginPropsMapOf<TPlugin>
|
|
@@ -242,12 +294,8 @@ type ExtendErrorPluginsMapWithProp<
|
|
|
242
294
|
type ExtendErrorPluginsMapWithMethod<
|
|
243
295
|
TMap extends ErrorPluginsMap,
|
|
244
296
|
TKey extends string,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
> = ExtendErrorPluginsMap<
|
|
248
|
-
TMap,
|
|
249
|
-
ErrorPlugin<Record<never, never>, Record<TKey, ErrorPluginMethodFn<TOutputValue, TArgs>>>
|
|
250
|
-
>
|
|
297
|
+
TMethod extends ErrorPluginAnyMethodFn,
|
|
298
|
+
> = ExtendErrorPluginsMap<TMap, ErrorPlugin<Record<never, never>, Record<TKey, TMethod>>>
|
|
251
299
|
|
|
252
300
|
type PluginsMapOf<TClass> = TClass extends { __pluginsMap?: infer TPluginsMap }
|
|
253
301
|
? TPluginsMap extends ErrorPluginsMap
|
|
@@ -312,10 +360,10 @@ export class PluginError0<
|
|
|
312
360
|
return this.use('prop', key, value)
|
|
313
361
|
}
|
|
314
362
|
|
|
315
|
-
method<TKey extends string,
|
|
363
|
+
method<TKey extends string, TMethod extends (error: BuilderError0<TProps, TMethods>, ...args: any[]) => any>(
|
|
316
364
|
key: TKey,
|
|
317
|
-
value:
|
|
318
|
-
): PluginError0<TProps, AddMethodToPluginMethods<TMethods, TKey,
|
|
365
|
+
value: TMethod,
|
|
366
|
+
): PluginError0<TProps, AddMethodToPluginMethods<TMethods, TKey, TMethod>> {
|
|
319
367
|
return this.use('method', key, value)
|
|
320
368
|
}
|
|
321
369
|
|
|
@@ -347,11 +395,11 @@ export class PluginError0<
|
|
|
347
395
|
key: TKey,
|
|
348
396
|
value: ErrorPluginPropOptions<TInputValue, TOutputValue, BuilderError0<TProps, TMethods>, TResolveValue>,
|
|
349
397
|
): PluginError0<AddPropToPluginProps<TProps, TKey, TInputValue, TOutputValue, TResolveValue>, TMethods>
|
|
350
|
-
use<TKey extends string,
|
|
398
|
+
use<TKey extends string, TMethod extends (error: BuilderError0<TProps, TMethods>, ...args: any[]) => any>(
|
|
351
399
|
kind: 'method',
|
|
352
400
|
key: TKey,
|
|
353
|
-
value:
|
|
354
|
-
): PluginError0<TProps, AddMethodToPluginMethods<TMethods, TKey,
|
|
401
|
+
value: TMethod,
|
|
402
|
+
): PluginError0<TProps, AddMethodToPluginMethods<TMethods, TKey, TMethod>>
|
|
355
403
|
use(
|
|
356
404
|
kind: 'adapt',
|
|
357
405
|
value: ErrorPluginAdaptFn<BuilderError0<TProps, TMethods>, PluginOutputProps<TProps>>,
|
|
@@ -460,10 +508,10 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
|
|
|
460
508
|
// key: TKey,
|
|
461
509
|
// value: ErrorPluginPropOptions<TInputValue, TOutputValue, ErrorInstanceOfMap<TPluginsMap>, TResolveValue>,
|
|
462
510
|
// ) => ClassError0<ExtendErrorPluginsMapWithProp<TPluginsMap, TKey, TInputValue, TOutputValue, TResolveValue>>
|
|
463
|
-
// method: <TKey extends string,
|
|
511
|
+
// method: <TKey extends string, TMethod extends (error: ErrorInstanceOfMap<TPluginsMap>, ...args: any[]) => any>(
|
|
464
512
|
// key: TKey,
|
|
465
|
-
// value:
|
|
466
|
-
// ) => ClassError0<ExtendErrorPluginsMapWithMethod<TPluginsMap, TKey,
|
|
513
|
+
// value: TMethod,
|
|
514
|
+
// ) => ClassError0<ExtendErrorPluginsMapWithMethod<TPluginsMap, TKey, TMethod>>
|
|
467
515
|
// adapt: (
|
|
468
516
|
// value: ErrorPluginAdaptFn<ErrorInstanceOfMap<TPluginsMap>, ErrorResolvedProps<TPluginsMap>>,
|
|
469
517
|
// ) => ClassError0<TPluginsMap>
|
|
@@ -483,11 +531,11 @@ export type ClassError0<TPluginsMap extends ErrorPluginsMap = EmptyPluginsMap> =
|
|
|
483
531
|
key: TKey,
|
|
484
532
|
value: ErrorPluginPropOptions<TInputValue, TOutputValue, ErrorInstanceOfMap<TPluginsMap>, TResolveValue>,
|
|
485
533
|
): ClassError0<ExtendErrorPluginsMapWithProp<TPluginsMap, TKey, TInputValue, TOutputValue, TResolveValue>>
|
|
486
|
-
<TKey extends string,
|
|
534
|
+
<TKey extends string, TMethod extends (error: ErrorInstanceOfMap<TPluginsMap>, ...args: any[]) => any>(
|
|
487
535
|
kind: 'method',
|
|
488
536
|
key: TKey,
|
|
489
|
-
value:
|
|
490
|
-
): ClassError0<ExtendErrorPluginsMapWithMethod<TPluginsMap, TKey,
|
|
537
|
+
value: TMethod,
|
|
538
|
+
): ClassError0<ExtendErrorPluginsMapWithMethod<TPluginsMap, TKey, TMethod>>
|
|
491
539
|
(
|
|
492
540
|
kind: 'adapt',
|
|
493
541
|
value: ErrorPluginAdaptFn<ErrorInstanceOfMap<TPluginsMap>, ErrorResolvedProps<TPluginsMap>>,
|
|
@@ -648,29 +696,17 @@ export class Error0 extends Error {
|
|
|
648
696
|
|
|
649
697
|
private static readonly isOwnProperty = (object: object, key: string): boolean => {
|
|
650
698
|
const ownStore = this._getOwnStore(object)
|
|
651
|
-
if (ownStore
|
|
652
|
-
return
|
|
699
|
+
if (ownStore) {
|
|
700
|
+
return Object.prototype.hasOwnProperty.call(ownStore, key)
|
|
653
701
|
}
|
|
654
|
-
|
|
655
|
-
if (!d) return false
|
|
656
|
-
if (typeof d.get === 'function' || typeof d.set === 'function') {
|
|
657
|
-
if ('name' in object && object.name === 'Error0') {
|
|
658
|
-
return false
|
|
659
|
-
} else {
|
|
660
|
-
return true
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
return true
|
|
702
|
+
return !!Object.getOwnPropertyDescriptor(object, key)
|
|
664
703
|
}
|
|
665
704
|
private static _ownByKey(error: object, key: string): unknown {
|
|
666
705
|
const ownStore = this._getOwnStore(error)
|
|
667
|
-
if (ownStore
|
|
706
|
+
if (ownStore) {
|
|
668
707
|
return ownStore[key]
|
|
669
708
|
}
|
|
670
|
-
|
|
671
|
-
return (error as Record<string, unknown>)[key]
|
|
672
|
-
}
|
|
673
|
-
return undefined
|
|
709
|
+
return (error as Record<string, unknown>)[key]
|
|
674
710
|
}
|
|
675
711
|
private static _flowByKey(error: object, key: string): unknown[] {
|
|
676
712
|
const causes = this.causes(error, true)
|
|
@@ -963,11 +999,11 @@ export class Error0 extends Error {
|
|
|
963
999
|
// return this.use('prop', key, value)
|
|
964
1000
|
// }
|
|
965
1001
|
|
|
966
|
-
// static method<TThis extends typeof Error0, TKey extends string,
|
|
1002
|
+
// static method<TThis extends typeof Error0, TKey extends string, TMethod extends (error: ErrorInstanceOfMap<PluginsMapOf<TThis>>, ...args: any[]) => any>(
|
|
967
1003
|
// this: TThis,
|
|
968
1004
|
// key: TKey,
|
|
969
|
-
// value:
|
|
970
|
-
// ): ClassError0<ExtendErrorPluginsMapWithMethod<PluginsMapOf<TThis>, TKey,
|
|
1005
|
+
// value: TMethod,
|
|
1006
|
+
// ): ClassError0<ExtendErrorPluginsMapWithMethod<PluginsMapOf<TThis>, TKey, TMethod>> {
|
|
971
1007
|
// return this.use('method', key, value)
|
|
972
1008
|
// }
|
|
973
1009
|
|
|
@@ -1008,12 +1044,16 @@ export class Error0 extends Error {
|
|
|
1008
1044
|
key: TKey,
|
|
1009
1045
|
value: ErrorPluginPropOptions<TInputValue, TOutputValue, ErrorInstanceOfMap<PluginsMapOf<TThis>>, TResolveValue>,
|
|
1010
1046
|
): ClassError0<ExtendErrorPluginsMapWithProp<PluginsMapOf<TThis>, TKey, TInputValue, TOutputValue, TResolveValue>>
|
|
1011
|
-
static use<
|
|
1047
|
+
static use<
|
|
1048
|
+
TThis extends typeof Error0,
|
|
1049
|
+
TKey extends string,
|
|
1050
|
+
TMethod extends (error: ErrorInstanceOfMap<PluginsMapOf<TThis>>, ...args: any[]) => any,
|
|
1051
|
+
>(
|
|
1012
1052
|
this: TThis,
|
|
1013
1053
|
kind: 'method',
|
|
1014
1054
|
key: TKey,
|
|
1015
|
-
value:
|
|
1016
|
-
): ClassError0<ExtendErrorPluginsMapWithMethod<PluginsMapOf<TThis>, TKey,
|
|
1055
|
+
value: TMethod,
|
|
1056
|
+
): ClassError0<ExtendErrorPluginsMapWithMethod<PluginsMapOf<TThis>, TKey, TMethod>>
|
|
1017
1057
|
static use<TThis extends typeof Error0>(
|
|
1018
1058
|
this: TThis,
|
|
1019
1059
|
kind: 'adapt',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Error0 } from '../index.js'
|
|
2
2
|
|
|
3
3
|
export const causeSerializePlugin = ({ hideWhenPublic = true }: { hideWhenPublic?: boolean } = {}) =>
|
|
4
|
-
Error0.plugin().
|
|
4
|
+
Error0.plugin().cause({
|
|
5
5
|
serialize: ({ value, error, isPublic }) => {
|
|
6
6
|
if (hideWhenPublic && isPublic) {
|
|
7
7
|
return undefined
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'bun:test'
|
|
2
|
+
import { Error0 } from '../index.js'
|
|
3
|
+
import { codePlugin } from './code.js'
|
|
4
|
+
|
|
5
|
+
describe('codePlugin', () => {
|
|
6
|
+
const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
|
|
7
|
+
|
|
8
|
+
it('serializes and deserializes allowed codes', () => {
|
|
9
|
+
const AppError = Error0.use(codePlugin({ codes: [...codes] }))
|
|
10
|
+
const error = new AppError('test', { code: 'NOT_FOUND' })
|
|
11
|
+
|
|
12
|
+
expect(error.code).toBe('NOT_FOUND')
|
|
13
|
+
expectTypeOf<typeof error.code>().toEqualTypeOf<'NOT_FOUND' | 'BAD_REQUEST' | 'UNAUTHORIZED' | undefined>()
|
|
14
|
+
|
|
15
|
+
const json = AppError.serialize(error, false)
|
|
16
|
+
expect(json.code).toBe('NOT_FOUND')
|
|
17
|
+
|
|
18
|
+
const recreated = AppError.from(json)
|
|
19
|
+
expect(recreated.code).toBe('NOT_FOUND')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('ignores code values outside the allowed list', () => {
|
|
23
|
+
const AppError = Error0.use(codePlugin({ codes: [...codes] }))
|
|
24
|
+
const recreated = AppError.from({ message: 'test', code: 'SOMETHING_ELSE' })
|
|
25
|
+
expect(recreated.code).toBeUndefined()
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Error0 } from '../index.js'
|
|
2
|
+
|
|
3
|
+
export const codePlugin = <TCode extends string>({ codes }: { codes?: TCode[] } = {}) => {
|
|
4
|
+
const isCode = (value: unknown): value is TCode =>
|
|
5
|
+
typeof value === 'string' && (!codes || codes.includes(value as TCode))
|
|
6
|
+
return Error0.plugin().prop('code', {
|
|
7
|
+
init: (code: TCode) => code,
|
|
8
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
9
|
+
serialize: ({ resolved, isPublic }) => resolved,
|
|
10
|
+
deserialize: ({ value, record }) => (isCode(value) ? value : undefined),
|
|
11
|
+
})
|
|
12
|
+
}
|
package/src/plugins/expected.ts
CHANGED
|
@@ -15,7 +15,7 @@ const isExpected = (flow: unknown[]) => {
|
|
|
15
15
|
|
|
16
16
|
export const expectedPlugin = ({ hideWhenPublic = true }: { hideWhenPublic?: boolean } = {}) =>
|
|
17
17
|
Error0.plugin()
|
|
18
|
-
.
|
|
18
|
+
.prop('expected', {
|
|
19
19
|
init: (input: boolean) => input,
|
|
20
20
|
resolve: ({ flow }) => isExpected(flow),
|
|
21
21
|
serialize: ({ resolved, isPublic }) => {
|
|
@@ -26,6 +26,6 @@ export const expectedPlugin = ({ hideWhenPublic = true }: { hideWhenPublic?: boo
|
|
|
26
26
|
},
|
|
27
27
|
deserialize: ({ value }) => (typeof value === 'boolean' ? value : undefined),
|
|
28
28
|
})
|
|
29
|
-
.
|
|
29
|
+
.method('isExpected', (error) => {
|
|
30
30
|
return isExpected(error.flow('expected'))
|
|
31
31
|
})
|
package/src/plugins/meta.ts
CHANGED
|
@@ -29,7 +29,7 @@ const isMetaRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
29
29
|
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
30
30
|
|
|
31
31
|
export const metaPlugin = ({ hideWhenPublic = true }: { hideWhenPublic?: boolean } = {}) =>
|
|
32
|
-
Error0.plugin().
|
|
32
|
+
Error0.plugin().prop('meta', {
|
|
33
33
|
init: (input: Record<string, unknown>) => input,
|
|
34
34
|
resolve: ({ flow }) => {
|
|
35
35
|
const values = flow.filter(isMetaRecord)
|
|
@@ -4,7 +4,7 @@ export const stackMergePlugin = ({
|
|
|
4
4
|
hideWhenPublic = true,
|
|
5
5
|
delimiter = '\n',
|
|
6
6
|
}: { hideWhenPublic?: boolean; delimiter?: string } = {}) =>
|
|
7
|
-
Error0.plugin().
|
|
7
|
+
Error0.plugin().stack({
|
|
8
8
|
serialize: ({ error, isPublic }) => {
|
|
9
9
|
if (hideWhenPublic && isPublic) {
|
|
10
10
|
return undefined
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from 'bun:test'
|
|
2
|
+
import { Error0 } from '../index.js'
|
|
3
|
+
import { statusPlugin } from './status.js'
|
|
4
|
+
|
|
5
|
+
describe('statusPlugin', () => {
|
|
6
|
+
const statuses = {
|
|
7
|
+
BAD_REQUEST: 400,
|
|
8
|
+
UNAUTHORIZED: 401,
|
|
9
|
+
NOT_FOUND: 404,
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
it('maps status keys to numeric values', () => {
|
|
13
|
+
const AppError = Error0.use(statusPlugin({ statuses }))
|
|
14
|
+
const error = new AppError('test', { status: 'NOT_FOUND' })
|
|
15
|
+
|
|
16
|
+
expectTypeOf<typeof error.status>().toEqualTypeOf<number | undefined>()
|
|
17
|
+
expect(error.status).toBe(404)
|
|
18
|
+
|
|
19
|
+
const json = AppError.serialize(error, false)
|
|
20
|
+
expect(json.status).toBe(404)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('accepts numeric status values when no statuses map is provided', () => {
|
|
24
|
+
const AppError = Error0.use(statusPlugin())
|
|
25
|
+
const error = new AppError('test', { status: 500 })
|
|
26
|
+
expect(error.status).toBe(500)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('if status number not in list, it is not converts to undefined by default', () => {
|
|
30
|
+
const AppError = Error0.use(statusPlugin({ statuses }))
|
|
31
|
+
const error = new AppError('test', { status: 999 })
|
|
32
|
+
expect(error.status).toBe(999)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('if status number not in list, it converts to undefined if strict is true', () => {
|
|
36
|
+
const AppError = Error0.use(statusPlugin({ statuses, strict: true }))
|
|
37
|
+
const error = new AppError('test', { status: 999 })
|
|
38
|
+
expect(error.status).toBeUndefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('not allowed incorrect status name', () => {
|
|
42
|
+
const AppError = Error0.use(statusPlugin({ statuses }))
|
|
43
|
+
// @ts-expect-error - incorrect status name
|
|
44
|
+
const error = new AppError('test', { status: 'SOMETHING_ELSE' })
|
|
45
|
+
expect(error.status).toBeUndefined()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('not allowed status name if statuses not provided', () => {
|
|
49
|
+
const AppError = Error0.use(statusPlugin())
|
|
50
|
+
// @ts-expect-error - incorrect status name
|
|
51
|
+
const error = new AppError('test', { status: 'SOMETHING_ELSE' })
|
|
52
|
+
expect(error.status).toBeUndefined()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Error0 } from '../index.js'
|
|
2
|
+
|
|
3
|
+
export const statusPlugin = <TStatuses extends Record<string, number> = Record<never, number>>({
|
|
4
|
+
statuses,
|
|
5
|
+
strict = false,
|
|
6
|
+
}: { statuses?: TStatuses; strict?: boolean } = {}) => {
|
|
7
|
+
const statusValues = statuses ? Object.values(statuses) : undefined
|
|
8
|
+
const isStatusValue = (value: unknown): value is number =>
|
|
9
|
+
typeof value === 'number' && (!statusValues || !strict || statusValues.includes(value))
|
|
10
|
+
const normalizeStatusValue = (value: unknown): number | undefined => {
|
|
11
|
+
return isStatusValue(value) ? value : undefined
|
|
12
|
+
}
|
|
13
|
+
const convertStatusValue = (value: number | string): number | undefined => {
|
|
14
|
+
if (typeof value === 'number') {
|
|
15
|
+
return normalizeStatusValue(value)
|
|
16
|
+
}
|
|
17
|
+
if (statuses && value in statuses) {
|
|
18
|
+
return statuses[value as keyof TStatuses]
|
|
19
|
+
}
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return Error0.plugin().prop('status', {
|
|
24
|
+
init: (status: number | Extract<keyof TStatuses, string>) => convertStatusValue(status),
|
|
25
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
26
|
+
serialize: ({ resolved }) => resolved,
|
|
27
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
28
|
+
})
|
|
29
|
+
}
|
package/src/plugins/tags.test.ts
CHANGED
|
@@ -3,12 +3,14 @@ import { Error0 } from '../index.js'
|
|
|
3
3
|
import { tagsPlugin } from './tags.js'
|
|
4
4
|
|
|
5
5
|
describe('tagsPlugin', () => {
|
|
6
|
+
const tags = ['api', 'db', 'retry'] as const
|
|
7
|
+
|
|
6
8
|
it('merges and deduplicates tags across causes', () => {
|
|
7
9
|
const AppError = Error0.use(tagsPlugin())
|
|
8
10
|
const root = new AppError('root', { tags: ['db', 'retry'] })
|
|
9
11
|
const leaf = new AppError('leaf', { tags: ['api', 'retry'], cause: root })
|
|
10
|
-
expect(leaf.
|
|
11
|
-
expectTypeOf(leaf.
|
|
12
|
+
expect(leaf.tags).toEqual(['api', 'retry', 'db'])
|
|
13
|
+
expectTypeOf(leaf.tags).toEqualTypeOf<string[] | undefined>()
|
|
12
14
|
expectTypeOf(leaf.own('tags')).toEqualTypeOf<string[] | undefined>()
|
|
13
15
|
expectTypeOf(leaf.flow('tags')).toEqualTypeOf<Array<string[] | undefined>>()
|
|
14
16
|
})
|
|
@@ -19,4 +21,54 @@ describe('tagsPlugin', () => {
|
|
|
19
21
|
expect(AppError.serialize(error, false).tags).toEqual(['internal'])
|
|
20
22
|
expect('tags' in AppError.serialize(error, true)).toBe(false)
|
|
21
23
|
})
|
|
24
|
+
|
|
25
|
+
it('supports hasTag for single, some, and every checks', () => {
|
|
26
|
+
const AppError = Error0.use(tagsPlugin())
|
|
27
|
+
const root = new AppError('root', { tags: ['db', 'retry'] })
|
|
28
|
+
const leaf = new AppError('leaf', { tags: ['api'], cause: root })
|
|
29
|
+
|
|
30
|
+
expect(AppError.hasTag(leaf, 'db')).toBe(true)
|
|
31
|
+
expect(leaf.hasTag('db')).toBe(true)
|
|
32
|
+
expect(leaf.hasTag('unknown')).toBe(false)
|
|
33
|
+
expect(leaf.hasTag(['api', 'db'], 'some')).toBe(true)
|
|
34
|
+
expect(leaf.hasTag(['api', 'db'], 'every')).toBe(true)
|
|
35
|
+
expect(leaf.hasTag(['api', 'unknown'], 'every')).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('filters tags not in the allow-list when strict is true', () => {
|
|
39
|
+
const AppError = Error0.use(tagsPlugin({ tags: [...tags], strict: true }))
|
|
40
|
+
const recreated = AppError.from({
|
|
41
|
+
name: 'Error0',
|
|
42
|
+
message: 'test',
|
|
43
|
+
tags: ['db', 'invalid', 123],
|
|
44
|
+
})
|
|
45
|
+
expect(recreated.tags).toEqual(['db'])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('keeps all string tags when strict is false', () => {
|
|
49
|
+
const AppError = Error0.use(tagsPlugin({ tags: [...tags], strict: false }))
|
|
50
|
+
const recreated = AppError.from({
|
|
51
|
+
name: 'Error0',
|
|
52
|
+
message: 'test',
|
|
53
|
+
tags: ['db', 'custom', 123],
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
expect(recreated.tags).toEqual(['db', 'custom'])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('enforces typed hasTag inputs when allow-list is provided', () => {
|
|
60
|
+
const AppError = Error0.use(tagsPlugin({ tags }))
|
|
61
|
+
const error = new AppError('test', { tags: ['api', 'db'] })
|
|
62
|
+
|
|
63
|
+
expectTypeOf(error.hasTag('api')).toEqualTypeOf<boolean>()
|
|
64
|
+
expectTypeOf(error.hasTag(['api', 'db'], 'every')).toEqualTypeOf<boolean>()
|
|
65
|
+
expectTypeOf(error.hasTag(['api', 'retry'], 'some')).toEqualTypeOf<boolean>()
|
|
66
|
+
|
|
67
|
+
// @ts-expect-error - unknown tag is not part of allow-list
|
|
68
|
+
error.hasTag('custom')
|
|
69
|
+
// @ts-expect-error - array checks require policy argument
|
|
70
|
+
error.hasTag(['api', 'db'])
|
|
71
|
+
// @ts-expect-error - unsupported policy
|
|
72
|
+
error.hasTag(['api', 'db'], 'all')
|
|
73
|
+
})
|
|
22
74
|
})
|