@devp0nt/error0 1.0.0-next.40 → 1.0.0-next.42
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 +131 -164
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +130 -125
- package/dist/esm/index.d.ts +130 -125
- package/dist/esm/index.js +130 -163
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.test.ts +130 -118
- package/src/index.ts +344 -398
package/src/index.test.ts
CHANGED
|
@@ -20,37 +20,23 @@ const fixStack = (stack: string | undefined) => {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
describe('Error0', () => {
|
|
23
|
-
const
|
|
23
|
+
const statusPlugin = Error0.plugin()
|
|
24
24
|
.prop('status', {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!Number.isNaN(status)) {
|
|
30
|
-
return status
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return undefined
|
|
34
|
-
},
|
|
35
|
-
serialize: (value) => value,
|
|
25
|
+
init: (input: number) => input,
|
|
26
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
27
|
+
serialize: ({ value }) => value,
|
|
28
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
36
29
|
})
|
|
37
30
|
.method('isStatus', (error, status: number) => error.status === status)
|
|
38
31
|
|
|
39
32
|
const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
|
|
40
33
|
type Code = (typeof codes)[number]
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return value as Code
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return undefined
|
|
52
|
-
},
|
|
53
|
-
serialize: (value, error, isPublic) => (isPublic ? undefined : value),
|
|
34
|
+
const codePlugin = Error0.plugin().use('prop', 'code', {
|
|
35
|
+
init: (input: Code) => input,
|
|
36
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
37
|
+
serialize: ({ value, isPublic }) => (isPublic ? undefined : value),
|
|
38
|
+
deserialize: ({ value }) =>
|
|
39
|
+
typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
|
|
54
40
|
})
|
|
55
41
|
|
|
56
42
|
it('simple', () => {
|
|
@@ -66,19 +52,12 @@ describe('Error0', () => {
|
|
|
66
52
|
`)
|
|
67
53
|
})
|
|
68
54
|
|
|
69
|
-
it('with direct prop
|
|
70
|
-
const AppError = Error0.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!Number.isNaN(status)) {
|
|
76
|
-
return status
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return undefined
|
|
80
|
-
},
|
|
81
|
-
serialize: (value: number | undefined) => value,
|
|
55
|
+
it('with direct prop plugin', () => {
|
|
56
|
+
const AppError = Error0.use('prop', 'status', {
|
|
57
|
+
init: (input: number) => input,
|
|
58
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
59
|
+
serialize: ({ value }) => value,
|
|
60
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
82
61
|
})
|
|
83
62
|
const error = new AppError('test', { status: 400 })
|
|
84
63
|
expect(error).toBeInstanceOf(AppError)
|
|
@@ -94,8 +73,32 @@ describe('Error0', () => {
|
|
|
94
73
|
expectTypeOf<typeof AppError>().toExtend<ClassError0>()
|
|
95
74
|
})
|
|
96
75
|
|
|
97
|
-
it('
|
|
98
|
-
const AppError = Error0.
|
|
76
|
+
it('class helpers prop/method/adapt mirror use API', () => {
|
|
77
|
+
const AppError = Error0.prop('status', {
|
|
78
|
+
init: (value: number) => value,
|
|
79
|
+
resolve: ({ value, flow }) => {
|
|
80
|
+
return typeof value === 'number' ? value : undefined
|
|
81
|
+
},
|
|
82
|
+
serialize: ({ value }) => value,
|
|
83
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
84
|
+
})
|
|
85
|
+
.method('isStatus', (error, expectedStatus: number) => error.status === expectedStatus)
|
|
86
|
+
.adapt((error) => {
|
|
87
|
+
if (error.cause instanceof Error && error.status === undefined) {
|
|
88
|
+
return { status: 500 }
|
|
89
|
+
}
|
|
90
|
+
return undefined
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const error = AppError.from(new Error('inner'))
|
|
94
|
+
expect(error.status).toBe(500)
|
|
95
|
+
expect(error.isStatus(500)).toBe(true)
|
|
96
|
+
expect(AppError.isStatus(error, 500)).toBe(true)
|
|
97
|
+
expectTypeOf<typeof AppError>().toExtend<ClassError0>()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('with defined plugin', () => {
|
|
101
|
+
const AppError = Error0.use(statusPlugin)
|
|
99
102
|
const error = new AppError('test', { status: 400 })
|
|
100
103
|
expect(error).toBeInstanceOf(AppError)
|
|
101
104
|
expect(error).toBeInstanceOf(Error0)
|
|
@@ -109,9 +112,9 @@ describe('Error0', () => {
|
|
|
109
112
|
`)
|
|
110
113
|
})
|
|
111
114
|
|
|
112
|
-
it('twice
|
|
113
|
-
const AppError1 = Error0.
|
|
114
|
-
const AppError2 = AppError1.
|
|
115
|
+
it('twice used Error0 extends previous by types', () => {
|
|
116
|
+
const AppError1 = Error0.use(statusPlugin)
|
|
117
|
+
const AppError2 = AppError1.use(codePlugin)
|
|
115
118
|
const error1 = new AppError1('test', { status: 400 })
|
|
116
119
|
const error2 = new AppError2('test', { status: 400, code: 'NOT_FOUND' })
|
|
117
120
|
expect(error1.status).toBe(400)
|
|
@@ -126,7 +129,7 @@ describe('Error0', () => {
|
|
|
126
129
|
})
|
|
127
130
|
|
|
128
131
|
it('can have cause', () => {
|
|
129
|
-
const AppError = Error0.
|
|
132
|
+
const AppError = Error0.use(statusPlugin)
|
|
130
133
|
const anotherError = new Error('another error')
|
|
131
134
|
const error = new AppError('test', { status: 400, cause: anotherError })
|
|
132
135
|
expect(error.status).toBe(400)
|
|
@@ -140,7 +143,7 @@ describe('Error0', () => {
|
|
|
140
143
|
})
|
|
141
144
|
|
|
142
145
|
it('can have many causes', () => {
|
|
143
|
-
const AppError = Error0.
|
|
146
|
+
const AppError = Error0.use(statusPlugin)
|
|
144
147
|
const anotherError = new Error('another error')
|
|
145
148
|
const error1 = new AppError('test1', { status: 400, cause: anotherError })
|
|
146
149
|
const error2 = new AppError('test2', { status: 400, cause: error1 })
|
|
@@ -150,7 +153,7 @@ describe('Error0', () => {
|
|
|
150
153
|
})
|
|
151
154
|
|
|
152
155
|
it('properties floating', () => {
|
|
153
|
-
const AppError = Error0.
|
|
156
|
+
const AppError = Error0.use(statusPlugin).use(codePlugin)
|
|
154
157
|
const anotherError = new Error('another error')
|
|
155
158
|
const error1 = new AppError('test1', { status: 400, cause: anotherError })
|
|
156
159
|
const error2 = new AppError('test2', { code: 'NOT_FOUND', cause: error1 })
|
|
@@ -161,51 +164,40 @@ describe('Error0', () => {
|
|
|
161
164
|
expect(Error0.causes(error2)).toEqual([error2, error1, anotherError])
|
|
162
165
|
})
|
|
163
166
|
|
|
164
|
-
it('serialize uses identity by default and skips undefined
|
|
165
|
-
const AppError = Error0.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for (const value of error.flow('code')) {
|
|
169
|
-
if (typeof value === 'string') {
|
|
170
|
-
return value
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return undefined
|
|
174
|
-
},
|
|
167
|
+
it('serialize uses identity by default and skips undefined plugin values', () => {
|
|
168
|
+
const AppError = Error0.use(statusPlugin).prop('code', {
|
|
169
|
+
init: (input: string) => input,
|
|
170
|
+
resolve: ({ flow }) => flow.find(Boolean),
|
|
175
171
|
serialize: () => undefined,
|
|
172
|
+
deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
|
|
176
173
|
})
|
|
177
174
|
const error = new AppError('test', { status: 401, code: 'secret' })
|
|
178
|
-
const json = AppError.serialize(error)
|
|
175
|
+
const json = AppError.serialize(error)
|
|
179
176
|
expect(json.status).toBe(401)
|
|
180
177
|
expect('code' in json).toBe(false)
|
|
181
178
|
})
|
|
182
179
|
|
|
183
|
-
it('serialize keeps stack by default without stack
|
|
184
|
-
const AppError = Error0.
|
|
180
|
+
it('serialize keeps stack by default without stack plugin', () => {
|
|
181
|
+
const AppError = Error0.use(statusPlugin)
|
|
185
182
|
const error = new AppError('test', { status: 500 })
|
|
186
|
-
const json = AppError.serialize(error)
|
|
183
|
+
const json = AppError.serialize(error)
|
|
187
184
|
expect(json.stack).toBe(error.stack)
|
|
188
185
|
})
|
|
189
186
|
|
|
190
|
-
it('stack
|
|
191
|
-
const AppError = Error0.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return stack
|
|
197
|
-
}
|
|
198
|
-
return undefined
|
|
199
|
-
},
|
|
200
|
-
serialize: () => undefined,
|
|
187
|
+
it('stack plugin can customize serialization of stack prop', () => {
|
|
188
|
+
const AppError = Error0.prop('stack', {
|
|
189
|
+
init: (input: string) => input,
|
|
190
|
+
resolve: ({ value }) => (typeof value === 'string' ? value : undefined),
|
|
191
|
+
serialize: ({ value }) => undefined,
|
|
192
|
+
deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
|
|
201
193
|
})
|
|
202
194
|
const error = new AppError('test')
|
|
203
|
-
const json = AppError.serialize(error)
|
|
195
|
+
const json = AppError.serialize(error)
|
|
204
196
|
expect('stack' in json).toBe(false)
|
|
205
197
|
})
|
|
206
198
|
|
|
207
|
-
it('.serialize() -> .from() roundtrip keeps
|
|
208
|
-
const AppError = Error0.
|
|
199
|
+
it('.serialize() -> .from() roundtrip keeps plugin values', () => {
|
|
200
|
+
const AppError = Error0.use(statusPlugin).use(codePlugin)
|
|
209
201
|
const error = new AppError('test', { status: 409, code: 'NOT_FOUND' })
|
|
210
202
|
const json = AppError.serialize(error, false)
|
|
211
203
|
const recreated = AppError.from(json)
|
|
@@ -215,31 +207,57 @@ describe('Error0', () => {
|
|
|
215
207
|
expect(AppError.serialize(recreated, false)).toEqual(json)
|
|
216
208
|
})
|
|
217
209
|
|
|
218
|
-
it('
|
|
219
|
-
const AppError = Error0.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const error = new AppError('test', { status: 400, code: 'NOT_FOUND' })
|
|
227
|
-
expect(error.summary).toBe('test:NOT_FOUND')
|
|
228
|
-
expect(error.hasCode('NOT_FOUND')).toBe(true)
|
|
229
|
-
expect(AppError.hasCode(error, 'NOT_FOUND')).toBe(true)
|
|
230
|
-
expect(AppError.hasCode('just string', 'NOT_FOUND')).toBe(false)
|
|
231
|
-
expect('summary' in AppError.serialize(error)).toBe(false)
|
|
210
|
+
it('.serialize() floated props and not serialize causes', () => {
|
|
211
|
+
const AppError = Error0.use(statusPlugin).use(codePlugin)
|
|
212
|
+
const error1 = new AppError('test', { status: 409 })
|
|
213
|
+
const error2 = new AppError('test', { code: 'NOT_FOUND', cause: error1 })
|
|
214
|
+
const json = AppError.serialize(error2, false)
|
|
215
|
+
expect(json.status).toBe(409)
|
|
216
|
+
expect(json.code).toBe('NOT_FOUND')
|
|
217
|
+
expect('cause' in json).toBe(false)
|
|
232
218
|
})
|
|
233
219
|
|
|
234
220
|
it('serialize can hide props for public output', () => {
|
|
235
|
-
const AppError = Error0.
|
|
221
|
+
const AppError = Error0.use(statusPlugin).use(codePlugin)
|
|
236
222
|
const error = new AppError('test', { status: 401, code: 'NOT_FOUND' })
|
|
237
|
-
const privateJson = AppError.serialize(error, false)
|
|
238
|
-
const publicJson = AppError.serialize(error, true)
|
|
223
|
+
const privateJson = AppError.serialize(error, false)
|
|
224
|
+
const publicJson = AppError.serialize(error, true)
|
|
239
225
|
expect(privateJson.code).toBe('NOT_FOUND')
|
|
240
226
|
expect('code' in publicJson).toBe(false)
|
|
241
227
|
})
|
|
242
228
|
|
|
229
|
+
it('prop init without input arg infers undefined-only constructor input', () => {
|
|
230
|
+
const AppError = Error0.prop('computed', {
|
|
231
|
+
init: () => undefined as number | undefined,
|
|
232
|
+
resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
|
|
233
|
+
serialize: ({ value }) => value,
|
|
234
|
+
deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const error = new AppError('test')
|
|
238
|
+
expect(error.computed).toBe(undefined)
|
|
239
|
+
expectTypeOf<typeof error.computed>().toEqualTypeOf<number | undefined>()
|
|
240
|
+
|
|
241
|
+
// @ts-expect-error - computed input is disallowed when init has no input arg
|
|
242
|
+
// eslint-disable-next-line no-new
|
|
243
|
+
new AppError('test', { computed: 123 })
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('serialize/deserialize can be set to false to disable them', () => {
|
|
247
|
+
const AppError = Error0.prop('status', {
|
|
248
|
+
init: (input: number) => input,
|
|
249
|
+
resolve: ({ value, flow }) => value ?? flow.find((item) => typeof item === 'number'),
|
|
250
|
+
serialize: false,
|
|
251
|
+
deserialize: false,
|
|
252
|
+
})
|
|
253
|
+
const error = new AppError('test', { status: 401 })
|
|
254
|
+
const json = AppError.serialize(error)
|
|
255
|
+
expect('status' in json).toBe(false)
|
|
256
|
+
|
|
257
|
+
const recreated = AppError.from({ ...json, status: 999 })
|
|
258
|
+
expect(recreated.status).toBe(undefined)
|
|
259
|
+
})
|
|
260
|
+
|
|
243
261
|
it('by default error0 created from another error has same message', () => {
|
|
244
262
|
const schema = z.object({
|
|
245
263
|
x: z.string(),
|
|
@@ -251,16 +269,16 @@ describe('Error0', () => {
|
|
|
251
269
|
expect(error.message).toBe(parsedError.message)
|
|
252
270
|
})
|
|
253
271
|
|
|
254
|
-
it('
|
|
272
|
+
it('adapt message and other props via direct transformations', () => {
|
|
255
273
|
const schema = z.object({
|
|
256
274
|
x: z.string(),
|
|
257
275
|
})
|
|
258
276
|
const parseResult = schema.safeParse({ x: 123 })
|
|
259
277
|
const parsedError = parseResult.error
|
|
260
278
|
assert.ok(parsedError)
|
|
261
|
-
const AppError = Error0.
|
|
262
|
-
.
|
|
263
|
-
.
|
|
279
|
+
const AppError = Error0.use(statusPlugin)
|
|
280
|
+
.use(codePlugin)
|
|
281
|
+
.use('adapt', (error) => {
|
|
264
282
|
if (error.cause instanceof ZodError) {
|
|
265
283
|
error.status = 422
|
|
266
284
|
error.code = 'NOT_FOUND'
|
|
@@ -277,16 +295,16 @@ describe('Error0', () => {
|
|
|
277
295
|
expect(error1.code).toBe(undefined)
|
|
278
296
|
})
|
|
279
297
|
|
|
280
|
-
it('
|
|
298
|
+
it('adapt message and other props via return output values from plugin', () => {
|
|
281
299
|
const schema = z.object({
|
|
282
300
|
x: z.string(),
|
|
283
301
|
})
|
|
284
302
|
const parseResult = schema.safeParse({ x: 123 })
|
|
285
303
|
const parsedError = parseResult.error
|
|
286
304
|
assert.ok(parsedError)
|
|
287
|
-
const AppError = Error0.
|
|
288
|
-
.
|
|
289
|
-
.
|
|
305
|
+
const AppError = Error0.use(statusPlugin)
|
|
306
|
+
.use(codePlugin)
|
|
307
|
+
.adapt((error) => {
|
|
290
308
|
if (error.cause instanceof ZodError) {
|
|
291
309
|
error.message = `Validation Error: ${error.message}`
|
|
292
310
|
return {
|
|
@@ -307,21 +325,15 @@ describe('Error0', () => {
|
|
|
307
325
|
})
|
|
308
326
|
|
|
309
327
|
it('expected prop can be realized to send or not to send error to your error tracker', () => {
|
|
310
|
-
const AppError = Error0.
|
|
311
|
-
.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return value
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return undefined
|
|
320
|
-
},
|
|
321
|
-
serialize: (value) => value,
|
|
328
|
+
const AppError = Error0.use(statusPlugin)
|
|
329
|
+
.prop('expected', {
|
|
330
|
+
init: (input: boolean) => input,
|
|
331
|
+
resolve: ({ flow }) => flow.find((value) => typeof value === 'boolean'),
|
|
332
|
+
serialize: ({ value }) => value,
|
|
333
|
+
deserialize: ({ value }) => (typeof value === 'boolean' ? value : undefined),
|
|
322
334
|
})
|
|
323
|
-
.
|
|
324
|
-
return error.expected
|
|
335
|
+
.method('isExpected', (error) => {
|
|
336
|
+
return error.expected ?? false
|
|
325
337
|
})
|
|
326
338
|
const errorExpected = new AppError('test', { status: 400, expected: true })
|
|
327
339
|
const errorUnexpected = new AppError('test', { status: 400, expected: false })
|
|
@@ -342,13 +354,13 @@ describe('Error0', () => {
|
|
|
342
354
|
|
|
343
355
|
// we will have no variants
|
|
344
356
|
// becouse you can thorw any errorm and when you do AppError.from(yourError)
|
|
345
|
-
// can use
|
|
357
|
+
// can use adapt to assign desired props to error, it is enough for transport
|
|
346
358
|
// you even can create computed or method to retrieve your error, so no problems with variants
|
|
347
359
|
|
|
348
360
|
// it('can create and recongnize variant', () => {
|
|
349
|
-
// const AppError = Error0.
|
|
350
|
-
// .
|
|
351
|
-
// .
|
|
361
|
+
// const AppError = Error0.use(statusPlugin)
|
|
362
|
+
// .use(codePlugin)
|
|
363
|
+
// .use('prop', 'userId', {
|
|
352
364
|
// input: (value: string) => value,
|
|
353
365
|
// output: (error) => {
|
|
354
366
|
// for (const value of error.flow('userId')) {
|