@outputai/core 0.14.0 → 0.14.1-next.1ec4416.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/dist/credentials/credentials.d.ts +6 -0
- package/dist/credentials/credentials.js +87 -0
- package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
- package/dist/credentials/encrypted_yaml_provider.js +77 -0
- package/dist/credentials/encryption.d.ts +3 -0
- package/dist/credentials/encryption.js +6 -0
- package/dist/credentials/errors.d.ts +13 -0
- package/dist/credentials/errors.js +39 -0
- package/dist/credentials/index.d.ts +6 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/init.d.ts +1 -0
- package/dist/credentials/init.js +7 -0
- package/dist/credentials/paths.d.ts +7 -0
- package/dist/credentials/paths.js +14 -0
- package/dist/credentials/provider_registry.d.ts +3 -0
- package/dist/credentials/provider_registry.js +6 -0
- package/dist/credentials/types.d.ts +12 -0
- package/dist/credentials/types.js +1 -0
- package/package.json +25 -14
- package/src/async_storage.d.ts +4 -0
- package/src/consts.js +0 -2
- package/src/helpers/object.d.ts +2 -0
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.js +0 -3
- package/src/logger/index.d.ts +4 -0
- package/src/worker/index.js +3 -1
- package/bin/healthcheck.spec.js +0 -90
- package/src/bus.spec.js +0 -196
- package/src/errors.spec.js +0 -19
- package/src/helpers/activity_options.spec.js +0 -55
- package/src/helpers/component.spec.js +0 -54
- package/src/helpers/error_serializer.spec.js +0 -479
- package/src/helpers/errors.spec.js +0 -189
- package/src/helpers/fetch.spec.js +0 -292
- package/src/helpers/function.spec.js +0 -24
- package/src/helpers/object.spec.js +0 -425
- package/src/helpers/promise.spec.js +0 -62
- package/src/helpers/redact.spec.js +0 -66
- package/src/helpers/string.spec.js +0 -99
- package/src/helpers/temporal_context.spec.ts +0 -83
- package/src/helpers/trace_info.spec.js +0 -45
- package/src/helpers/workflow_context.spec.js +0 -46
- package/src/hooks/index.spec.js +0 -265
- package/src/hooks/pending_hooks.spec.js +0 -62
- package/src/interface/evaluator.spec.js +0 -685
- package/src/interface/logger.spec.js +0 -172
- package/src/interface/step.spec.js +0 -128
- package/src/interface/validations/index.spec.js +0 -197
- package/src/interface/validations/schemas.spec.js +0 -229
- package/src/interface/webhook.spec.js +0 -170
- package/src/interface/workflow.spec.js +0 -710
- package/src/interface/workflow_utils.spec.js +0 -271
- package/src/interface/zod_integration.spec.js +0 -646
- package/src/internal_activities/index.spec.js +0 -186
- package/src/logger/development.spec.js +0 -87
- package/src/logger/index.spec.js +0 -27
- package/src/logger/production.spec.js +0 -71
- package/src/sdk/helpers/component_metadata.spec.js +0 -30
- package/src/sdk/helpers/objects.spec.js +0 -16
- package/src/sdk/helpers/path.spec.js +0 -102
- package/src/sdk/runtime/context.spec.js +0 -37
- package/src/sdk/runtime/events.spec.js +0 -40
- package/src/temporal/client.spec.js +0 -124
- package/src/tracing/processors/local/index.spec.js +0 -204
- package/src/tracing/processors/s3/configs.spec.js +0 -64
- package/src/tracing/processors/s3/index.spec.js +0 -185
- package/src/tracing/processors/s3/redis_client.spec.js +0 -179
- package/src/tracing/processors/s3/s3_client.spec.js +0 -68
- package/src/tracing/tools/build_trace_tree.spec.js +0 -216
- package/src/tracing/trace_engine.spec.js +0 -299
- package/src/worker/bundle.spec.js +0 -53
- package/src/worker/bundler_options.spec.js +0 -62
- package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
- package/src/worker/catalog_workflow/index.spec.js +0 -222
- package/src/worker/configs.spec.js +0 -213
- package/src/worker/configs_tuner_schema.spec.js +0 -127
- package/src/worker/connection_monitor.spec.js +0 -328
- package/src/worker/global_functions.spec.js +0 -55
- package/src/worker/index.spec.js +0 -526
- package/src/worker/interceptors/activity.spec.js +0 -369
- package/src/worker/interceptors/headers.spec.js +0 -73
- package/src/worker/interceptors/workflow.spec.js +0 -357
- package/src/worker/interruption.spec.js +0 -177
- package/src/worker/loader/activities.spec.js +0 -212
- package/src/worker/loader/hooks.spec.js +0 -64
- package/src/worker/loader/matchers.spec.js +0 -140
- package/src/worker/loader/tools.spec.js +0 -595
- package/src/worker/loader/workflows.spec.js +0 -287
- package/src/worker/log_hooks.spec.js +0 -303
- package/src/worker/shutdown.spec.js +0 -197
- package/src/worker/sinks.spec.js +0 -220
- package/src/worker/telemetry.spec.js +0 -80
- package/src/worker/temporal_logger.spec.js +0 -90
- package/src/worker/webpack_loaders/tools.spec.js +0 -314
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
- package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
- package/src/worker/worker_runner.spec.js +0 -220
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
flattenPrototypeChain,
|
|
4
|
-
isDomException,
|
|
5
|
-
resolveErrorName,
|
|
6
|
-
serializeError,
|
|
7
|
-
truncateArray,
|
|
8
|
-
truncateString
|
|
9
|
-
} from './error_serializer.js';
|
|
10
|
-
|
|
11
|
-
const redactUrlMock = vi.hoisted( () => vi.fn().mockReturnValue( '[Redacted URL]' ) );
|
|
12
|
-
const isUrlMock = vi.hoisted( () => vi.fn().mockReturnValue( false ) );
|
|
13
|
-
|
|
14
|
-
vi.mock( './redact.js', () => ( { redactUrl: redactUrlMock } ) );
|
|
15
|
-
vi.mock( './string.js', () => ( { isUrl: isUrlMock } ) );
|
|
16
|
-
|
|
17
|
-
describe( 'error serializer helpers', () => {
|
|
18
|
-
describe( 'truncateString', () => {
|
|
19
|
-
it( 'preserves values within the limit and marks omitted characters', () => {
|
|
20
|
-
expect( truncateString( 'exact', 5 ) ).toBe( 'exact' );
|
|
21
|
-
expect( truncateString( 'longer', 5 ) ).toBe( 'longe... [1 more characters omitted]' );
|
|
22
|
-
} );
|
|
23
|
-
} );
|
|
24
|
-
|
|
25
|
-
describe( 'truncateArray', () => {
|
|
26
|
-
it( 'preserves values within the limit and marks omitted items without mutating the source', () => {
|
|
27
|
-
const exact = [ 1, 2 ];
|
|
28
|
-
const long = [ 1, 2, 3 ];
|
|
29
|
-
|
|
30
|
-
expect( truncateArray( exact, 2 ) ).toBe( exact );
|
|
31
|
-
expect( truncateArray( long, 2 ) ).toEqual( [ 1, 2, '... [1 more items omitted]' ] );
|
|
32
|
-
expect( long ).toEqual( [ 1, 2, 3 ] );
|
|
33
|
-
} );
|
|
34
|
-
} );
|
|
35
|
-
|
|
36
|
-
describe( 'flattenPrototypeChain', () => {
|
|
37
|
-
it( 'returns child-first prototypes and honors the depth limit', () => {
|
|
38
|
-
const grandparent = { grandparent: true };
|
|
39
|
-
const parent = Object.create( grandparent );
|
|
40
|
-
const child = Object.create( parent );
|
|
41
|
-
|
|
42
|
-
expect( flattenPrototypeChain( child, 0, [], 2 ) ).toEqual( [ child, parent ] );
|
|
43
|
-
expect( flattenPrototypeChain( child, 0, [], 3 ) ).toEqual( [ child, parent, grandparent ] );
|
|
44
|
-
} );
|
|
45
|
-
} );
|
|
46
|
-
|
|
47
|
-
describe( 'isDomException', () => {
|
|
48
|
-
afterEach( () => {
|
|
49
|
-
vi.unstubAllGlobals();
|
|
50
|
-
} );
|
|
51
|
-
|
|
52
|
-
it( 'detects DOMExceptions, including abort reasons, and rejects lookalikes', () => {
|
|
53
|
-
expect( isDomException( new DOMException( 'aborted', 'AbortError' ) ) ).toBe( true );
|
|
54
|
-
expect( isDomException( AbortSignal.abort().reason ) ).toBe( true );
|
|
55
|
-
expect( isDomException( new Error( 'aborted' ) ) ).toBe( false );
|
|
56
|
-
expect( isDomException( { name: 'AbortError', code: 20 } ) ).toBe( false );
|
|
57
|
-
expect( isDomException( undefined ) ).toBe( false );
|
|
58
|
-
expect( isDomException( null ) ).toBe( false );
|
|
59
|
-
} );
|
|
60
|
-
|
|
61
|
-
it( 'detects DOMExceptions where the global is unavailable, as in the workflow sandbox', () => {
|
|
62
|
-
const error = new DOMException( 'aborted', 'AbortError' );
|
|
63
|
-
vi.stubGlobal( 'DOMException', undefined );
|
|
64
|
-
|
|
65
|
-
expect( isDomException( error ) ).toBe( true );
|
|
66
|
-
expect( serializeError( error, { dropKeys: [ 'stack' ] } ) ).toEqual( {
|
|
67
|
-
name: 'AbortError',
|
|
68
|
-
message: 'aborted',
|
|
69
|
-
code: 20
|
|
70
|
-
} );
|
|
71
|
-
} );
|
|
72
|
-
} );
|
|
73
|
-
|
|
74
|
-
describe( 'resolveErrorName', () => {
|
|
75
|
-
it( 'uses explicit, inherited, and constructor names in precedence order', () => {
|
|
76
|
-
class ConstructorNamedError extends Error {}
|
|
77
|
-
class InheritedNamedError extends Error {}
|
|
78
|
-
InheritedNamedError.prototype.name = 'InheritedName';
|
|
79
|
-
class ExplicitlyNamedError extends Error {
|
|
80
|
-
name = 'ExplicitName';
|
|
81
|
-
}
|
|
82
|
-
class ExplicitlyGenericError extends Error {
|
|
83
|
-
name = 'Error';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
expect( resolveErrorName( new ConstructorNamedError() ) ).toBe( 'ConstructorNamedError' );
|
|
87
|
-
expect( resolveErrorName( new InheritedNamedError() ) ).toBe( 'InheritedName' );
|
|
88
|
-
expect( resolveErrorName( new ExplicitlyNamedError() ) ).toBe( 'ExplicitName' );
|
|
89
|
-
expect( resolveErrorName( new ExplicitlyGenericError() ) ).toBe( 'Error' );
|
|
90
|
-
expect( resolveErrorName( new Error() ) ).toBe( 'Error' );
|
|
91
|
-
expect( resolveErrorName( { name: 'ObjectName' } ) ).toBeUndefined();
|
|
92
|
-
} );
|
|
93
|
-
|
|
94
|
-
it( 'returns undefined when an explicit name getter throws', () => {
|
|
95
|
-
const error = new Error();
|
|
96
|
-
Object.defineProperty( error, 'name', {
|
|
97
|
-
get: () => {
|
|
98
|
-
throw new Error( 'name unavailable' );
|
|
99
|
-
}
|
|
100
|
-
} );
|
|
101
|
-
|
|
102
|
-
expect( resolveErrorName( error ) ).toBeUndefined();
|
|
103
|
-
} );
|
|
104
|
-
} );
|
|
105
|
-
} );
|
|
106
|
-
|
|
107
|
-
describe( 'serializeError', () => {
|
|
108
|
-
it( 'serializes descriptors and skips unusable properties', () => {
|
|
109
|
-
const error = new Error( 'step failed' );
|
|
110
|
-
Object.defineProperties( error, {
|
|
111
|
-
code: { value: 'ESTEP' },
|
|
112
|
-
hidden: { value: 'available for debugging' },
|
|
113
|
-
computed: { get: () => `computed from ${error.message}` },
|
|
114
|
-
broken: {
|
|
115
|
-
get: () => {
|
|
116
|
-
throw new Error( 'getter failed' );
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
callback: { value: () => 'not serializable' },
|
|
120
|
-
skipped: { value: undefined },
|
|
121
|
-
symbol: { value: Symbol( 'debug' ) },
|
|
122
|
-
__proto__: { value: { unsafe: true } }
|
|
123
|
-
} );
|
|
124
|
-
error.nullable = null;
|
|
125
|
-
|
|
126
|
-
const serialized = serializeError( error, { dropKeys: [ 'stack' ] } );
|
|
127
|
-
|
|
128
|
-
expect( serialized ).toEqual( {
|
|
129
|
-
name: 'Error',
|
|
130
|
-
message: 'step failed',
|
|
131
|
-
code: 'ESTEP',
|
|
132
|
-
hidden: 'available for debugging',
|
|
133
|
-
computed: 'computed from step failed',
|
|
134
|
-
nullable: null,
|
|
135
|
-
symbol: 'Symbol(debug)'
|
|
136
|
-
} );
|
|
137
|
-
expect( Object.hasOwn( serialized, '__proto__' ) ).toBe( false );
|
|
138
|
-
} );
|
|
139
|
-
|
|
140
|
-
it( 'applies custom dropped keys recursively and across prototypes', () => {
|
|
141
|
-
class DebugError extends Error {}
|
|
142
|
-
DebugError.prototype.internal = 'prototype value';
|
|
143
|
-
|
|
144
|
-
const cause = new DebugError( 'cause failed' );
|
|
145
|
-
cause.context = { internal: 'nested cause value', visible: 'cause context' };
|
|
146
|
-
const error = new DebugError( 'request failed', { cause } );
|
|
147
|
-
error.internal = 'instance value';
|
|
148
|
-
error.context = { internal: 'nested root value', visible: 'root context' };
|
|
149
|
-
|
|
150
|
-
const serialized = serializeError( error, { dropKeys: [ 'internal', 'stack' ] } );
|
|
151
|
-
|
|
152
|
-
expect( serialized ).not.toHaveProperty( 'internal' );
|
|
153
|
-
expect( serialized.context ).toEqual( { visible: 'root context' } );
|
|
154
|
-
expect( serialized.cause ).not.toHaveProperty( 'internal' );
|
|
155
|
-
expect( serialized.cause.context ).toEqual( { visible: 'cause context' } );
|
|
156
|
-
} );
|
|
157
|
-
|
|
158
|
-
it( 'merges prototype properties child-first without invoking overridden getters', () => {
|
|
159
|
-
const overriddenGetter = vi.fn( () => 'base' );
|
|
160
|
-
class BaseFailure extends Error {}
|
|
161
|
-
Object.defineProperties( BaseFailure.prototype, {
|
|
162
|
-
baseProperty: { value: 'base' },
|
|
163
|
-
overridden: { get: overriddenGetter }
|
|
164
|
-
} );
|
|
165
|
-
class RequestFailure extends BaseFailure {}
|
|
166
|
-
RequestFailure.prototype.requestProperty = 'request';
|
|
167
|
-
|
|
168
|
-
const error = new RequestFailure( 'request failed' );
|
|
169
|
-
Object.defineProperty( error, 'overridden', { value: 'instance' } );
|
|
170
|
-
|
|
171
|
-
expect( serializeError( error, { dropKeys: [ 'stack' ] } ) ).toEqual( {
|
|
172
|
-
name: 'RequestFailure',
|
|
173
|
-
message: 'request failed',
|
|
174
|
-
overridden: 'instance',
|
|
175
|
-
requestProperty: 'request',
|
|
176
|
-
baseProperty: 'base'
|
|
177
|
-
} );
|
|
178
|
-
expect( overriddenGetter ).not.toHaveBeenCalled();
|
|
179
|
-
} );
|
|
180
|
-
|
|
181
|
-
it( 'recursively serializes causes, nested objects, and arrays', () => {
|
|
182
|
-
const databaseError = new Error( 'database unavailable' );
|
|
183
|
-
databaseError.code = 'ECONNREFUSED';
|
|
184
|
-
const serviceError = new Error( 'service failed', { cause: databaseError } );
|
|
185
|
-
serviceError.context = {
|
|
186
|
-
attempts: [
|
|
187
|
-
{ number: 1, metadata: { retryable: true } },
|
|
188
|
-
{ number: 2, metadata: null }
|
|
189
|
-
]
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
expect( serializeError( serviceError, { dropKeys: [ 'stack' ] } ) ).toEqual( {
|
|
193
|
-
name: 'Error',
|
|
194
|
-
message: 'service failed',
|
|
195
|
-
cause: {
|
|
196
|
-
name: 'Error',
|
|
197
|
-
message: 'database unavailable',
|
|
198
|
-
code: 'ECONNREFUSED'
|
|
199
|
-
},
|
|
200
|
-
context: {
|
|
201
|
-
attempts: [
|
|
202
|
-
{ number: 1, metadata: { retryable: true } },
|
|
203
|
-
{ number: 2, metadata: null }
|
|
204
|
-
]
|
|
205
|
-
}
|
|
206
|
-
} );
|
|
207
|
-
} );
|
|
208
|
-
|
|
209
|
-
it( 'returns primitives as-is', () => {
|
|
210
|
-
expect( serializeError( undefined ) ).toBeUndefined();
|
|
211
|
-
expect( serializeError( null ) ).toBeNull();
|
|
212
|
-
expect( serializeError( 42 ) ).toBe( 42 );
|
|
213
|
-
expect( serializeError( true ) ).toBe( true );
|
|
214
|
-
} );
|
|
215
|
-
|
|
216
|
-
it( 'serializes Date values as ISO strings and marks invalid dates', () => {
|
|
217
|
-
expect( serializeError( new Date( '2026-07-22T16:30:00.000Z' ) ) ).toBe( '2026-07-22T16:30:00.000Z' );
|
|
218
|
-
expect( serializeError( new Date( 'foo' ) ) ).toBe( '[Invalid Date]' );
|
|
219
|
-
} );
|
|
220
|
-
|
|
221
|
-
it( 'serializes RegExp values with toString', () => {
|
|
222
|
-
expect( serializeError( /request-\d+/gi ) ).toBe( '/request-\\d+/gi' );
|
|
223
|
-
} );
|
|
224
|
-
|
|
225
|
-
it( 'serializes DOMException values without the legacy prototype constants', () => {
|
|
226
|
-
const error = new DOMException( 'Cancelled by caller', 'AbortError' );
|
|
227
|
-
|
|
228
|
-
expect( serializeError( error ) ).toEqual( {
|
|
229
|
-
name: 'AbortError',
|
|
230
|
-
message: 'Cancelled by caller',
|
|
231
|
-
code: 20,
|
|
232
|
-
stack: expect.stringContaining( 'AbortError: Cancelled by caller' )
|
|
233
|
-
} );
|
|
234
|
-
expect( serializeError( error, { dropKeys: [ /stack/ ] } ) ).toEqual( {
|
|
235
|
-
name: 'AbortError',
|
|
236
|
-
message: 'Cancelled by caller',
|
|
237
|
-
code: 20
|
|
238
|
-
} );
|
|
239
|
-
} );
|
|
240
|
-
|
|
241
|
-
it( 'marks circular DOMException fields and drops properties outside the allowed list', () => {
|
|
242
|
-
const error = new DOMException( 'aborted', 'AbortError' );
|
|
243
|
-
Object.defineProperty( error, 'message', { value: error, enumerable: true } );
|
|
244
|
-
error.attempt = 2;
|
|
245
|
-
|
|
246
|
-
expect( serializeError( { reason: error, sibling: 'preserved' }, { dropKeys: [ 'stack' ] } ) ).toEqual( {
|
|
247
|
-
reason: { name: 'AbortError', message: '[Circular Reference]', code: 20 },
|
|
248
|
-
sibling: 'preserved'
|
|
249
|
-
} );
|
|
250
|
-
} );
|
|
251
|
-
|
|
252
|
-
it( 'serializes bigint values with an n suffix', () => {
|
|
253
|
-
expect( serializeError( 42n ) ).toBe( '42n' );
|
|
254
|
-
expect( serializeError( -42n ) ).toBe( '-42n' );
|
|
255
|
-
} );
|
|
256
|
-
|
|
257
|
-
it( 'marks bigints that would exceed the string length budget', () => {
|
|
258
|
-
const maxDigits = 16_383; // MAX_STRING_LEN - 1
|
|
259
|
-
const tooLarge = 10n ** BigInt( maxDigits );
|
|
260
|
-
|
|
261
|
-
expect( serializeError( tooLarge - 1n ) ).toBe( `${tooLarge - 1n}n` );
|
|
262
|
-
expect( serializeError( tooLarge ) ).toBe( '[BigInt too large]' );
|
|
263
|
-
expect( serializeError( -tooLarge ) ).toBe( '[BigInt too large]' );
|
|
264
|
-
expect( serializeError( 10n ** 20_000n ) ).toBe( '[BigInt too large]' );
|
|
265
|
-
} );
|
|
266
|
-
|
|
267
|
-
it( 'serializes symbol values', () => {
|
|
268
|
-
expect( serializeError( Symbol( 'request' ) ) ).toBe( 'Symbol(request)' );
|
|
269
|
-
} );
|
|
270
|
-
|
|
271
|
-
it( 'serializes functions by name', () => {
|
|
272
|
-
function namedFunction() {}
|
|
273
|
-
expect( serializeError( namedFunction ) ).toBe( '[Function: namedFunction]' );
|
|
274
|
-
expect( serializeError( () => undefined ) ).toBe( '[Function: (anonymous)]' );
|
|
275
|
-
} );
|
|
276
|
-
|
|
277
|
-
it( 'serializes ArrayBuffers and views as type and size without dumping contents', () => {
|
|
278
|
-
expect( serializeError( new Uint16Array( [ 1, 2 ] ) ) ).toBe( 'Uint16Array(2)' );
|
|
279
|
-
expect( serializeError( Buffer.from( [ 1, 2 ] ) ) ).toBe( 'Buffer(2)' );
|
|
280
|
-
expect( serializeError( new DataView( new ArrayBuffer( 8 ) ) ) ).toBe( 'DataView(8)' );
|
|
281
|
-
expect( serializeError( new ArrayBuffer( 16 ) ) ).toBe( 'ArrayBuffer(16)' );
|
|
282
|
-
} );
|
|
283
|
-
|
|
284
|
-
it( 'redacts URL strings and URL instances with redactUrl', () => {
|
|
285
|
-
const stringUrl = 'https://user:password@example.com/models?model=gpt-5&token=secret#debug';
|
|
286
|
-
const url = new URL( 'ftp://user:password@example.com/file.txt?download=true#details' );
|
|
287
|
-
redactUrlMock.mockClear();
|
|
288
|
-
isUrlMock.mockClear();
|
|
289
|
-
isUrlMock.mockReturnValueOnce( true );
|
|
290
|
-
|
|
291
|
-
expect( serializeError( {
|
|
292
|
-
stringUrl,
|
|
293
|
-
url
|
|
294
|
-
} ) ).toEqual( {
|
|
295
|
-
stringUrl: '[Redacted URL]',
|
|
296
|
-
url: '[Redacted URL]'
|
|
297
|
-
} );
|
|
298
|
-
expect( isUrlMock ).toHaveBeenCalledOnce();
|
|
299
|
-
expect( isUrlMock ).toHaveBeenCalledWith( stringUrl );
|
|
300
|
-
expect( redactUrlMock ).toHaveBeenCalledTimes( 2 );
|
|
301
|
-
expect( redactUrlMock ).toHaveBeenNthCalledWith( 1, stringUrl );
|
|
302
|
-
expect( redactUrlMock ).toHaveBeenNthCalledWith( 2, url );
|
|
303
|
-
} );
|
|
304
|
-
|
|
305
|
-
it( 'truncates string values and property names', () => {
|
|
306
|
-
const longValue = 'v'.repeat( 16_385 );
|
|
307
|
-
const sharedKeyPrefix = 'k'.repeat( 256 );
|
|
308
|
-
|
|
309
|
-
expect( serializeError( {
|
|
310
|
-
longValue,
|
|
311
|
-
[`${sharedKeyPrefix}a`]: 'first',
|
|
312
|
-
[`${sharedKeyPrefix}b`]: 'second'
|
|
313
|
-
} ) ).toEqual( {
|
|
314
|
-
longValue: `${'v'.repeat( 16_384 )}... [1 more characters omitted]`,
|
|
315
|
-
[`${sharedKeyPrefix}... [1 more characters omitted]`]: 'first'
|
|
316
|
-
} );
|
|
317
|
-
} );
|
|
318
|
-
|
|
319
|
-
it( 'limits arrays, maps, and sets while recursively serializing their values', () => {
|
|
320
|
-
const exactArray = Array.from( { length: 50 }, ( _, index ) => index );
|
|
321
|
-
const longArray = Array.from( { length: 52 }, ( _, index ) => index );
|
|
322
|
-
const longMap = new Map( Array.from( { length: 51 }, ( _, index ) => [ `key-${index}`, BigInt( index ) ] ) );
|
|
323
|
-
const longSet = new Set( Array.from( { length: 52 }, ( _, index ) => BigInt( index ) ) );
|
|
324
|
-
|
|
325
|
-
expect( serializeError( exactArray ) ).toEqual( exactArray );
|
|
326
|
-
expect( serializeError( longArray ) ).toEqual( [ ...exactArray, '... [2 more items omitted]' ] );
|
|
327
|
-
|
|
328
|
-
const serializedMap = serializeError( longMap );
|
|
329
|
-
expect( serializedMap ).toHaveLength( 51 );
|
|
330
|
-
expect( serializedMap[0] ).toEqual( [ 'key-0', '0n' ] );
|
|
331
|
-
expect( serializedMap.at( -1 ) ).toBe( '... [1 more items omitted]' );
|
|
332
|
-
|
|
333
|
-
const serializedSet = serializeError( longSet );
|
|
334
|
-
expect( serializedSet ).toHaveLength( 51 );
|
|
335
|
-
expect( serializedSet[0] ).toBe( '0n' );
|
|
336
|
-
expect( serializedSet.at( -1 ) ).toBe( '... [2 more items omitted]' );
|
|
337
|
-
} );
|
|
338
|
-
|
|
339
|
-
it( 'globally limits object properties while prioritizing own properties', () => {
|
|
340
|
-
const exactProperties = Object.fromEntries( Array.from( { length: 50 }, ( _, index ) => [ `property-${index}`, index ] ) );
|
|
341
|
-
expect( serializeError( exactProperties ) ).toEqual( exactProperties );
|
|
342
|
-
|
|
343
|
-
const prototype = { inherited: 'omitted' };
|
|
344
|
-
const instance = Object.assign(
|
|
345
|
-
Object.create( prototype ),
|
|
346
|
-
Object.fromEntries( Array.from( { length: 50 }, ( _, index ) => [ `own-${index}`, index ] ) )
|
|
347
|
-
);
|
|
348
|
-
const serialized = serializeError( instance );
|
|
349
|
-
|
|
350
|
-
expect( Object.keys( serialized ) ).toHaveLength( 51 );
|
|
351
|
-
expect( serialized['own-49'] ).toBe( 49 );
|
|
352
|
-
expect( serialized ).not.toHaveProperty( 'inherited' );
|
|
353
|
-
expect( serialized['...'] ).toBe( '[Maximum Object Properties Reached]' );
|
|
354
|
-
} );
|
|
355
|
-
|
|
356
|
-
it( 'serializes the resolved error name instead of returning it raw', () => {
|
|
357
|
-
const circular = new Error( 'boom' );
|
|
358
|
-
circular.name = circular;
|
|
359
|
-
const oversized = new Error( 'boom' );
|
|
360
|
-
oversized.name = 'n'.repeat( 16_385 );
|
|
361
|
-
|
|
362
|
-
expect( serializeError( circular, { dropKeys: [ 'stack' ] } ).name ).toBe( '[Circular Reference]' );
|
|
363
|
-
expect( serializeError( oversized, { dropKeys: [ 'stack' ] } ).name )
|
|
364
|
-
.toBe( `${'n'.repeat( 16_384 )}... [1 more characters omitted]` );
|
|
365
|
-
} );
|
|
366
|
-
|
|
367
|
-
it( 'retains error identity when properties reach the object limit', () => {
|
|
368
|
-
class DetailedError extends Error {}
|
|
369
|
-
const error = new DetailedError( 'failed' );
|
|
370
|
-
Object.assign( error, Object.fromEntries( Array.from( { length: 50 }, ( _, index ) => [ `field-${index}`, index ] ) ) );
|
|
371
|
-
|
|
372
|
-
const serialized = serializeError( error, { dropKeys: [ 'stack' ] } );
|
|
373
|
-
|
|
374
|
-
expect( serialized.name ).toBe( 'DetailedError' );
|
|
375
|
-
expect( serialized.message ).toBe( 'failed' );
|
|
376
|
-
expect( serialized['...'] ).toBe( '[Maximum Object Properties Reached]' );
|
|
377
|
-
} );
|
|
378
|
-
|
|
379
|
-
it( 'recursively excludes sensitive and internal properties while retaining similarly named diagnostics', () => {
|
|
380
|
-
const value = {
|
|
381
|
-
failure: { internal: 'Temporal protobuf' },
|
|
382
|
-
originalFailure: { details: 'Nexus failure' },
|
|
383
|
-
$type: { parent: 'protobuf reflection graph' },
|
|
384
|
-
request: { body: 'private request' },
|
|
385
|
-
response: { body: 'private response' },
|
|
386
|
-
headers: { Authorization: 'Bearer secret' },
|
|
387
|
-
Authorization: 'Bearer secret',
|
|
388
|
-
RequestBodyValues: { prompt: 'private prompt' },
|
|
389
|
-
RESPONSE_HEADERS: { Cookie: 'private cookie' },
|
|
390
|
-
APIKey: 'private key',
|
|
391
|
-
AccessToken: 'private token',
|
|
392
|
-
PASSWORD: 'private password',
|
|
393
|
-
Credential: 'private credential',
|
|
394
|
-
keyboard: 'preserved',
|
|
395
|
-
tokenCount: 42,
|
|
396
|
-
passwordPolicy: 'preserved',
|
|
397
|
-
secretCount: 1,
|
|
398
|
-
nested: {
|
|
399
|
-
Cookie: 'private cookie',
|
|
400
|
-
code: 'EFAILED'
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
expect( serializeError( value ) ).toEqual( {
|
|
405
|
-
keyboard: 'preserved',
|
|
406
|
-
tokenCount: 42,
|
|
407
|
-
passwordPolicy: 'preserved',
|
|
408
|
-
secretCount: 1,
|
|
409
|
-
nested: { code: 'EFAILED' }
|
|
410
|
-
} );
|
|
411
|
-
} );
|
|
412
|
-
|
|
413
|
-
it( 'includes stacks by default and supports dropping them recursively', () => {
|
|
414
|
-
const cause = new Error( 'database unavailable' );
|
|
415
|
-
const error = new Error( 'service failed', { cause } );
|
|
416
|
-
|
|
417
|
-
const withStacks = serializeError( error );
|
|
418
|
-
const withoutStacks = serializeError( error, { dropKeys: [ 'stack' ] } );
|
|
419
|
-
|
|
420
|
-
expect( withStacks.stack ).toContain( 'Error: service failed' );
|
|
421
|
-
expect( withStacks.cause.stack ).toContain( 'Error: database unavailable' );
|
|
422
|
-
expect( withoutStacks ).not.toHaveProperty( 'stack' );
|
|
423
|
-
expect( withoutStacks.cause ).not.toHaveProperty( 'stack' );
|
|
424
|
-
} );
|
|
425
|
-
|
|
426
|
-
it( 'marks circular references but serializes shared sibling values independently', () => {
|
|
427
|
-
const shared = { code: 'ESHARED' };
|
|
428
|
-
const objectCycle = {};
|
|
429
|
-
objectCycle.self = objectCycle;
|
|
430
|
-
const arrayCycle = [];
|
|
431
|
-
arrayCycle.push( arrayCycle );
|
|
432
|
-
const mapCycle = new Map();
|
|
433
|
-
mapCycle.set( 'self', mapCycle );
|
|
434
|
-
const setCycle = new Set();
|
|
435
|
-
setCycle.add( setCycle );
|
|
436
|
-
|
|
437
|
-
expect( serializeError( {
|
|
438
|
-
objectCycle,
|
|
439
|
-
arrayCycle,
|
|
440
|
-
mapCycle,
|
|
441
|
-
setCycle,
|
|
442
|
-
first: shared,
|
|
443
|
-
second: shared
|
|
444
|
-
} ) ).toEqual( {
|
|
445
|
-
objectCycle: { self: '[Circular Reference]' },
|
|
446
|
-
arrayCycle: [ '[Circular Reference]' ],
|
|
447
|
-
mapCycle: [ [ 'self', '[Circular Reference]' ] ],
|
|
448
|
-
setCycle: [ '[Circular Reference]' ],
|
|
449
|
-
first: { code: 'ESHARED' },
|
|
450
|
-
second: { code: 'ESHARED' }
|
|
451
|
-
} );
|
|
452
|
-
} );
|
|
453
|
-
|
|
454
|
-
it( 'marks values beyond the maximum recursive depth', () => {
|
|
455
|
-
const createNestedValue = depth => depth === 0 ? {} : { child: createNestedValue( depth - 1 ) };
|
|
456
|
-
const root = createNestedValue( 10 );
|
|
457
|
-
|
|
458
|
-
expect(
|
|
459
|
-
Array.from( { length: 9 } ).reduce( value => value.child, serializeError( root ) ).child
|
|
460
|
-
).toBe( '[Maximum Depth Reached]' );
|
|
461
|
-
} );
|
|
462
|
-
|
|
463
|
-
it( 'marks values after the total serialization limit is reached', () => {
|
|
464
|
-
const value = Array.from(
|
|
465
|
-
{ length: 50 },
|
|
466
|
-
() => Array.from( { length: 50 }, ( _, index ) => index )
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
expect( JSON.stringify( serializeError( value ) ) ).toContain( '[Serialization Limit Reached]' );
|
|
470
|
-
} );
|
|
471
|
-
|
|
472
|
-
it( 'returns an unserializable marker for exotic values that throw during reflection', () => {
|
|
473
|
-
const { proxy, revoke } = Proxy.revocable( {}, {} );
|
|
474
|
-
revoke();
|
|
475
|
-
|
|
476
|
-
expect( serializeError( proxy ) ).toBe( '[Unserializable]' );
|
|
477
|
-
expect( serializeError( { nested: proxy } ) ).toEqual( { nested: '[Unserializable]' } );
|
|
478
|
-
} );
|
|
479
|
-
} );
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { inheritsFromAnyNamedType, rehydrateError } from './errors.js';
|
|
3
|
-
|
|
4
|
-
describe( 'inheritsFromAnyNamedType', () => {
|
|
5
|
-
class BaseFailure extends Error {}
|
|
6
|
-
class DomainFailure extends BaseFailure {}
|
|
7
|
-
class RequestFailure extends DomainFailure {}
|
|
8
|
-
class UnrelatedFailure extends Error {}
|
|
9
|
-
|
|
10
|
-
it( 'matches exact, parent, and grandparent constructor names', () => {
|
|
11
|
-
const error = new RequestFailure( 'request failed' );
|
|
12
|
-
|
|
13
|
-
expect( inheritsFromAnyNamedType( error, [ 'RequestFailure' ] ) ).toBe( true );
|
|
14
|
-
expect( inheritsFromAnyNamedType( error, [ 'DomainFailure' ] ) ).toBe( true );
|
|
15
|
-
expect( inheritsFromAnyNamedType( error, [ 'BaseFailure' ] ) ).toBe( true );
|
|
16
|
-
expect( inheritsFromAnyNamedType( error, [ 'OtherFailure', 'BaseFailure' ] ) ).toBe( true );
|
|
17
|
-
} );
|
|
18
|
-
|
|
19
|
-
it( 'does not match unrelated or descendant constructor names', () => {
|
|
20
|
-
expect( inheritsFromAnyNamedType( new RequestFailure(), [ 'UnrelatedFailure' ] ) ).toBe( false );
|
|
21
|
-
expect( inheritsFromAnyNamedType( new BaseFailure(), [ 'RequestFailure' ] ) ).toBe( false );
|
|
22
|
-
expect( inheritsFromAnyNamedType( new UnrelatedFailure(), [ 'BaseFailure' ] ) ).toBe( false );
|
|
23
|
-
} );
|
|
24
|
-
|
|
25
|
-
it( 'ignores a spoofed own constructor property', () => {
|
|
26
|
-
const error = new RequestFailure();
|
|
27
|
-
Object.defineProperty( error, 'constructor', {
|
|
28
|
-
value: { name: 'SpoofedFailure' }
|
|
29
|
-
} );
|
|
30
|
-
|
|
31
|
-
expect( inheritsFromAnyNamedType( error, [ 'SpoofedFailure' ] ) ).toBe( false );
|
|
32
|
-
expect( inheritsFromAnyNamedType( error, [ 'RequestFailure' ] ) ).toBe( true );
|
|
33
|
-
} );
|
|
34
|
-
|
|
35
|
-
it( 'rejects empty names and invalid values without false positives', () => {
|
|
36
|
-
const unnamedConstructor = function NamedConstructor() {};
|
|
37
|
-
Object.defineProperty( unnamedConstructor, 'name', { value: '' } );
|
|
38
|
-
const value = Object.create( { constructor: unnamedConstructor } );
|
|
39
|
-
|
|
40
|
-
expect( inheritsFromAnyNamedType( value, [ '', undefined ] ) ).toBe( false );
|
|
41
|
-
expect( inheritsFromAnyNamedType( value, [] ) ).toBe( false );
|
|
42
|
-
expect( inheritsFromAnyNamedType( null, [ 'BaseFailure' ] ) ).toBe( false );
|
|
43
|
-
expect( inheritsFromAnyNamedType( 'failure', [ 'String' ] ) ).toBe( false );
|
|
44
|
-
} );
|
|
45
|
-
} );
|
|
46
|
-
|
|
47
|
-
describe( 'rehydrateError', () => {
|
|
48
|
-
it( 'returns the same Error instance when given an Error', () => {
|
|
49
|
-
const original = new Error( 'already an error' );
|
|
50
|
-
original.name = 'FatalError';
|
|
51
|
-
|
|
52
|
-
expect( rehydrateError( original ) ).toBe( original );
|
|
53
|
-
} );
|
|
54
|
-
|
|
55
|
-
it( 'rebuilds a plain serialized error with name and message', () => {
|
|
56
|
-
const result = rehydrateError( {
|
|
57
|
-
name: 'FatalError',
|
|
58
|
-
message: 'workflow failed'
|
|
59
|
-
} );
|
|
60
|
-
|
|
61
|
-
expect( result ).toBeInstanceOf( Error );
|
|
62
|
-
expect( result.name ).toBe( 'FatalError' );
|
|
63
|
-
expect( result.message ).toBe( 'workflow failed' );
|
|
64
|
-
expect( result.stack ).toBeUndefined();
|
|
65
|
-
} );
|
|
66
|
-
|
|
67
|
-
it( 'restores stack when present on the serialized object', () => {
|
|
68
|
-
const result = rehydrateError( {
|
|
69
|
-
name: 'Error',
|
|
70
|
-
message: 'boom',
|
|
71
|
-
stack: 'Error: boom\n at workflow.js:1:1'
|
|
72
|
-
} );
|
|
73
|
-
|
|
74
|
-
expect( result.stack ).toBe( 'Error: boom\n at workflow.js:1:1' );
|
|
75
|
-
} );
|
|
76
|
-
|
|
77
|
-
it( 'copies additional enumerable properties', () => {
|
|
78
|
-
const result = rehydrateError( {
|
|
79
|
-
name: 'HTTPError',
|
|
80
|
-
message: 'bad request',
|
|
81
|
-
status: 400,
|
|
82
|
-
code: 'BAD_REQUEST'
|
|
83
|
-
} );
|
|
84
|
-
|
|
85
|
-
expect( result.status ).toBe( 400 );
|
|
86
|
-
expect( result.code ).toBe( 'BAD_REQUEST' );
|
|
87
|
-
} );
|
|
88
|
-
|
|
89
|
-
it( 'recursively rehydrates a plain-object cause', () => {
|
|
90
|
-
const result = rehydrateError( {
|
|
91
|
-
name: 'ActivityFailure',
|
|
92
|
-
message: 'Activity task failed',
|
|
93
|
-
cause: {
|
|
94
|
-
name: 'ApplicationFailure',
|
|
95
|
-
message: 'root failure',
|
|
96
|
-
cause: {
|
|
97
|
-
name: 'ValidationError',
|
|
98
|
-
message: 'invalid input'
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} );
|
|
102
|
-
|
|
103
|
-
expect( result.cause ).toBeInstanceOf( Error );
|
|
104
|
-
expect( result.cause.name ).toBe( 'ApplicationFailure' );
|
|
105
|
-
expect( result.cause.message ).toBe( 'root failure' );
|
|
106
|
-
expect( result.cause.cause ).toBeInstanceOf( Error );
|
|
107
|
-
expect( result.cause.cause.name ).toBe( 'ValidationError' );
|
|
108
|
-
expect( result.cause.cause.message ).toBe( 'invalid input' );
|
|
109
|
-
} );
|
|
110
|
-
|
|
111
|
-
it( 'leaves a non-object cause unchanged', () => {
|
|
112
|
-
const result = rehydrateError( {
|
|
113
|
-
name: 'Error',
|
|
114
|
-
message: 'outer',
|
|
115
|
-
cause: 'string cause'
|
|
116
|
-
} );
|
|
117
|
-
|
|
118
|
-
expect( result.cause ).toBe( 'string cause' );
|
|
119
|
-
} );
|
|
120
|
-
|
|
121
|
-
it( 'returns an empty Error for null and undefined without throwing', () => {
|
|
122
|
-
const fromNull = rehydrateError( null );
|
|
123
|
-
const fromUndefined = rehydrateError( undefined );
|
|
124
|
-
|
|
125
|
-
expect( fromNull ).toBeInstanceOf( Error );
|
|
126
|
-
expect( fromNull.message ).toBe( '' );
|
|
127
|
-
expect( fromNull.stack ).toBeUndefined();
|
|
128
|
-
|
|
129
|
-
expect( fromUndefined ).toBeInstanceOf( Error );
|
|
130
|
-
expect( fromUndefined.message ).toBe( '' );
|
|
131
|
-
expect( fromUndefined.stack ).toBeUndefined();
|
|
132
|
-
} );
|
|
133
|
-
|
|
134
|
-
it.each( [
|
|
135
|
-
[ 'string', 'boom', 'boom' ],
|
|
136
|
-
[ 'number', 42, '42' ],
|
|
137
|
-
[ 'boolean', true, 'true' ],
|
|
138
|
-
[ 'bigint', 1n, '1' ],
|
|
139
|
-
[ 'symbol', Symbol( 'x' ), 'Symbol(x)' ],
|
|
140
|
-
[ 'named function', function named() {}, 'function named() {}' ],
|
|
141
|
-
[ 'arrow function', () => {}, '() => {}' ]
|
|
142
|
-
] )( 'stringifies a %s into Error.message', ( _label, input, message ) => {
|
|
143
|
-
const result = rehydrateError( input );
|
|
144
|
-
|
|
145
|
-
expect( result ).toBeInstanceOf( Error );
|
|
146
|
-
expect( result.message ).toBe( message );
|
|
147
|
-
expect( result.stack ).toBeUndefined();
|
|
148
|
-
} );
|
|
149
|
-
|
|
150
|
-
it( 'copies enumerable array indexes onto an empty Error', () => {
|
|
151
|
-
const result = rehydrateError( [ 'a', 'b' ] );
|
|
152
|
-
|
|
153
|
-
expect( result ).toBeInstanceOf( Error );
|
|
154
|
-
expect( result.message ).toBe( '' );
|
|
155
|
-
expect( result.stack ).toBeUndefined();
|
|
156
|
-
expect( result[0] ).toBe( 'a' );
|
|
157
|
-
expect( result[1] ).toBe( 'b' );
|
|
158
|
-
} );
|
|
159
|
-
|
|
160
|
-
it( 'returns an empty Error for RegExp and Date (no enumerable own props)', () => {
|
|
161
|
-
const fromRegexp = rehydrateError( /foo/gi );
|
|
162
|
-
const fromDate = rehydrateError( new Date( '2020-01-01T00:00:00.000Z' ) );
|
|
163
|
-
|
|
164
|
-
expect( fromRegexp ).toBeInstanceOf( Error );
|
|
165
|
-
expect( fromRegexp.message ).toBe( '' );
|
|
166
|
-
expect( fromRegexp.stack ).toBeUndefined();
|
|
167
|
-
|
|
168
|
-
expect( fromDate ).toBeInstanceOf( Error );
|
|
169
|
-
expect( fromDate.message ).toBe( '' );
|
|
170
|
-
expect( fromDate.stack ).toBeUndefined();
|
|
171
|
-
} );
|
|
172
|
-
|
|
173
|
-
it( 'copies enumerable own props from a custom class instance', () => {
|
|
174
|
-
class Widget {
|
|
175
|
-
constructor() {
|
|
176
|
-
this.x = 1;
|
|
177
|
-
this.name = 'WidgetError';
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const result = rehydrateError( new Widget() );
|
|
182
|
-
|
|
183
|
-
expect( result ).toBeInstanceOf( Error );
|
|
184
|
-
expect( result.message ).toBe( '' );
|
|
185
|
-
expect( result.stack ).toBeUndefined();
|
|
186
|
-
expect( result.x ).toBe( 1 );
|
|
187
|
-
expect( result.name ).toBe( 'WidgetError' );
|
|
188
|
-
} );
|
|
189
|
-
} );
|