@devp0nt/error0 1.0.0-next.55 → 1.0.0-next.57

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.
@@ -1,106 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { Error0 } from '../index.js'
3
- import { causePlugin } from './cause.js'
4
-
5
- describe('causePlugin', () => {
6
- const statusPlugin = Error0.plugin().use('prop', 'status', {
7
- init: (input: number) => input,
8
- resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
9
- serialize: ({ resolved }) => resolved,
10
- deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
11
- })
12
-
13
- const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
14
- type Code = (typeof codes)[number]
15
- const codePlugin = Error0.plugin().use('prop', 'code', {
16
- init: (input: Code) => input,
17
- resolve: ({ flow }) => flow.find((value) => typeof value === 'string' && codes.includes(value)),
18
- serialize: ({ resolved, isPublic }) => (isPublic ? undefined : resolved),
19
- deserialize: ({ value }) =>
20
- typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
21
- })
22
-
23
- it('serializes and deserializes nested Error0 causes', () => {
24
- const AppError = Error0.use(statusPlugin)
25
- .use(codePlugin)
26
- .use(causePlugin({ isPublic: true }))
27
- const deepCauseError = new AppError('deep cause')
28
- const causeError = new AppError('cause', { status: 409, code: 'NOT_FOUND', cause: deepCauseError })
29
- const error = new AppError('root', { status: 500, cause: causeError })
30
-
31
- const json = AppError.serialize(error, false)
32
- expect(typeof json.cause).toBe('object')
33
- expect((json.cause as any).message).toBe('cause')
34
- expect((json.cause as any).status).toBe(409)
35
- expect((json.cause as any).code).toBe('NOT_FOUND')
36
- expect((json.cause as any).cause).toBeDefined()
37
- expect((json.cause as any).cause.message).toBe('deep cause')
38
- expect((json.cause as any).cause.status).toBe(undefined)
39
- expect((json.cause as any).cause.code).toBe(undefined)
40
- expect((json.cause as any).cause.cause).toBeUndefined()
41
-
42
- const recreated = AppError.from(json)
43
- expect(recreated).toBeInstanceOf(AppError)
44
- expect(recreated.cause).toBeInstanceOf(AppError)
45
- expect((recreated.cause as any).status).toBe(409)
46
- expect((recreated.cause as any).code).toBe('NOT_FOUND')
47
- expect((recreated.cause as any).cause).toBeInstanceOf(AppError)
48
- expect((recreated.cause as any).cause.message).toBe('deep cause')
49
- expect((recreated.cause as any).cause.status).toBe(undefined)
50
- expect((recreated.cause as any).cause.code).toBe(undefined)
51
- expect((recreated.cause as any).cause.cause).toBeUndefined()
52
- })
53
-
54
- it('supports variants', () => {
55
- class DbError extends Error {
56
- query: string
57
- constructor(message: string, options: { cause?: unknown; query: string }) {
58
- super(message, { cause: options.cause })
59
- this.query = options.query
60
- this.name = 'DbError'
61
- }
62
- static serialize(error: DbError): Record<string, unknown> {
63
- return {
64
- message: error.message,
65
- query: error.query,
66
- }
67
- }
68
- static from(error: unknown): DbError {
69
- if (error instanceof DbError) {
70
- return error
71
- }
72
- const object = typeof error === 'object' && error !== null ? (error as Record<string, unknown>) : {}
73
- const message =
74
- typeof object.message === 'string' ? object.message : typeof error === 'string' ? error : 'Unknown error'
75
- const query = typeof object.query === 'string' ? object.query : 'NOT_FOUND'
76
- return new DbError(message, { cause: error, query })
77
- }
78
- static isSerialized(serializedCause: unknown): boolean {
79
- return (
80
- typeof serializedCause === 'object' &&
81
- serializedCause !== null &&
82
- 'query' in serializedCause &&
83
- typeof serializedCause.query === 'string'
84
- )
85
- }
86
- }
87
- const AppError = Error0.use(statusPlugin)
88
- .use(codePlugin)
89
- .use(
90
- causePlugin({
91
- variants: {
92
- DbError,
93
- },
94
- }),
95
- )
96
- const dbError = new DbError('test', { query: 'SELECT * FROM users' })
97
- const error = new AppError('root', { status: 500, cause: dbError })
98
- const json = AppError.serialize(error, false)
99
- expect(json.cause).toBeDefined()
100
- expect((json.cause as any).query).toBe('SELECT * FROM users')
101
- const recreated = AppError.from(json)
102
- expect(recreated).toBeInstanceOf(AppError)
103
- expect(recreated.cause).toBeInstanceOf(DbError)
104
- expect((recreated.cause as any).query).toBe('SELECT * FROM users')
105
- })
106
- })
@@ -1,45 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- type Variant = {
4
- new (...args: any[]): unknown
5
- [Symbol.hasInstance]: (value: any) => boolean
6
- isSerialized: (serializedCause: any) => boolean
7
- serialize: (error: any) => unknown
8
- from: (error: any) => unknown
9
- }
10
-
11
- export const causePlugin = <TVariants extends Record<string, Variant> = Record<never, Variant>>({
12
- isPublic = false,
13
- variants = undefined,
14
- }: { isPublic?: boolean; variants?: TVariants } = {}) =>
15
- Error0.plugin().cause({
16
- serialize: ({ cause, isPublic: _isPublic, is, serialize }) => {
17
- if (!isPublic && _isPublic) {
18
- return undefined
19
- }
20
- if (variants) {
21
- for (const variant of Object.values(variants)) {
22
- if (cause instanceof variant) {
23
- return variant.serialize(cause)
24
- }
25
- }
26
- }
27
- if (is(cause)) {
28
- return serialize(cause)
29
- }
30
- return undefined
31
- },
32
- deserialize: ({ cause, fromSerialized, isSerialized }) => {
33
- if (variants) {
34
- for (const variant of Object.values(variants)) {
35
- if (variant.isSerialized(cause)) {
36
- return variant.from(cause)
37
- }
38
- }
39
- }
40
- if (isSerialized(cause)) {
41
- return fromSerialized(cause)
42
- }
43
- return cause
44
- },
45
- })
@@ -1,27 +0,0 @@
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
- })
@@ -1,20 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- export const codePlugin = <TCode extends string>({
4
- codes,
5
- isPublic = false,
6
- }: { codes?: TCode[]; isPublic?: boolean } = {}) => {
7
- const isCode = (value: unknown): value is TCode =>
8
- typeof value === 'string' && (!codes || codes.includes(value as TCode))
9
- return Error0.plugin().prop('code', {
10
- init: (code: TCode) => code,
11
- resolve: ({ flow }) => flow.find(Boolean),
12
- serialize: ({ resolved, isPublic: _isPublic }) => {
13
- if (!isPublic && _isPublic) {
14
- return undefined
15
- }
16
- return resolved
17
- },
18
- deserialize: ({ value, record }) => (isCode(value) ? value : undefined),
19
- })
20
- }
@@ -1,66 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { Error0 } from '../index.js'
3
- import { expectedPlugin } from './expected.js'
4
-
5
- describe('expectedPlugin', () => {
6
- const statusPlugin = Error0.plugin().use('prop', 'status', {
7
- init: (input: number) => input,
8
- resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
9
- serialize: ({ resolved }) => resolved,
10
- deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
11
- })
12
-
13
- it('can be used to control error tracker behavior', () => {
14
- const AppError = Error0.use(statusPlugin).use(expectedPlugin({ isPublic: true }))
15
- const errorExpected = new AppError('test', { status: 400, expected: true })
16
- const errorUnexpected = new AppError('test', { status: 400, expected: false })
17
- const usualError = new Error('test')
18
- const errorFromUsualError = AppError.from(usualError)
19
- const errorWithExpectedErrorAsCause = new AppError('test', { status: 400, cause: errorExpected })
20
- const errorWithUnexpectedErrorAsCause = new AppError('test', { status: 400, cause: errorUnexpected })
21
- expect(errorExpected.expected).toBe(true)
22
- expect(errorUnexpected.expected).toBe(false)
23
- expect(AppError.isExpected(usualError)).toBe(false)
24
- expect(errorFromUsualError.expected).toBe(false)
25
- expect(errorFromUsualError.isExpected()).toBe(false)
26
- expect(errorWithExpectedErrorAsCause.expected).toBe(true)
27
- expect(errorWithExpectedErrorAsCause.isExpected()).toBe(true)
28
- expect(errorWithUnexpectedErrorAsCause.expected).toBe(false)
29
- expect(errorWithUnexpectedErrorAsCause.isExpected()).toBe(false)
30
- })
31
-
32
- it('resolves to false when any cause has false', () => {
33
- const AppError = Error0.use(expectedPlugin({ isPublic: true }))
34
- const root = new AppError('root', { expected: true })
35
- const middle = new AppError('middle', { expected: false, cause: root })
36
- const leaf = new AppError('leaf', { expected: false, cause: middle })
37
- expect(leaf.expected).toBe(false)
38
- expect(leaf.isExpected()).toBe(false)
39
- })
40
-
41
- it('treats undefined expected as unexpected', () => {
42
- const AppError = Error0.use(expectedPlugin({ isPublic: true }))
43
- const error = new AppError('without expected')
44
- expect(error.expected).toBe(false)
45
- expect(error.isExpected()).toBe(false)
46
- })
47
-
48
- it('supports override', () => {
49
- const AppError = Error0.use(statusPlugin).use(
50
- expectedPlugin({
51
- isPublic: true,
52
- override: (error) => {
53
- return error.message.includes('CRITICAL') ? false : undefined
54
- },
55
- }),
56
- )
57
- const errorNonCritical = new AppError('USUAL: test', { status: 400 })
58
- const errorNonCriticalExpected = new AppError('USUAL: test', { status: 400, expected: true })
59
- const errorCritical = new AppError('CRITICAL: test', { status: 400 })
60
- const errorCriticalExpected = new AppError('CRITICAL: test', { status: 400, expected: true })
61
- expect(errorNonCritical.expected).toBe(false) // becouse expected was not provided at all
62
- expect(errorNonCriticalExpected.expected).toBe(true)
63
- expect(errorCritical.expected).toBe(false)
64
- expect(errorCriticalExpected.expected).toBe(false)
65
- })
66
- })
@@ -1,48 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- const isExpected = ({
4
- flow,
5
- error,
6
- override,
7
- }: {
8
- flow: unknown[]
9
- error: Error0
10
- override?: (error: any) => boolean | undefined
11
- }) => {
12
- if (override) {
13
- const overridden = override(error)
14
- if (overridden !== undefined) {
15
- return overridden
16
- }
17
- }
18
- let expected = false
19
- for (const value of flow) {
20
- if (value === false) {
21
- return false
22
- }
23
- if (value === true) {
24
- expected = true
25
- }
26
- }
27
- return expected
28
- }
29
-
30
- export const expectedPlugin = <TError extends Error0>({
31
- isPublic = false,
32
- override,
33
- }: { isPublic?: boolean; override?: (error: TError) => boolean | undefined } = {}) =>
34
- Error0.plugin()
35
- .prop('expected', {
36
- init: (input: boolean) => input,
37
- resolve: ({ flow, error }) => isExpected({ flow, error, override }),
38
- serialize: ({ resolved, isPublic: _isPublic }) => {
39
- if (isPublic && _isPublic) {
40
- return undefined
41
- }
42
- return resolved
43
- },
44
- deserialize: ({ value }) => (typeof value === 'boolean' ? value : undefined),
45
- })
46
- .method('isExpected', (error) => {
47
- return isExpected({ flow: error.flow('expected'), error, override })
48
- })
@@ -1,45 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { Error0 } from '../index.js'
3
- import { flatOriginalPlugin } from './flat-original.js'
4
-
5
- describe('flatOriginalPlugin', () => {
6
- it('without plugin original error comes to cause', () => {
7
- const usualError = new Error('another error')
8
- const error = Error0.from(usualError)
9
- expect(error).toBeInstanceOf(Error0)
10
- expect(error).toBeInstanceOf(Error)
11
- expect(error.name).toBe('Error0')
12
- expect(error.cause).not.toBeInstanceOf(Error0)
13
- expect(error.cause).toBeInstanceOf(Error)
14
- expect(error.cause).toBe(usualError)
15
- expect((error.cause as any).name).toBe('Error')
16
- expect(error.causes()).toEqual([error, usualError])
17
- })
18
-
19
- it('with plugin original error becomes error0 itself', () => {
20
- const usualError = new Error('another error')
21
- const AppError = Error0.use(flatOriginalPlugin())
22
- const error = AppError.from(usualError)
23
- expect(error).toBeInstanceOf(AppError)
24
- expect(error).toBeInstanceOf(Error0)
25
- expect(error).toBeInstanceOf(Error)
26
- expect(error.message).toBe(usualError.message)
27
- expect(error.stack).toBe(usualError.stack)
28
- expect(error.name).toBe('Error0')
29
- expect(error.cause).toBeUndefined()
30
- })
31
-
32
- it('with plugin original error becomes error0 itself but keep it own causes', () => {
33
- const causeError = new Error('cause error')
34
- const usualError = new Error('another error', { cause: causeError })
35
- const AppError = Error0.use(flatOriginalPlugin())
36
- const error = AppError.from(usualError)
37
- expect(error).toBeInstanceOf(AppError)
38
- expect(error).toBeInstanceOf(Error0)
39
- expect(error).toBeInstanceOf(Error)
40
- expect(error.message).toBe(usualError.message)
41
- expect(error.stack).toBe(usualError.stack)
42
- expect(error.name).toBe('Error0')
43
- expect(error.causes()).toEqual([error, causeError])
44
- })
45
- })
@@ -1,12 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- export const flatOriginalPlugin = ({ prefix }: { prefix?: string } = {}) => {
4
- return Error0.plugin().adapt((error) => {
5
- const cause = error.cause
6
- if (cause instanceof Error && cause.constructor === Error) {
7
- error.cause = cause.cause
8
- error.message = `${prefix ?? ''}${cause.message}`
9
- error.stack = cause.stack
10
- }
11
- })
12
- }
@@ -1,32 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { Error0 } from '../index.js'
3
- import { messageMergePlugin } from './message-merge.js'
4
-
5
- describe('messageMergePlugin', () => {
6
- const statusPlugin = Error0.plugin().use('prop', 'status', {
7
- init: (input: number) => input,
8
- resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
9
- serialize: ({ resolved }) => resolved,
10
- deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
11
- })
12
-
13
- const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
14
- type Code = (typeof codes)[number]
15
- const codePlugin = Error0.plugin().use('prop', 'code', {
16
- init: (input: Code) => input,
17
- resolve: ({ flow }) => flow.find((value) => typeof value === 'string' && codes.includes(value)),
18
- serialize: ({ resolved, isPublic }) => (isPublic ? undefined : resolved),
19
- deserialize: ({ value }) =>
20
- typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
21
- })
22
-
23
- it('can merge message across causes in one serialized value', () => {
24
- const AppError = Error0.use(statusPlugin).use(codePlugin).use(messageMergePlugin())
25
- const error1 = new AppError('test1', { status: 400, code: 'NOT_FOUND' })
26
- const error2 = new AppError('test2', { status: 401, cause: error1 })
27
- expect(error1.message).toBe('test1')
28
- expect(error2.message).toBe('test2')
29
- expect(error1.serialize().message).toBe('test1')
30
- expect(error2.serialize().message).toBe('test2: test1')
31
- })
32
- })
@@ -1,19 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- export const messageMergePlugin = ({
4
- delimiter = ': ',
5
- fallback = 'Unknown error',
6
- }: { delimiter?: string; fallback?: string } = {}) =>
7
- Error0.plugin().use('message', {
8
- serialize: ({ error }) => {
9
- return (
10
- error
11
- .causes()
12
- .map((cause) => {
13
- return cause instanceof Error ? cause.message : undefined
14
- })
15
- .filter((value): value is string => typeof value === 'string')
16
- .join(delimiter) || fallback
17
- )
18
- },
19
- })
@@ -1,32 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { Error0 } from '../index.js'
3
- import { metaPlugin } from './meta.js'
4
-
5
- describe('metaPlugin', () => {
6
- it('merges meta across causes into current error', () => {
7
- const AppError = Error0.use(metaPlugin())
8
- const root = new AppError('root', { meta: { requestId: 'r1', source: 'db' } })
9
- const leaf = new AppError('leaf', { meta: { route: '/ideas', source: 'api' }, cause: root })
10
- expect(leaf.resolve().meta).toEqual({
11
- requestId: 'r1',
12
- source: 'api',
13
- route: '/ideas',
14
- })
15
- })
16
-
17
- it('serializes meta only for private output and keeps json-safe values', () => {
18
- const AppError = Error0.use(metaPlugin())
19
- const error = new AppError('test', {
20
- meta: {
21
- ok: true,
22
- nested: { id: 1 },
23
- skip: () => 'x',
24
- },
25
- })
26
- expect(AppError.serialize(error, false).meta).toEqual({
27
- ok: true,
28
- nested: { id: 1 },
29
- })
30
- expect('meta' in AppError.serialize(error, true)).toBe(false)
31
- })
32
- })
@@ -1,59 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- type Json = null | boolean | number | string | Json[] | { [key: string]: Json }
4
-
5
- const toJsonSafe = (input: unknown): Json | undefined => {
6
- if (input === null) {
7
- return null
8
- }
9
- if (typeof input === 'string' || typeof input === 'number' || typeof input === 'boolean') {
10
- return input
11
- }
12
- if (Array.isArray(input)) {
13
- return input.map((value) => toJsonSafe(value)) as Json[]
14
- }
15
- if (typeof input === 'object') {
16
- const output: Record<string, Json> = {}
17
- for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
18
- const jsonValue = toJsonSafe(value)
19
- if (jsonValue !== undefined) {
20
- output[key] = jsonValue
21
- }
22
- }
23
- return output
24
- }
25
- return undefined
26
- }
27
-
28
- const isMetaRecord = (value: unknown): value is Record<string, unknown> =>
29
- typeof value === 'object' && value !== null && !Array.isArray(value)
30
-
31
- export const metaPlugin = ({ isPublic = false }: { isPublic?: boolean } = {}) =>
32
- Error0.plugin().prop('meta', {
33
- init: (input: Record<string, unknown>) => input,
34
- resolve: ({ flow }) => {
35
- const values = flow.filter(isMetaRecord)
36
- if (values.length === 0) {
37
- return undefined
38
- }
39
-
40
- // Merge cause meta into the current error; nearer errors win on conflicts.
41
- const merged: Record<string, unknown> = {}
42
- for (const value of [...values].reverse()) {
43
- Object.assign(merged, value)
44
- }
45
- return merged
46
- },
47
- serialize: ({ resolved, isPublic: _isPublic }) => {
48
- if (!isPublic && _isPublic) {
49
- return undefined
50
- }
51
- return toJsonSafe(resolved)
52
- },
53
- deserialize: ({ value }) => {
54
- if (!isMetaRecord(value)) {
55
- return undefined
56
- }
57
- return value
58
- },
59
- })
@@ -1,57 +0,0 @@
1
- import { describe, expect, it } from 'bun:test'
2
- import { Error0 } from '../index.js'
3
- import { stackMergePlugin } from './stack-merge.js'
4
-
5
- const fixStack = (stack: string | undefined) => {
6
- if (!stack) {
7
- return stack
8
- }
9
- const lines = stack.split('\n')
10
- const fixedLines = lines.map((line) => {
11
- const withoutPath = line.replace(/\(.*\)$/, '(...)')
12
- return withoutPath
13
- })
14
- return fixedLines.join('\n')
15
- }
16
-
17
- describe('stackMergePlugin', () => {
18
- const statusPlugin = Error0.plugin().use('prop', 'status', {
19
- init: (input: number) => input,
20
- resolve: ({ flow }) => flow.find((value) => typeof value === 'number'),
21
- serialize: ({ resolved }) => resolved,
22
- deserialize: ({ value }) => (typeof value === 'number' ? value : undefined),
23
- })
24
-
25
- const codes = ['NOT_FOUND', 'BAD_REQUEST', 'UNAUTHORIZED'] as const
26
- type Code = (typeof codes)[number]
27
- const codePlugin = Error0.plugin().use('prop', 'code', {
28
- init: (input: Code) => input,
29
- resolve: ({ flow }) => flow.find((value) => typeof value === 'string' && codes.includes(value)),
30
- serialize: ({ resolved, isPublic }) => (isPublic ? undefined : resolved),
31
- deserialize: ({ value }) =>
32
- typeof value === 'string' && codes.includes(value as Code) ? (value as Code) : undefined,
33
- })
34
-
35
- it('can merge stack across causes in one serialized value', () => {
36
- const AppError = Error0.use(statusPlugin).use(codePlugin).use(stackMergePlugin())
37
- const error1 = new AppError('test1', { status: 400, code: 'NOT_FOUND' })
38
- const error2 = new AppError('test2', { status: 401, cause: error1 })
39
- const mergedStack1 = error1.serialize(false).stack as string
40
- const mergedStack2 = error2.serialize(false).stack as string
41
- const mergedStack2Public = error2.serialize(true).stack as string | undefined
42
- expect(mergedStack1).toContain('Error0: test1')
43
- expect(mergedStack2).toContain('Error0: test2')
44
- expect(mergedStack2).toContain('Error0: test1')
45
- expect(fixStack(mergedStack1)).toMatchInlineSnapshot(`
46
- "Error0: test1
47
- at <anonymous> (...)"
48
- `)
49
- expect(fixStack(mergedStack2)).toMatchInlineSnapshot(`
50
- "Error0: test2
51
- at <anonymous> (...)
52
- Error0: test1
53
- at <anonymous> (...)"
54
- `)
55
- expect(mergedStack2Public).toBeUndefined()
56
- })
57
- })
@@ -1,19 +0,0 @@
1
- import { Error0 } from '../index.js'
2
-
3
- export const stackMergePlugin = ({
4
- isPublic = false,
5
- delimiter = '\n',
6
- }: { isPublic?: boolean; delimiter?: string } = {}) =>
7
- Error0.plugin().stack({
8
- serialize: ({ error, isPublic: _isPublic }) => {
9
- if (!isPublic && _isPublic) {
10
- return undefined
11
- }
12
- return error
13
- .causes()
14
- .flatMap((cause) => {
15
- return cause instanceof Error && cause.stack && typeof cause.stack === 'string' ? cause.stack : []
16
- })
17
- .join(delimiter)
18
- },
19
- })
@@ -1,54 +0,0 @@
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
- })