@outputai/core 0.6.1-next.d3c9b1f.0 → 0.6.1-next.fc6a93e.0
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/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { extractErrorDetail } from './errors.js';
|
|
3
|
+
|
|
4
|
+
describe( 'extractErrorDetail', () => {
|
|
5
|
+
it( 'returns a matching value from error details', () => {
|
|
6
|
+
const error = new Error( 'failed' );
|
|
7
|
+
error.details = [
|
|
8
|
+
{ requestId: 'req-1' },
|
|
9
|
+
{ workflowId: 'workflow-1' }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
expect( extractErrorDetail( error, 'workflowId' ) ).toBe( 'workflow-1' );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
it( 'walks the cause chain until it finds matching details', () => {
|
|
16
|
+
const root = new Error( 'root' );
|
|
17
|
+
root.details = [ { traceId: 'trace-1' } ];
|
|
18
|
+
const wrapped = new Error( 'wrapped', { cause: root } );
|
|
19
|
+
|
|
20
|
+
expect( extractErrorDetail( wrapped, 'traceId' ) ).toBe( 'trace-1' );
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
it( 'prefers details from the current error over causes', () => {
|
|
24
|
+
const root = new Error( 'root' );
|
|
25
|
+
root.details = [ { traceId: 'root-trace' } ];
|
|
26
|
+
const wrapped = new Error( 'wrapped', { cause: root } );
|
|
27
|
+
wrapped.details = [ { traceId: 'wrapped-trace' } ];
|
|
28
|
+
|
|
29
|
+
expect( extractErrorDetail( wrapped, 'traceId' ) ).toBe( 'wrapped-trace' );
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
it( 'returns null when the key is not found', () => {
|
|
33
|
+
const root = new Error( 'root' );
|
|
34
|
+
root.details = [ { traceId: 'trace-1' } ];
|
|
35
|
+
const wrapped = new Error( 'wrapped', { cause: root } );
|
|
36
|
+
|
|
37
|
+
expect( extractErrorDetail( wrapped, 'missing' ) ).toBeNull();
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'returns null for empty errors', () => {
|
|
41
|
+
expect( extractErrorDetail( null, 'traceId' ) ).toBeNull();
|
|
42
|
+
} );
|
|
43
|
+
} );
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @typedef {object} SerializedError
|
|
3
5
|
* @property {string} name - The error constructor name
|
|
@@ -6,16 +8,35 @@
|
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
|
-
* Serialize an error object.
|
|
11
|
+
* Recursively Serialize an error object. Navigate using "cause" property.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
13
|
+
* Goes up 10 levels deep.
|
|
12
14
|
*
|
|
13
15
|
* @param {Error} error
|
|
14
16
|
* @returns {SerializedError}
|
|
15
17
|
*/
|
|
16
|
-
export const serializeError =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export const serializeError = ( () => {
|
|
19
|
+
|
|
20
|
+
const serializeValue = v => {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse( JSON.stringify( v ) );
|
|
23
|
+
} catch {
|
|
24
|
+
return inspect( v, { depth: 5, breakLength: Infinity, colors: false } );
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return ( error, depth = 0 ) => {
|
|
29
|
+
if ( depth > 10 ) {
|
|
30
|
+
return { name: 'Error', message: 'Cause chain too deep' };
|
|
31
|
+
}
|
|
32
|
+
if ( error instanceof Error ) {
|
|
33
|
+
return {
|
|
34
|
+
name: error.constructor.name,
|
|
35
|
+
message: error.message,
|
|
36
|
+
stack: error.stack,
|
|
37
|
+
...( error.cause !== undefined ? { cause: serializeError( error.cause, depth + 1 ) } : {} )
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return serializeValue( error );
|
|
21
41
|
};
|
|
42
|
+
} )();
|
|
@@ -1,14 +1,83 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { serializeError } from './utils.js';
|
|
3
3
|
|
|
4
|
-
describe( '
|
|
5
|
-
it( '
|
|
6
|
-
const
|
|
7
|
-
const outer = new Error( 'outer', { cause: inner } );
|
|
4
|
+
describe( 'serializeError', () => {
|
|
5
|
+
it( 'serializes basic Error fields', () => {
|
|
6
|
+
const error = new Error( 'boom' );
|
|
8
7
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
expect(
|
|
12
|
-
|
|
8
|
+
const result = serializeError( error );
|
|
9
|
+
|
|
10
|
+
expect( result ).toMatchObject( {
|
|
11
|
+
name: 'Error',
|
|
12
|
+
message: 'boom'
|
|
13
|
+
} );
|
|
14
|
+
expect( typeof result.stack ).toBe( 'string' );
|
|
15
|
+
expect( result ).not.toHaveProperty( 'cause' );
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
it( 'preserves custom error constructor names', () => {
|
|
19
|
+
class CustomError extends Error {}
|
|
20
|
+
const error = new CustomError( 'custom boom' );
|
|
21
|
+
|
|
22
|
+
expect( serializeError( error ) ).toMatchObject( {
|
|
23
|
+
name: 'CustomError',
|
|
24
|
+
message: 'custom boom'
|
|
25
|
+
} );
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
it( 'recursively serializes Error causes', () => {
|
|
29
|
+
const root = new TypeError( 'root failure' );
|
|
30
|
+
const wrapped = new Error( 'wrapped failure', { cause: root } );
|
|
31
|
+
|
|
32
|
+
expect( serializeError( wrapped ) ).toMatchObject( {
|
|
33
|
+
name: 'Error',
|
|
34
|
+
message: 'wrapped failure',
|
|
35
|
+
cause: {
|
|
36
|
+
name: 'TypeError',
|
|
37
|
+
message: 'root failure'
|
|
38
|
+
}
|
|
39
|
+
} );
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
it( 'preserves JSON-serializable non-Error causes', () => {
|
|
43
|
+
const cause = {
|
|
44
|
+
code: 'bad_input',
|
|
45
|
+
path: [ 'items', 0, 'title' ],
|
|
46
|
+
message: 'Expected title'
|
|
47
|
+
};
|
|
48
|
+
const error = new Error( 'validation failed', { cause } );
|
|
49
|
+
|
|
50
|
+
expect( serializeError( error ) ).toMatchObject( {
|
|
51
|
+
name: 'Error',
|
|
52
|
+
message: 'validation failed',
|
|
53
|
+
cause
|
|
54
|
+
} );
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
it( 'falls back to inspect for non-JSON-serializable causes', () => {
|
|
58
|
+
const cause = { name: 'circular' };
|
|
59
|
+
cause.self = cause;
|
|
60
|
+
const error = new Error( 'failed', { cause } );
|
|
61
|
+
|
|
62
|
+
const result = serializeError( error );
|
|
63
|
+
|
|
64
|
+
expect( result.cause ).toContain( 'circular' );
|
|
65
|
+
expect( result.cause ).toContain( 'Circular' );
|
|
66
|
+
} );
|
|
67
|
+
|
|
68
|
+
it( 'falls back to inspect for primitive values JSON cannot serialize', () => {
|
|
69
|
+
expect( serializeError( 1n ) ).toBe( '1n' );
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
it( 'stops serializing cause chains after the depth limit', () => {
|
|
73
|
+
const makeErrorChain = depth => depth === 0 ?
|
|
74
|
+
new Error( 'leaf' ) :
|
|
75
|
+
new Error( `level ${depth}`, { cause: makeErrorChain( depth - 1 ) } );
|
|
76
|
+
const getCauseAtDepth = ( error, depth ) => depth === 0 ?
|
|
77
|
+
error :
|
|
78
|
+
getCauseAtDepth( error.cause, depth - 1 );
|
|
79
|
+
|
|
80
|
+
expect( getCauseAtDepth( serializeError( makeErrorChain( 11 ) ), 11 ) )
|
|
81
|
+
.toEqual( { name: 'Error', message: 'Cause chain too deep' } );
|
|
13
82
|
} );
|
|
14
83
|
} );
|