@devp0nt/error0 1.0.0-next.47 → 1.0.0-next.49

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.
Files changed (57) hide show
  1. package/dist/cjs/index.cjs +165 -85
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +47 -16
  4. package/dist/cjs/plugins/cause-serialize.cjs +4 -1
  5. package/dist/cjs/plugins/cause-serialize.cjs.map +1 -1
  6. package/dist/cjs/plugins/cause-serialize.d.cts +3 -1
  7. package/dist/cjs/plugins/expected.cjs +7 -2
  8. package/dist/cjs/plugins/expected.cjs.map +1 -1
  9. package/dist/cjs/plugins/expected.d.cts +3 -1
  10. package/dist/cjs/plugins/message-merge.cjs +5 -2
  11. package/dist/cjs/plugins/message-merge.cjs.map +1 -1
  12. package/dist/cjs/plugins/message-merge.d.cts +4 -1
  13. package/dist/cjs/plugins/meta.cjs +7 -2
  14. package/dist/cjs/plugins/meta.cjs.map +1 -1
  15. package/dist/cjs/plugins/meta.d.cts +3 -1
  16. package/dist/cjs/plugins/stack-merge.cjs +6 -3
  17. package/dist/cjs/plugins/stack-merge.cjs.map +1 -1
  18. package/dist/cjs/plugins/stack-merge.d.cts +4 -1
  19. package/dist/cjs/plugins/tags.cjs +7 -2
  20. package/dist/cjs/plugins/tags.cjs.map +1 -1
  21. package/dist/cjs/plugins/tags.d.cts +3 -1
  22. package/dist/esm/index.d.ts +47 -16
  23. package/dist/esm/index.js +165 -85
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/plugins/cause-serialize.d.ts +3 -1
  26. package/dist/esm/plugins/cause-serialize.js +4 -1
  27. package/dist/esm/plugins/cause-serialize.js.map +1 -1
  28. package/dist/esm/plugins/expected.d.ts +3 -1
  29. package/dist/esm/plugins/expected.js +7 -2
  30. package/dist/esm/plugins/expected.js.map +1 -1
  31. package/dist/esm/plugins/message-merge.d.ts +4 -1
  32. package/dist/esm/plugins/message-merge.js +5 -2
  33. package/dist/esm/plugins/message-merge.js.map +1 -1
  34. package/dist/esm/plugins/meta.d.ts +3 -1
  35. package/dist/esm/plugins/meta.js +7 -2
  36. package/dist/esm/plugins/meta.js.map +1 -1
  37. package/dist/esm/plugins/stack-merge.d.ts +4 -1
  38. package/dist/esm/plugins/stack-merge.js +6 -3
  39. package/dist/esm/plugins/stack-merge.js.map +1 -1
  40. package/dist/esm/plugins/tags.d.ts +3 -1
  41. package/dist/esm/plugins/tags.js +7 -2
  42. package/dist/esm/plugins/tags.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/index.test.ts +96 -23
  45. package/src/index.ts +229 -101
  46. package/src/plugins/cause-serialize.test.ts +6 -4
  47. package/src/plugins/cause-serialize.ts +13 -9
  48. package/src/plugins/expected.test.ts +4 -4
  49. package/src/plugins/expected.ts +16 -10
  50. package/src/plugins/message-merge.test.ts +3 -3
  51. package/src/plugins/message-merge.ts +17 -13
  52. package/src/plugins/meta.test.ts +2 -2
  53. package/src/plugins/meta.ts +28 -22
  54. package/src/plugins/stack-merge.test.ts +4 -4
  55. package/src/plugins/stack-merge.ts +18 -14
  56. package/src/plugins/tags.test.ts +2 -2
  57. package/src/plugins/tags.ts +24 -18
package/src/index.test.ts CHANGED
@@ -24,7 +24,7 @@ describe('Error0', () => {
24
24
  .prop('status', {
25
25
  init: (input: number) => input,
26
26
  resolve: ({ flow }) => flow.find(Boolean),
27
- serialize: ({ value }) => value,
27
+ serialize: ({ resolved }) => resolved,
28
28
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
29
29
  })
30
30
  .method('isStatus', (error, status: number) => error.status === status)
@@ -34,7 +34,7 @@ describe('Error0', () => {
34
34
  const codePlugin = Error0.plugin().use('prop', 'code', {
35
35
  init: (input: Code) => input,
36
36
  resolve: ({ flow }) => flow.find(Boolean),
37
- serialize: ({ value, isPublic }) => (isPublic ? undefined : value),
37
+ serialize: ({ resolved, isPublic }) => (isPublic ? undefined : resolved),
38
38
  deserialize: ({ value }) =>
39
39
  typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
40
40
  })
@@ -56,7 +56,7 @@ describe('Error0', () => {
56
56
  const AppError = Error0.use('prop', 'status', {
57
57
  init: (input: number) => input,
58
58
  resolve: ({ flow }) => flow.find(Boolean),
59
- serialize: ({ value }) => value,
59
+ serialize: ({ resolved }) => resolved,
60
60
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
61
61
  })
62
62
  const error = new AppError('test', { status: 400 })
@@ -79,7 +79,7 @@ describe('Error0', () => {
79
79
  resolve: ({ own, flow }) => {
80
80
  return typeof own === 'number' ? own : undefined
81
81
  },
82
- serialize: ({ value }) => value,
82
+ serialize: ({ resolved }) => resolved,
83
83
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
84
84
  })
85
85
  .use('method', 'isStatus', (error, expectedStatus: number) => error.status === expectedStatus)
@@ -152,6 +152,19 @@ describe('Error0', () => {
152
152
  expect(Error0.causes(error2)).toEqual([error2, error1, anotherError])
153
153
  })
154
154
 
155
+ it('can limit causes depth via MAX_CAUSES_DEPTH on class', () => {
156
+ const AppError = Error0.use(statusPlugin)
157
+ const base = new AppError('base', { status: 400 })
158
+ const level1 = new AppError('level1', { status: 401, cause: base })
159
+ const level2 = new AppError('level2', { status: 402, cause: level1 })
160
+
161
+ AppError.MAX_CAUSES_DEPTH = 2
162
+ expect(AppError.causes(level2)).toEqual([level2, level1])
163
+
164
+ AppError.MAX_CAUSES_DEPTH = 999
165
+ expect(AppError.causes(level2)).toEqual([level2, level1, base])
166
+ })
167
+
155
168
  it('properties floating', () => {
156
169
  const AppError = Error0.use(statusPlugin).use(codePlugin)
157
170
  const anotherError = new Error('another error')
@@ -164,6 +177,19 @@ describe('Error0', () => {
164
177
  expect(Error0.causes(error2)).toEqual([error2, error1, anotherError])
165
178
  })
166
179
 
180
+ it('property getter return resolved value, not own value', () => {
181
+ const AppError = Error0.use('prop', 'status', {
182
+ init: (input: number) => input,
183
+ resolve: () => 500,
184
+ serialize: ({ resolved }) => resolved,
185
+ deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
186
+ })
187
+ const error = new AppError('another error', { status: 400 })
188
+ expect(error.status).toBe(500)
189
+ expect(error.own('status')).toBe(400)
190
+ expect(error.flow('status')).toEqual([400])
191
+ })
192
+
167
193
  it('serialize uses identity by default and skips undefined plugin values', () => {
168
194
  const AppError = Error0.use(statusPlugin).use('prop', 'code', {
169
195
  init: (input: string) => input,
@@ -219,7 +245,7 @@ describe('Error0', () => {
219
245
  Error0.use('prop', 'stack', {
220
246
  init: (input: string) => input,
221
247
  resolve: ({ own }) => (typeof own === 'string' ? own : undefined),
222
- serialize: ({ value }) => value,
248
+ serialize: ({ resolved }) => resolved,
223
249
  deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
224
250
  }),
225
251
  ).toThrow('reserved prop key')
@@ -230,7 +256,7 @@ describe('Error0', () => {
230
256
  Error0.plugin().prop('stack', {
231
257
  init: (input: string) => input,
232
258
  resolve: ({ own }) => (typeof own === 'string' ? own : undefined),
233
- serialize: ({ value }) => value,
259
+ serialize: ({ resolved }) => resolved,
234
260
  deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
235
261
  }),
236
262
  ).toThrow('reserved prop key')
@@ -240,7 +266,7 @@ describe('Error0', () => {
240
266
  expect(() =>
241
267
  Error0.use('prop', 'message', {
242
268
  resolve: ({ own }) => own as string,
243
- serialize: ({ value }) => value,
269
+ serialize: ({ resolved }) => resolved,
244
270
  deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
245
271
  }),
246
272
  ).toThrow('reserved prop key')
@@ -250,7 +276,7 @@ describe('Error0', () => {
250
276
  expect(() =>
251
277
  Error0.plugin().prop('message', {
252
278
  resolve: ({ own }) => own as string,
253
- serialize: ({ value }) => value,
279
+ serialize: ({ resolved }) => resolved,
254
280
  deserialize: ({ value }) => (typeof value === 'string' ? value : undefined),
255
281
  }),
256
282
  ).toThrow('reserved prop key')
@@ -267,6 +293,24 @@ describe('Error0', () => {
267
293
  expect(AppError.serialize(recreated, false)).toEqual(json)
268
294
  })
269
295
 
296
+ it('.round() static and instance do serialize/from roundtrip', () => {
297
+ const AppError = Error0.use(statusPlugin).use(codePlugin)
298
+ const error = new AppError('test', { status: 409, code: 'NOT_FOUND' })
299
+ const roundedStatic = AppError.round(error, false)
300
+ const roundedInstance = error.round(false)
301
+
302
+ expect(roundedStatic).toBeInstanceOf(AppError)
303
+ expect(roundedInstance).toBeInstanceOf(AppError)
304
+ expect(roundedStatic.status).toBe(409)
305
+ expect(roundedStatic.code).toBe('NOT_FOUND')
306
+ expect(roundedInstance.status).toBe(409)
307
+ expect(roundedInstance.code).toBe('NOT_FOUND')
308
+ expectTypeOf(roundedStatic.status).toEqualTypeOf<number | undefined>()
309
+ expectTypeOf(roundedStatic.code).toEqualTypeOf<'NOT_FOUND' | 'BAD_REQUEST' | 'UNAUTHORIZED' | undefined>()
310
+ expectTypeOf(roundedInstance.status).toEqualTypeOf<number | undefined>()
311
+ expectTypeOf(roundedInstance.code).toEqualTypeOf<'NOT_FOUND' | 'BAD_REQUEST' | 'UNAUTHORIZED' | undefined>()
312
+ })
313
+
270
314
  it('.serialize() floated props and not serialize causes', () => {
271
315
  const AppError = Error0.use(statusPlugin).use(codePlugin)
272
316
  const error1 = new AppError('test', { status: 409 })
@@ -277,7 +321,6 @@ describe('Error0', () => {
277
321
  expect('cause' in json).toBe(false)
278
322
  })
279
323
 
280
-
281
324
  it('by default causes not serialized', () => {
282
325
  const AppError = Error0.use(statusPlugin).use(codePlugin)
283
326
  const error = new AppError('test', { status: 400, code: 'NOT_FOUND' })
@@ -298,7 +341,7 @@ describe('Error0', () => {
298
341
  const AppError = Error0.use('prop', 'computed', {
299
342
  init: () => undefined as number | undefined,
300
343
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
301
- serialize: ({ value }) => value,
344
+ serialize: ({ resolved }) => resolved,
302
345
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
303
346
  })
304
347
 
@@ -314,7 +357,7 @@ describe('Error0', () => {
314
357
  it('prop without init omits constructor input and infers resolve output', () => {
315
358
  const AppError = Error0.use('prop', 'statusCode', {
316
359
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
317
- serialize: ({ value }) => value,
360
+ serialize: ({ resolved }) => resolved,
318
361
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
319
362
  })
320
363
 
@@ -331,7 +374,7 @@ describe('Error0', () => {
331
374
  const AppError = Error0.use('prop', 'x', {
332
375
  init: (input: number) => input,
333
376
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') || 500,
334
- serialize: ({ value }) => value,
377
+ serialize: ({ resolved }) => resolved,
335
378
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
336
379
  })
337
380
 
@@ -345,7 +388,7 @@ describe('Error0', () => {
345
388
  init: (input: number) => input,
346
389
  // @ts-expect-error - resolve type extends init type
347
390
  resolve: ({ flow }) => 'string',
348
- serialize: ({ value }) => value,
391
+ serialize: ({ resolved }) => resolved,
349
392
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
350
393
  })
351
394
  })
@@ -354,7 +397,7 @@ describe('Error0', () => {
354
397
  const AppError = Error0.use('prop', 'code', {
355
398
  init: (input: number | 'fallback') => input,
356
399
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') ?? 500,
357
- serialize: ({ value }) => value,
400
+ serialize: ({ resolved }) => resolved,
358
401
  deserialize: ({ value }) => (typeof value === 'number' || value === 'fallback' ? value : undefined),
359
402
  })
360
403
  const error = new AppError('test')
@@ -375,12 +418,12 @@ describe('Error0', () => {
375
418
  const AppError = Error0.use('prop', 'status', {
376
419
  init: (input: number) => input,
377
420
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
378
- serialize: ({ value }) => value,
421
+ serialize: ({ resolved }) => resolved,
379
422
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
380
423
  }).use('prop', 'code', {
381
424
  init: (input: Code) => input,
382
425
  resolve: ({ flow }) => flow.find(isCode),
383
- serialize: ({ value }) => value,
426
+ serialize: ({ resolved }) => resolved,
384
427
  deserialize: ({ value }) => (value === 'A' || value === 'B' ? value : undefined),
385
428
  })
386
429
 
@@ -402,12 +445,12 @@ describe('Error0', () => {
402
445
  const AppError = Error0.use('prop', 'status', {
403
446
  init: (input: number) => input,
404
447
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
405
- serialize: ({ value }) => value,
448
+ serialize: ({ resolved }) => resolved,
406
449
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
407
450
  }).use('prop', 'code', {
408
451
  init: (input: Code) => input,
409
452
  resolve: ({ flow }) => flow.find(isCode),
410
- serialize: ({ value }) => value,
453
+ serialize: ({ resolved }) => resolved,
411
454
  deserialize: ({ value }) => (value === 'A' || value === 'B' ? value : undefined),
412
455
  })
413
456
 
@@ -429,13 +472,13 @@ describe('Error0', () => {
429
472
  const AppError = Error0.use('prop', 'status', {
430
473
  init: (input: number) => input,
431
474
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') ?? 500,
432
- serialize: ({ value }) => value,
475
+ serialize: ({ resolved }) => resolved,
433
476
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
434
477
  })
435
478
  .use('prop', 'code', {
436
479
  init: (input: Code) => input,
437
480
  resolve: ({ flow }) => flow.find(isCode),
438
- serialize: ({ value }) => value,
481
+ serialize: ({ resolved }) => resolved,
439
482
  deserialize: ({ value }) => (value === 'A' || value === 'B' ? value : undefined),
440
483
  })
441
484
  .use('method', 'isStatus', (error, status: number) => error.status === status)
@@ -456,7 +499,7 @@ describe('Error0', () => {
456
499
  it('prop resolved type can be not undefined with init not provided', () => {
457
500
  const AppError = Error0.use('prop', 'x', {
458
501
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') || 500,
459
- serialize: ({ value }) => value,
502
+ serialize: ({ resolved }) => resolved,
460
503
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
461
504
  })
462
505
 
@@ -468,7 +511,7 @@ describe('Error0', () => {
468
511
  init: (input: number) => input,
469
512
  // @ts-expect-error - resolve type extends init type
470
513
  resolve: ({ flow }) => 'string',
471
- serialize: ({ value }) => value,
514
+ serialize: ({ resolved }) => resolved,
472
515
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
473
516
  })
474
517
  })
@@ -554,7 +597,6 @@ describe('Error0', () => {
554
597
  expect(error1.code).toBe(undefined)
555
598
  })
556
599
 
557
-
558
600
  it('messages can be combined on serialization', () => {
559
601
  const AppError = Error0.use(statusPlugin)
560
602
  .use(codePlugin)
@@ -609,6 +651,37 @@ describe('Error0', () => {
609
651
  `)
610
652
  })
611
653
 
654
+ it('stress: resolve/serialize/flow stays within perf budget', () => {
655
+ const AppError = Error0.use(statusPlugin).use(codePlugin)
656
+
657
+ let current: InstanceType<typeof AppError> = new AppError('root', {
658
+ status: 500,
659
+ code: 'BAD_REQUEST',
660
+ })
661
+ for (let i = 0; i < 300; i += 1) {
662
+ current = new AppError(`level-${i}`, {
663
+ status: 500,
664
+ code: i % 2 === 0 ? 'NOT_FOUND' : 'BAD_REQUEST',
665
+ cause: current,
666
+ })
667
+ }
668
+
669
+ let checksum = 0
670
+ const startedAt = performance.now()
671
+ for (let i = 0; i < 3000; i += 1) {
672
+ const resolved = AppError.resolve(current)
673
+ const serialized = AppError.serialize(current, false)
674
+ const flow = current.flow('status')
675
+ checksum += resolved.status ?? 0
676
+ checksum += (serialized.status as number | undefined) ?? 0
677
+ checksum += flow.length
678
+ }
679
+ const elapsedMs = performance.now() - startedAt
680
+ const budgetMs = process.env.CI ? 8000 : 4000
681
+
682
+ expect(checksum).toBeGreaterThan(0)
683
+ expect(elapsedMs).toBeLessThan(budgetMs)
684
+ })
612
685
 
613
686
  it('Error0 assignable to LikeError0', () => {
614
687
  type LikeError0 = {