@outputai/core 0.10.1-next.fc0a41f.0 → 0.11.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 +12 -7
- package/src/consts.js +5 -2
- package/src/errors.d.ts +12 -2
- package/src/errors.js +17 -3
- package/src/errors.spec.js +19 -0
- package/src/helpers/activity_options.js +21 -0
- package/src/helpers/activity_options.spec.js +55 -0
- package/src/helpers/error_serializer.js +236 -0
- package/src/helpers/error_serializer.spec.js +413 -0
- package/src/helpers/errors.js +55 -13
- package/src/helpers/errors.spec.js +166 -66
- package/src/helpers/fetch.js +1 -38
- package/src/helpers/fetch.spec.js +1 -39
- package/src/helpers/function.js +13 -0
- package/src/helpers/function.spec.js +23 -1
- package/src/helpers/redact.js +51 -0
- package/src/helpers/redact.spec.js +66 -0
- package/src/helpers/string.js +10 -0
- package/src/helpers/string.spec.js +36 -1
- package/src/hooks/index.d.ts +35 -44
- package/src/hooks/index.js +2 -1
- package/src/hooks/index.spec.js +15 -7
- package/src/interface/evaluator.d.ts +10 -9
- package/src/interface/evaluator.js +1 -6
- package/src/interface/evaluator.spec.js +10 -10
- package/src/interface/step.d.ts +16 -15
- package/src/interface/step.js +1 -6
- package/src/interface/step.spec.js +10 -10
- package/src/interface/validations/index.js +33 -53
- package/src/interface/validations/index.spec.js +25 -17
- package/src/interface/webhook.js +1 -2
- package/src/interface/webhook.spec.js +25 -19
- package/src/interface/workflow.d.ts +25 -18
- package/src/interface/workflow.js +24 -52
- package/src/interface/workflow.spec.js +109 -108
- package/src/interface/workflow_activity_options.js +2 -5
- package/src/temporal/client.js +47 -0
- package/src/temporal/client.spec.js +124 -0
- package/src/temporal/index.d.ts +32 -0
- package/src/temporal/index.js +1 -0
- package/src/tracing/trace_engine.js +9 -11
- package/src/tracing/trace_engine.spec.js +67 -28
- package/src/worker/catalog_workflow/catalog_job.js +3 -3
- package/src/worker/catalog_workflow/catalog_job.spec.js +1 -1
- package/src/worker/index.js +10 -1
- package/src/worker/index.spec.js +23 -8
- package/src/worker/interceptors/activity.js +32 -19
- package/src/worker/interceptors/activity.spec.js +71 -166
- package/src/worker/interceptors/workflow.js +53 -11
- package/src/worker/interceptors/workflow.spec.js +176 -49
- package/src/worker/loader/workflows.js +16 -3
- package/src/worker/loader/workflows.spec.js +37 -5
- package/src/worker/log_hooks.js +13 -10
- package/src/worker/log_hooks.spec.js +12 -4
- package/src/worker/sinks.js +3 -1
- package/src/worker/sinks.spec.js +21 -4
- package/src/worker/telemetry.js +2 -1
- package/src/worker/temporal_logger.js +33 -0
- package/src/worker/temporal_logger.spec.js +90 -0
- package/src/helpers/aggregations.js +0 -44
- package/src/helpers/aggregations.spec.js +0 -92
- package/src/tracing/tools/utils.js +0 -42
- package/src/tracing/tools/utils.spec.js +0 -83
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "The core module of the output framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"./sdk/runtime": {
|
|
23
23
|
"types": "./src/sdk/runtime/index.d.ts",
|
|
24
24
|
"import": "./src/sdk/runtime/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./temporal": {
|
|
27
|
+
"types": "./src/temporal/index.d.ts",
|
|
28
|
+
"import": "./src/temporal/index.js"
|
|
25
29
|
}
|
|
26
30
|
},
|
|
27
31
|
"files": [
|
|
@@ -41,17 +45,17 @@
|
|
|
41
45
|
"@babel/parser": "7.29.2",
|
|
42
46
|
"@babel/traverse": "7.29.0",
|
|
43
47
|
"@babel/types": "7.29.0",
|
|
44
|
-
"@temporalio/activity": "1.
|
|
45
|
-
"@temporalio/client": "1.
|
|
46
|
-
"@temporalio/common": "1.
|
|
47
|
-
"@temporalio/worker": "1.
|
|
48
|
-
"@temporalio/workflow": "1.
|
|
48
|
+
"@temporalio/activity": "1.20.3",
|
|
49
|
+
"@temporalio/client": "1.20.3",
|
|
50
|
+
"@temporalio/common": "1.20.3",
|
|
51
|
+
"@temporalio/worker": "1.20.3",
|
|
52
|
+
"@temporalio/workflow": "1.20.3",
|
|
49
53
|
"decimal.js": "10.6.0",
|
|
50
54
|
"folder-hash": "4.1.3",
|
|
51
55
|
"json-stream-stringify": "3.1.6",
|
|
52
56
|
"redis": "5.12.1",
|
|
53
57
|
"stacktrace-parser": "0.1.11",
|
|
54
|
-
"undici": "8.
|
|
58
|
+
"undici": "8.9.0",
|
|
55
59
|
"winston": "3.19.0"
|
|
56
60
|
},
|
|
57
61
|
"peerDependencies": {
|
|
@@ -74,6 +78,7 @@
|
|
|
74
78
|
"#tracing": "./src/tracing/internal_interface.js",
|
|
75
79
|
"#trace_attribute": "./src/tracing/trace_attribute.js",
|
|
76
80
|
"#async_storage": "./src/async_storage.js",
|
|
81
|
+
"#temporal/*": "./src/temporal/*.js",
|
|
77
82
|
"#internal_activities": "./src/internal_activities/index.js"
|
|
78
83
|
},
|
|
79
84
|
"scripts": {
|
package/src/consts.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export const ACTIVITY_GET_TRACE_DESTINATIONS = '__internal#getTraceDestinations';
|
|
2
2
|
export const ACTIVITY_OPTIONS_FILENAME = '__activity_options.js';
|
|
3
3
|
export const ACTIVITY_SEND_HTTP_REQUEST = '__internal#sendHttpRequest';
|
|
4
|
-
export const ACTIVITY_WRAPPER_VERSION_FIELD = '__output_activity_wrapper_version';
|
|
5
4
|
export const ACTIVITY_LOGGER_SYMBOL = Symbol.for( '__activity_logger' );
|
|
6
5
|
export const METADATA_ACCESS_SYMBOL = Symbol( '__metadata' );
|
|
7
6
|
export const WORKFLOW_CATALOG = '$catalog';
|
|
7
|
+
export const WORKFLOW_OPTIONS_FILENAME = '__workflow_options.js';
|
|
8
8
|
export const WORKFLOWS_INDEX_FILENAME = '__workflows_entrypoint.js';
|
|
9
|
-
export const WORKFLOW_WRAPPER_VERSION_FIELD = '__output_workflow_wrapper_version';
|
|
10
9
|
|
|
11
10
|
export const INVOKE_ACTIVITY_SYMBOL = Symbol.for( '@outputai/core:__invoke_activity' );
|
|
12
11
|
|
|
@@ -42,3 +41,7 @@ export const BusEventType = {
|
|
|
42
41
|
export const WorkflowSpecialOutput = {
|
|
43
42
|
CONTINUED_AS_NEW: '<<continued_as_new>>'
|
|
44
43
|
};
|
|
44
|
+
|
|
45
|
+
export const ActivitySpecialOutput = {
|
|
46
|
+
ASYNC_HANDOFF: '<<async_handoff>>'
|
|
47
|
+
};
|
package/src/errors.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Throw this error to end the workflow execution altogether without retries.
|
|
5
5
|
*/
|
|
6
|
-
export class FatalError extends Error {
|
|
6
|
+
export class FatalError extends Error {}
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Error indicating invalid input or schema validation issues.
|
|
@@ -12,4 +12,14 @@ export class FatalError extends Error { }
|
|
|
12
12
|
*
|
|
13
13
|
* It will end the workflow execution without retries.
|
|
14
14
|
*/
|
|
15
|
-
export class ValidationError extends
|
|
15
|
+
export class ValidationError extends FatalError {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* It serves to indicate a FatalError, but has no properties of its own, except cause.
|
|
19
|
+
*
|
|
20
|
+
* The SDK will discard it and use its cause in its place for logs, traces and the thrown error.
|
|
21
|
+
*/
|
|
22
|
+
export class TransparentFatalError extends FatalError {
|
|
23
|
+
readonly cause: Error;
|
|
24
|
+
constructor( cause: Error );
|
|
25
|
+
}
|
package/src/errors.js
CHANGED
|
@@ -4,15 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* A FatalError breaks workflow execution
|
|
8
8
|
*/
|
|
9
9
|
export class FatalError extends Error {
|
|
10
10
|
name = 'FatalError';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* It is a specialization of the FatalError to indicate validation problems
|
|
15
15
|
*/
|
|
16
|
-
export class ValidationError extends
|
|
16
|
+
export class ValidationError extends FatalError {
|
|
17
17
|
name = 'ValidationError';
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* It serves to indicate a FatalError, but has no properties of its own, except cause.
|
|
22
|
+
*
|
|
23
|
+
* The SDK will discard it and use its cause in its place for logs, traces and the thrown error.
|
|
24
|
+
*/
|
|
25
|
+
export class TransparentFatalError extends FatalError {
|
|
26
|
+
constructor( cause ) {
|
|
27
|
+
if ( !( cause instanceof Error ) ) {
|
|
28
|
+
throw new FatalError( 'TransparentFatalError argument must be an Error instance' );
|
|
29
|
+
}
|
|
30
|
+
super( undefined, { cause } );
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { FatalError, TransparentFatalError, ValidationError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
describe( 'SDK errors', () => {
|
|
5
|
+
it( 'treats validation and transparent errors as fatal errors', () => {
|
|
6
|
+
const cause = new TypeError( 'provider rejected request' );
|
|
7
|
+
const transparentError = new TransparentFatalError( cause );
|
|
8
|
+
|
|
9
|
+
expect( new ValidationError( 'invalid input' ) ).toBeInstanceOf( FatalError );
|
|
10
|
+
expect( transparentError ).toBeInstanceOf( FatalError );
|
|
11
|
+
expect( transparentError.cause ).toBe( cause );
|
|
12
|
+
} );
|
|
13
|
+
|
|
14
|
+
it( 'rejects transparent errors without an Error cause', () => {
|
|
15
|
+
expect( () => new TransparentFatalError( 'provider rejected request' ) ).toThrow(
|
|
16
|
+
new FatalError( 'TransparentFatalError argument must be an Error instance' )
|
|
17
|
+
);
|
|
18
|
+
} );
|
|
19
|
+
} );
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FatalError } from '#errors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a copy of activity options with framework-required policies enforced.
|
|
5
|
+
* @param {object} activityOptions
|
|
6
|
+
* @returns {object}
|
|
7
|
+
*/
|
|
8
|
+
export const enforceActivityOptions = activityOptions => {
|
|
9
|
+
const options = activityOptions ?? {};
|
|
10
|
+
const retry = options.retry ?? {};
|
|
11
|
+
const nonRetryableErrorTypes = retry.nonRetryableErrorTypes ?? [];
|
|
12
|
+
return {
|
|
13
|
+
...options,
|
|
14
|
+
retry: {
|
|
15
|
+
...retry,
|
|
16
|
+
nonRetryableErrorTypes: [
|
|
17
|
+
...new Set( nonRetryableErrorTypes.concat( FatalError.name ) )
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { enforceActivityOptions } from './activity_options.js';
|
|
3
|
+
|
|
4
|
+
describe( 'enforceActivityOptions', () => {
|
|
5
|
+
it( 'adds FatalError while preserving existing activity and retry options', () => {
|
|
6
|
+
const activityOptions = {
|
|
7
|
+
startToCloseTimeout: '5m',
|
|
8
|
+
retry: {
|
|
9
|
+
maximumAttempts: 4,
|
|
10
|
+
nonRetryableErrorTypes: [ 'DomainError' ]
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
expect( enforceActivityOptions( activityOptions ) ).toEqual( {
|
|
15
|
+
startToCloseTimeout: '5m',
|
|
16
|
+
retry: {
|
|
17
|
+
maximumAttempts: 4,
|
|
18
|
+
nonRetryableErrorTypes: [ 'DomainError', 'FatalError' ]
|
|
19
|
+
}
|
|
20
|
+
} );
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
it( 'does not duplicate FatalError', () => {
|
|
24
|
+
const result = enforceActivityOptions( {
|
|
25
|
+
retry: { nonRetryableErrorTypes: [ 'FatalError', 'DomainError', 'FatalError' ] }
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
expect( result.retry.nonRetryableErrorTypes ).toEqual( [ 'FatalError', 'DomainError' ] );
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'returns a new object without mutating the provided options', () => {
|
|
32
|
+
const activityOptions = {
|
|
33
|
+
retry: {
|
|
34
|
+
maximumAttempts: 2,
|
|
35
|
+
nonRetryableErrorTypes: [ 'DomainError' ]
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const result = enforceActivityOptions( activityOptions );
|
|
39
|
+
|
|
40
|
+
expect( result ).not.toBe( activityOptions );
|
|
41
|
+
expect( result.retry ).not.toBe( activityOptions.retry );
|
|
42
|
+
expect( activityOptions ).toEqual( {
|
|
43
|
+
retry: {
|
|
44
|
+
maximumAttempts: 2,
|
|
45
|
+
nonRetryableErrorTypes: [ 'DomainError' ]
|
|
46
|
+
}
|
|
47
|
+
} );
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
it( 'creates required retry options when options are omitted', () => {
|
|
51
|
+
expect( enforceActivityOptions() ).toEqual( {
|
|
52
|
+
retry: { nonRetryableErrorTypes: [ 'FatalError' ] }
|
|
53
|
+
} );
|
|
54
|
+
} );
|
|
55
|
+
} );
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { tryOrUndefined } from './function.js';
|
|
2
|
+
import { redactUrl } from './redact.js';
|
|
3
|
+
import { isUrl } from './string.js';
|
|
4
|
+
|
|
5
|
+
const MAX_VALUE_DEPTH = 10;
|
|
6
|
+
const MAX_PROTOTYPE_DEPTH = 10;
|
|
7
|
+
const MAX_STRING_LEN = 16_384;
|
|
8
|
+
const MAX_OBJECT_KEYS = 50;
|
|
9
|
+
const MAX_OBJECT_KEY_LEN = 256;
|
|
10
|
+
const MAX_ARRAY_SIZE = 50;
|
|
11
|
+
const MAX_SERIALIZED_VALUES = 1000;
|
|
12
|
+
const MAX_BIGINT = 10n ** BigInt( MAX_STRING_LEN - 1 ); // room for sign + trailing "n"
|
|
13
|
+
const GLOBAL_IGNORED_KEYS = [
|
|
14
|
+
'__proto__',
|
|
15
|
+
// Raw Temporal wire failure; duplicates normalized causes/details and may contain encoded payloads.
|
|
16
|
+
'failure',
|
|
17
|
+
// Raw failure retained by Nexus for lossless round-tripping; duplicates surfaced diagnostics and internal payloads.
|
|
18
|
+
'originalFailure',
|
|
19
|
+
// Protobufjs reflection metadata; points into a large cyclic schema graph with no diagnostic value.
|
|
20
|
+
'$type',
|
|
21
|
+
// HTTP request/response
|
|
22
|
+
/^request$/i,
|
|
23
|
+
/^request_?body$/i,
|
|
24
|
+
/^request_?body_?values$/i,
|
|
25
|
+
/^response$/i,
|
|
26
|
+
// Headers
|
|
27
|
+
/^headers$/i,
|
|
28
|
+
/^request_?headers$/i,
|
|
29
|
+
/^response_?headers$/i,
|
|
30
|
+
// Credentials
|
|
31
|
+
/authorization$/i,
|
|
32
|
+
/authentication$/i,
|
|
33
|
+
/cookie$/i,
|
|
34
|
+
/key$/i,
|
|
35
|
+
/token$/i,
|
|
36
|
+
/password$/i,
|
|
37
|
+
/secret$/i,
|
|
38
|
+
/credentials?$/i
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const Marker = {
|
|
42
|
+
BigIntTooLarge: '[BigInt too large]',
|
|
43
|
+
Circular: '[Circular Reference]',
|
|
44
|
+
MaxDepth: '[Maximum Depth Reached]',
|
|
45
|
+
MaxKeys: '[Maximum Object Properties Reached]',
|
|
46
|
+
MaxSelfCalls: '[Serialization Limit Reached]',
|
|
47
|
+
Unserializable: '[Unserializable]',
|
|
48
|
+
InvalidDate: '[Invalid Date]',
|
|
49
|
+
TruncatedArray: '... [:v more items omitted]',
|
|
50
|
+
TruncatedString: '... [:v more characters omitted]'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const truncateString = ( v, max = MAX_STRING_LEN ) =>
|
|
54
|
+
v.length <= max ? v : ( v.slice( 0, max ) + Marker.TruncatedString.replace( ':v', v.length - max ) );
|
|
55
|
+
|
|
56
|
+
export const truncateArray = ( v, max = MAX_ARRAY_SIZE ) =>
|
|
57
|
+
v.length <= max ? v : v.slice( 0, max ).concat( Marker.TruncatedArray.replace( ':v', v.length - max ) );
|
|
58
|
+
|
|
59
|
+
/** Converts a prototype chain to a max sized array */
|
|
60
|
+
export const flattenPrototypeChain = ( target, depth = 0, result = [], maxDepth = MAX_PROTOTYPE_DEPTH ) =>
|
|
61
|
+
!target || depth >= maxDepth ? result :
|
|
62
|
+
flattenPrototypeChain( Object.getPrototypeOf( target ), depth + 1, result.concat( target ), maxDepth );
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Define the best "name" for an Error object
|
|
66
|
+
* Rules: Assigned .name property > inherited .name if not "Error" > constructor.name
|
|
67
|
+
*/
|
|
68
|
+
export const resolveErrorName = target => {
|
|
69
|
+
// if its not error, don't return anything
|
|
70
|
+
if ( !( target instanceof Error ) ) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const name = tryOrUndefined( () => target.name );
|
|
75
|
+
// if name is an explicit property return it
|
|
76
|
+
if ( Object.hasOwn( target, 'name' ) ) {
|
|
77
|
+
return name; // eslint-disable-line consistent-return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// if not explicit (resolves from parent) check if it not a default prop
|
|
81
|
+
if ( name !== undefined && name !== 'Error' ) {
|
|
82
|
+
return name; // eslint-disable-line consistent-return
|
|
83
|
+
}
|
|
84
|
+
// fallback to the constructor name, or undefined
|
|
85
|
+
return target.constructor?.name; // eslint-disable-line consistent-return
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Check is target is in ancestor chain */
|
|
89
|
+
const isAncestor = ( ancestor, target ) =>
|
|
90
|
+
ancestor !== null && ( ancestor.value === target || isAncestor( ancestor.ancestor, target ) );
|
|
91
|
+
|
|
92
|
+
class GlobalCounter {
|
|
93
|
+
value = 0;
|
|
94
|
+
count() {
|
|
95
|
+
this.value++;
|
|
96
|
+
return this;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Recursive serialize a value, recursion comes for Array-like values and objects */
|
|
101
|
+
const serializeValue = ( target, options, state = { depth: 0, seen: new GlobalCounter(), ancestor: null } ) => {
|
|
102
|
+
// If the same object was already seen in this branch, it is a circular reference
|
|
103
|
+
if ( isAncestor( state.ancestor, target ) ) {
|
|
104
|
+
return Marker.Circular;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Reached the maximum times the function can self call
|
|
108
|
+
if ( state.seen.value >= MAX_SERIALIZED_VALUES ) {
|
|
109
|
+
return Marker.MaxSelfCalls;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Depth control
|
|
113
|
+
if ( state.depth >= MAX_VALUE_DEPTH ) {
|
|
114
|
+
return Marker.MaxDepth;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const nextState = {
|
|
118
|
+
depth: state.depth + 1,
|
|
119
|
+
seen: state.seen.count(),
|
|
120
|
+
ancestor: { value: target, ancestor: state.ancestor }
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
if ( target instanceof Date ) {
|
|
125
|
+
return Number.isNaN( target.getTime() ) ? Marker.InvalidDate : target.toISOString();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if ( target instanceof RegExp ) {
|
|
129
|
+
return target.toString();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if ( typeof target === 'bigint' ) {
|
|
133
|
+
return ( target >= MAX_BIGINT || target <= -MAX_BIGINT ) ? Marker.BigIntTooLarge : `${target}n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ( typeof target === 'symbol' ) {
|
|
137
|
+
return truncateString( target.toString() );
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ( typeof target === 'function' ) {
|
|
141
|
+
return `[Function: ${target.name || '(anonymous)'}]`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Buffers, typed arrays, and raw ArrayBuffers
|
|
145
|
+
if ( ArrayBuffer.isView( target ) || target instanceof ArrayBuffer ) {
|
|
146
|
+
const sizeKey = target instanceof ArrayBuffer || target instanceof DataView ? 'byteLength' : 'length';
|
|
147
|
+
return `${target.constructor.name}(${target[sizeKey]})`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ( typeof target === 'string' ) {
|
|
151
|
+
return truncateString( isUrl( target ) ? redactUrl( target ) : target );
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Other primitives are returned as they are
|
|
155
|
+
if ( typeof target !== 'object' || target === null ) {
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if ( target instanceof URL ) {
|
|
160
|
+
return truncateString( redactUrl( target ) );
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Maps/Sets use a helper function
|
|
164
|
+
if ( [ Map, Set ].some( I => target instanceof I ) ) {
|
|
165
|
+
const iterator = target instanceof Set ? target.values() : target.entries();
|
|
166
|
+
const values = iterator.take( MAX_ARRAY_SIZE ).toArray().map( v => serializeValue( v, options, nextState ) );
|
|
167
|
+
return values.concat( target.size > MAX_ARRAY_SIZE ? Marker.TruncatedArray.replace( ':v', target.size - MAX_ARRAY_SIZE ) : [] );
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Array as also recursively serialized
|
|
171
|
+
if ( Array.isArray( target ) ) {
|
|
172
|
+
return truncateArray( target ).map( value => serializeValue( value, options, nextState ) );
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Objects
|
|
176
|
+
const prototypes = flattenPrototypeChain( target );
|
|
177
|
+
const receiver = target;
|
|
178
|
+
|
|
179
|
+
const props = prototypes.reduce( ( projection, proto ) => {
|
|
180
|
+
if ( Object.keys( projection ).length > MAX_OBJECT_KEYS ) {
|
|
181
|
+
return projection;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const keys = Object.getOwnPropertyNames( proto );
|
|
185
|
+
for ( const key of keys ) {
|
|
186
|
+
if ( options.ignoredKeys.some( e => e.test ? e.test( key ) : e === key ) ) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sanitizedKey = truncateString( key, MAX_OBJECT_KEY_LEN );
|
|
191
|
+
if ( Object.hasOwn( projection, sanitizedKey ) ) { // do not overwrite similar keys
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const value = tryOrUndefined( () => Reflect.get( proto, key, receiver ) );
|
|
196
|
+
if ( value === undefined || typeof value === 'function' ) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// after confirming the key is valid, check if it would break the limit
|
|
201
|
+
if ( Object.keys( projection ).length === MAX_OBJECT_KEYS ) {
|
|
202
|
+
delete projection['...']; // make sure this is the last added property
|
|
203
|
+
return Object.assign( projection, { ['...']: Marker.MaxKeys } );
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
projection[sanitizedKey] = serializeValue( value, options, nextState );
|
|
207
|
+
};
|
|
208
|
+
return projection;
|
|
209
|
+
}, {} );
|
|
210
|
+
|
|
211
|
+
// name resolver
|
|
212
|
+
if ( !options.ignoredKeys.includes( 'name' ) && target instanceof Error ) {
|
|
213
|
+
const name = resolveErrorName( target );
|
|
214
|
+
if ( name !== undefined ) {
|
|
215
|
+
props.name = name;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return props;
|
|
220
|
+
} catch {
|
|
221
|
+
return Marker.Unserializable;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Converts an error-like value and its inherited properties into a plain object.
|
|
227
|
+
* Serializes recursively, including values inside Arrays, maps and sets.
|
|
228
|
+
* Has depth, prototype depth, max string size, max array/map/set size, max object props count limits.
|
|
229
|
+
*
|
|
230
|
+
* @param {*} target - Value to serialize
|
|
231
|
+
* @param {object} [options] - Serialization options
|
|
232
|
+
* @param {array} [options.dropKeys=[]] - Keys to exclude from the serialization (including recursion)
|
|
233
|
+
* @returns {*} Serialized value
|
|
234
|
+
*/
|
|
235
|
+
export const serializeError = ( target, { dropKeys } = {} ) =>
|
|
236
|
+
serializeValue( target, { ignoredKeys: GLOBAL_IGNORED_KEYS.concat( dropKeys ?? [] ) } );
|