@devp0nt/error0 1.0.0-next.48 → 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 +158 -85
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +44 -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 +44 -16
  23. package/dist/esm/index.js +158 -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 +79 -21
  45. package/src/index.ts +216 -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')
@@ -315,7 +341,7 @@ describe('Error0', () => {
315
341
  const AppError = Error0.use('prop', 'computed', {
316
342
  init: () => undefined as number | undefined,
317
343
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
318
- serialize: ({ value }) => value,
344
+ serialize: ({ resolved }) => resolved,
319
345
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
320
346
  })
321
347
 
@@ -331,7 +357,7 @@ describe('Error0', () => {
331
357
  it('prop without init omits constructor input and infers resolve output', () => {
332
358
  const AppError = Error0.use('prop', 'statusCode', {
333
359
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
334
- serialize: ({ value }) => value,
360
+ serialize: ({ resolved }) => resolved,
335
361
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
336
362
  })
337
363
 
@@ -348,7 +374,7 @@ describe('Error0', () => {
348
374
  const AppError = Error0.use('prop', 'x', {
349
375
  init: (input: number) => input,
350
376
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') || 500,
351
- serialize: ({ value }) => value,
377
+ serialize: ({ resolved }) => resolved,
352
378
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
353
379
  })
354
380
 
@@ -362,7 +388,7 @@ describe('Error0', () => {
362
388
  init: (input: number) => input,
363
389
  // @ts-expect-error - resolve type extends init type
364
390
  resolve: ({ flow }) => 'string',
365
- serialize: ({ value }) => value,
391
+ serialize: ({ resolved }) => resolved,
366
392
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
367
393
  })
368
394
  })
@@ -371,7 +397,7 @@ describe('Error0', () => {
371
397
  const AppError = Error0.use('prop', 'code', {
372
398
  init: (input: number | 'fallback') => input,
373
399
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') ?? 500,
374
- serialize: ({ value }) => value,
400
+ serialize: ({ resolved }) => resolved,
375
401
  deserialize: ({ value }) => (typeof value === 'number' || value === 'fallback' ? value : undefined),
376
402
  })
377
403
  const error = new AppError('test')
@@ -392,12 +418,12 @@ describe('Error0', () => {
392
418
  const AppError = Error0.use('prop', 'status', {
393
419
  init: (input: number) => input,
394
420
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
395
- serialize: ({ value }) => value,
421
+ serialize: ({ resolved }) => resolved,
396
422
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
397
423
  }).use('prop', 'code', {
398
424
  init: (input: Code) => input,
399
425
  resolve: ({ flow }) => flow.find(isCode),
400
- serialize: ({ value }) => value,
426
+ serialize: ({ resolved }) => resolved,
401
427
  deserialize: ({ value }) => (value === 'A' || value === 'B' ? value : undefined),
402
428
  })
403
429
 
@@ -419,12 +445,12 @@ describe('Error0', () => {
419
445
  const AppError = Error0.use('prop', 'status', {
420
446
  init: (input: number) => input,
421
447
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number'),
422
- serialize: ({ value }) => value,
448
+ serialize: ({ resolved }) => resolved,
423
449
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
424
450
  }).use('prop', 'code', {
425
451
  init: (input: Code) => input,
426
452
  resolve: ({ flow }) => flow.find(isCode),
427
- serialize: ({ value }) => value,
453
+ serialize: ({ resolved }) => resolved,
428
454
  deserialize: ({ value }) => (value === 'A' || value === 'B' ? value : undefined),
429
455
  })
430
456
 
@@ -446,13 +472,13 @@ describe('Error0', () => {
446
472
  const AppError = Error0.use('prop', 'status', {
447
473
  init: (input: number) => input,
448
474
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') ?? 500,
449
- serialize: ({ value }) => value,
475
+ serialize: ({ resolved }) => resolved,
450
476
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
451
477
  })
452
478
  .use('prop', 'code', {
453
479
  init: (input: Code) => input,
454
480
  resolve: ({ flow }) => flow.find(isCode),
455
- serialize: ({ value }) => value,
481
+ serialize: ({ resolved }) => resolved,
456
482
  deserialize: ({ value }) => (value === 'A' || value === 'B' ? value : undefined),
457
483
  })
458
484
  .use('method', 'isStatus', (error, status: number) => error.status === status)
@@ -473,7 +499,7 @@ describe('Error0', () => {
473
499
  it('prop resolved type can be not undefined with init not provided', () => {
474
500
  const AppError = Error0.use('prop', 'x', {
475
501
  resolve: ({ flow }) => flow.find((item) => typeof item === 'number') || 500,
476
- serialize: ({ value }) => value,
502
+ serialize: ({ resolved }) => resolved,
477
503
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
478
504
  })
479
505
 
@@ -485,7 +511,7 @@ describe('Error0', () => {
485
511
  init: (input: number) => input,
486
512
  // @ts-expect-error - resolve type extends init type
487
513
  resolve: ({ flow }) => 'string',
488
- serialize: ({ value }) => value,
514
+ serialize: ({ resolved }) => resolved,
489
515
  deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
490
516
  })
491
517
  })
@@ -625,6 +651,38 @@ describe('Error0', () => {
625
651
  `)
626
652
  })
627
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
+ })
685
+
628
686
  it('Error0 assignable to LikeError0', () => {
629
687
  type LikeError0 = {
630
688
  from: (error: unknown) => Error