@outputai/core 0.10.1-next.b7b2fbe.0 → 0.10.1-next.bf95a6f.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 +13 -7
- package/src/bus.js +58 -20
- package/src/bus.spec.js +135 -77
- package/src/consts.js +7 -3
- 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 +23 -14
- package/src/helpers/errors.spec.js +31 -75
- 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/object.js +48 -35
- package/src/helpers/object.spec.js +123 -75
- 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 +76 -78
- package/src/hooks/index.js +57 -49
- package/src/hooks/index.spec.js +104 -105
- package/src/hooks/pending_hooks.js +24 -0
- package/src/hooks/pending_hooks.spec.js +62 -0
- 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/index.js +2 -1
- 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 +64 -85
- package/src/interface/workflow.spec.js +220 -226
- package/src/interface/workflow_activity_options.js +2 -5
- package/src/interface/workflow_utils.d.ts +15 -0
- package/src/interface/workflow_utils.js +19 -0
- package/src/interface/workflow_utils.spec.js +82 -1
- package/src/sdk/helpers/objects.d.ts +24 -19
- package/src/sdk/runtime/events.d.ts +4 -6
- package/src/sdk/runtime/events.js +2 -15
- package/src/sdk/runtime/events.spec.js +14 -92
- 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/configs.js +0 -3
- package/src/worker/global_functions.js +2 -2
- package/src/worker/global_functions.spec.js +3 -3
- package/src/worker/index.js +24 -11
- package/src/worker/index.spec.js +36 -16
- package/src/worker/interceptors/activity.js +34 -21
- package/src/worker/interceptors/activity.spec.js +77 -172
- package/src/worker/interceptors/workflow.js +58 -16
- package/src/worker/interceptors/workflow.spec.js +183 -56
- package/src/worker/loader/activities.js +56 -40
- package/src/worker/loader/activities.spec.js +21 -22
- package/src/worker/loader/workflows.js +16 -3
- package/src/worker/loader/workflows.spec.js +36 -5
- package/src/worker/log_hooks.js +22 -19
- package/src/worker/log_hooks.spec.js +14 -6
- package/src/worker/sinks.js +8 -8
- package/src/worker/sinks.spec.js +9 -9
- 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/worker/webpack_loaders/consts.js +6 -0
- package/src/worker/webpack_loaders/tools.js +1 -146
- package/src/worker/webpack_loaders/tools.spec.js +0 -23
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +24 -32
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +82 -279
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +6 -7
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +57 -119
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.js +88 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +165 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +96 -29
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +110 -583
- package/src/worker/webpack_loaders/{npm_workflow_export_resolve.js → workflow_validator/npm_workflow_export_resolve.js} +1 -1
- package/src/worker/webpack_loaders/{npm_workflow_export_resolve.spec.js → workflow_validator/npm_workflow_export_resolve.spec.js} +1 -1
- 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/src/worker/proxy.js +0 -20
- package/src/worker/proxy.spec.js +0 -51
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +0 -193
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +0 -123
|
@@ -2,10 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import {
|
|
4
4
|
ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
5
|
-
|
|
6
|
-
METADATA_ACCESS_SYMBOL,
|
|
7
|
-
SHARED_STEP_PREFIX,
|
|
8
|
-
WORKFLOW_WRAPPER_VERSION_FIELD
|
|
5
|
+
INVOKE_ACTIVITY_SYMBOL
|
|
9
6
|
} from '#consts';
|
|
10
7
|
import { ValidationError } from '#errors';
|
|
11
8
|
|
|
@@ -13,11 +10,12 @@ const inWorkflowContextMock = vi.hoisted( () => vi.fn() );
|
|
|
13
10
|
const proxyActivitiesMock = vi.hoisted( () => vi.fn() );
|
|
14
11
|
const executeChildMock = vi.hoisted( () => vi.fn() );
|
|
15
12
|
const workflowInfoMock = vi.hoisted( () => vi.fn() );
|
|
13
|
+
const upsertMemoMock = vi.hoisted( () => vi.fn() );
|
|
16
14
|
const continueAsNewMock = vi.hoisted( () => vi.fn() );
|
|
17
15
|
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
16
|
+
const parseInputMock = vi.hoisted( () => vi.fn( input => input ) );
|
|
17
|
+
const parseOutputMock = vi.hoisted( () => vi.fn( output => output ) );
|
|
18
|
+
const parseInvocationOptionsMock = vi.hoisted( () => vi.fn( options => options ) );
|
|
21
19
|
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
22
20
|
const createWorkflowMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
23
21
|
|
|
@@ -29,9 +27,9 @@ vi.mock( './validations/index.js', () => {
|
|
|
29
27
|
|
|
30
28
|
constructor( ...args ) {
|
|
31
29
|
validatorConstructorMock( ...args );
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
30
|
+
this.parseInput = parseInputMock;
|
|
31
|
+
this.parseOutput = parseOutputMock;
|
|
32
|
+
this.parseInvocationOptions = parseInvocationOptionsMock;
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -50,6 +48,7 @@ vi.mock( '@temporalio/workflow', async importOriginal => {
|
|
|
50
48
|
proxyActivities: proxyActivitiesMock,
|
|
51
49
|
executeChild: executeChildMock,
|
|
52
50
|
workflowInfo: workflowInfoMock,
|
|
51
|
+
upsertMemo: upsertMemoMock,
|
|
53
52
|
continueAsNew: continueAsNewMock,
|
|
54
53
|
uuid4: () => '550e8400e29b41d4a716446655440000'
|
|
55
54
|
};
|
|
@@ -60,28 +59,24 @@ const baseWorkflowInfo = () => ( {
|
|
|
60
59
|
workflowType: 'test_wf',
|
|
61
60
|
runId: 'run-123',
|
|
62
61
|
startTime: new Date( '2025-01-01T00:00:00.000Z' ),
|
|
63
|
-
memo: {},
|
|
62
|
+
memo: { traceInfo: { workflowId: 'workflow-123' } },
|
|
64
63
|
continueAsNewSuggested: false
|
|
65
64
|
} );
|
|
66
65
|
|
|
67
66
|
const setWorkflowInfo = overrides => {
|
|
67
|
+
const base = baseWorkflowInfo();
|
|
68
68
|
const info = {
|
|
69
|
-
...
|
|
69
|
+
...base,
|
|
70
70
|
...overrides,
|
|
71
|
-
memo: overrides?.memo ??
|
|
71
|
+
memo: overrides?.memo ?? base.memo
|
|
72
72
|
};
|
|
73
73
|
workflowInfoMock.mockImplementation( () => info );
|
|
74
74
|
return info;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
const activityOutput = output => ( {
|
|
78
|
-
output,
|
|
79
|
-
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1
|
|
80
|
-
} );
|
|
81
|
-
|
|
82
77
|
const createActivities = handlers => new Proxy( {}, {
|
|
83
78
|
get: ( _, prop ) => typeof prop === 'string' ?
|
|
84
|
-
handlers[prop] ?? vi.fn().mockResolvedValue(
|
|
79
|
+
handlers[prop] ?? vi.fn().mockResolvedValue( undefined ) :
|
|
85
80
|
undefined
|
|
86
81
|
} );
|
|
87
82
|
|
|
@@ -91,6 +86,13 @@ const mockActivities = handlers => {
|
|
|
91
86
|
return activities;
|
|
92
87
|
};
|
|
93
88
|
|
|
89
|
+
const installGlobalDispatcher = runId => {
|
|
90
|
+
const dispatcher = vi.fn();
|
|
91
|
+
dispatcher.runId = runId;
|
|
92
|
+
globalThis[INVOKE_ACTIVITY_SYMBOL] = dispatcher;
|
|
93
|
+
return dispatcher;
|
|
94
|
+
};
|
|
95
|
+
|
|
94
96
|
const workflowDefinition = overrides => ( {
|
|
95
97
|
name: 'test_wf',
|
|
96
98
|
description: 'Test workflow',
|
|
@@ -100,16 +102,15 @@ const workflowDefinition = overrides => ( {
|
|
|
100
102
|
...overrides
|
|
101
103
|
} );
|
|
102
104
|
|
|
103
|
-
const invokeWorkflowFromHelper = ( wf, input, options ) => wf( input, options );
|
|
104
|
-
|
|
105
105
|
describe( 'workflow()', () => {
|
|
106
106
|
beforeEach( () => {
|
|
107
107
|
vi.clearAllMocks();
|
|
108
|
+
delete globalThis[INVOKE_ACTIVITY_SYMBOL];
|
|
108
109
|
inWorkflowContextMock.mockReturnValue( true );
|
|
109
|
-
executeChildMock.mockResolvedValue( {
|
|
110
|
+
executeChildMock.mockResolvedValue( {} );
|
|
110
111
|
setWorkflowInfo();
|
|
111
112
|
mockActivities( {
|
|
112
|
-
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue(
|
|
113
|
+
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( { local: '/tmp/trace' } )
|
|
113
114
|
} );
|
|
114
115
|
} );
|
|
115
116
|
|
|
@@ -204,9 +205,9 @@ describe( 'workflow()', () => {
|
|
|
204
205
|
value: 'test-workflow-ok',
|
|
205
206
|
extra: 'custom'
|
|
206
207
|
} );
|
|
207
|
-
expect(
|
|
208
|
-
expect(
|
|
209
|
-
expect(
|
|
208
|
+
expect( parseInvocationOptionsMock ).toHaveBeenCalledWith( { context: { extra: 'custom' } } );
|
|
209
|
+
expect( parseInputMock ).toHaveBeenCalledWith( { suffix: '-ok' } );
|
|
210
|
+
expect( parseOutputMock ).toHaveBeenCalledWith( { value: 'test-workflow-ok', extra: 'custom' } );
|
|
210
211
|
expect( workflowInfoMock ).not.toHaveBeenCalled();
|
|
211
212
|
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
212
213
|
} );
|
|
@@ -214,7 +215,7 @@ describe( 'workflow()', () => {
|
|
|
214
215
|
it( 'does not run fn when plain function input validation fails', async () => {
|
|
215
216
|
const { workflow } = await import( './workflow.js' );
|
|
216
217
|
const error = new ValidationError( 'invalid input' );
|
|
217
|
-
|
|
218
|
+
parseInputMock.mockImplementationOnce( () => {
|
|
218
219
|
throw error;
|
|
219
220
|
} );
|
|
220
221
|
const fn = vi.fn();
|
|
@@ -228,13 +229,13 @@ describe( 'workflow()', () => {
|
|
|
228
229
|
|
|
229
230
|
await expect( wf( { value: 1 } ) ).rejects.toBe( error );
|
|
230
231
|
expect( fn ).not.toHaveBeenCalled();
|
|
231
|
-
expect(
|
|
232
|
+
expect( parseOutputMock ).not.toHaveBeenCalled();
|
|
232
233
|
} );
|
|
233
234
|
|
|
234
235
|
it( 'propagates plain function output validation errors after fn runs', async () => {
|
|
235
236
|
const { workflow } = await import( './workflow.js' );
|
|
236
237
|
const error = new ValidationError( 'invalid output' );
|
|
237
|
-
|
|
238
|
+
parseOutputMock.mockImplementationOnce( () => {
|
|
238
239
|
throw error;
|
|
239
240
|
} );
|
|
240
241
|
const output = { result: 1 };
|
|
@@ -251,24 +252,27 @@ describe( 'workflow()', () => {
|
|
|
251
252
|
expect( fn ).toHaveBeenCalledWith( { value: 'ok' }, expect.objectContaining( {
|
|
252
253
|
info: { workflowId: 'test-workflow', runId: 'test-run' }
|
|
253
254
|
} ) );
|
|
254
|
-
expect(
|
|
255
|
+
expect( parseOutputMock ).toHaveBeenCalledWith( output );
|
|
255
256
|
} );
|
|
256
257
|
} );
|
|
257
258
|
|
|
258
259
|
describe( 'child workflow trigger path', () => {
|
|
259
|
-
it( 'starts a child workflow
|
|
260
|
+
it( 'starts a child workflow and forwards child-safe invocation options', async () => {
|
|
260
261
|
const { workflow } = await import( './workflow.js' );
|
|
261
262
|
const { ParentClosePolicy } = await import( '@temporalio/workflow' );
|
|
263
|
+
installGlobalDispatcher( 'run-123' );
|
|
262
264
|
const memo = {
|
|
263
|
-
stack: [ 'root-workflow', 'workflow-123' ],
|
|
264
265
|
traceInfo: { workflowId: 'root-workflow' },
|
|
265
266
|
activityOptions: {
|
|
266
|
-
|
|
267
|
-
retry: {
|
|
267
|
+
heartbeatTimeout: '30s',
|
|
268
|
+
retry: {
|
|
269
|
+
maximumInterval: '20s',
|
|
270
|
+
maximumAttempts: 4
|
|
271
|
+
}
|
|
268
272
|
}
|
|
269
273
|
};
|
|
270
274
|
setWorkflowInfo( { memo } );
|
|
271
|
-
executeChildMock.mockResolvedValueOnce( {
|
|
275
|
+
executeChildMock.mockResolvedValueOnce( { child: 'ok' } );
|
|
272
276
|
|
|
273
277
|
const wf = workflow( workflowDefinition( {
|
|
274
278
|
name: 'child_target_wf',
|
|
@@ -279,13 +283,15 @@ describe( 'workflow()', () => {
|
|
|
279
283
|
|
|
280
284
|
await expect( wf( { id: 1 }, {
|
|
281
285
|
detached: true,
|
|
286
|
+
context: { testOnly: true },
|
|
282
287
|
activityOptions: {
|
|
283
288
|
startToCloseTimeout: '2m',
|
|
284
289
|
retry: { maximumAttempts: 7 }
|
|
285
290
|
}
|
|
286
291
|
} ) ).resolves.toEqual( { child: 'ok' } );
|
|
287
|
-
expect(
|
|
292
|
+
expect( parseInvocationOptionsMock ).toHaveBeenCalledWith( {
|
|
288
293
|
detached: true,
|
|
294
|
+
context: { testOnly: true },
|
|
289
295
|
activityOptions: {
|
|
290
296
|
startToCloseTimeout: '2m',
|
|
291
297
|
retry: { maximumAttempts: 7 }
|
|
@@ -293,26 +299,29 @@ describe( 'workflow()', () => {
|
|
|
293
299
|
} );
|
|
294
300
|
|
|
295
301
|
expect( executeChildMock ).toHaveBeenCalledWith( 'child_target_wf', {
|
|
296
|
-
args: [ {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
memo: {
|
|
300
|
-
stack: [ 'root-workflow', 'workflow-123' ],
|
|
301
|
-
traceInfo: { workflowId: 'root-workflow' },
|
|
302
|
+
args: [ {
|
|
303
|
+
id: 1
|
|
304
|
+
}, {
|
|
302
305
|
activityOptions: {
|
|
303
306
|
startToCloseTimeout: '2m',
|
|
304
307
|
retry: { maximumAttempts: 7 }
|
|
305
308
|
}
|
|
306
|
-
}
|
|
309
|
+
} ],
|
|
310
|
+
workflowId: expect.stringMatching( /^workflow-123-/ ),
|
|
311
|
+
parentClosePolicy: ParentClosePolicy.ABANDON,
|
|
312
|
+
memo
|
|
307
313
|
} );
|
|
314
|
+
expect( executeChildMock.mock.calls[0][1].args[1] ).not.toHaveProperty( 'detached' );
|
|
315
|
+
expect( executeChildMock.mock.calls[0][1].args[1] ).not.toHaveProperty( 'context' );
|
|
308
316
|
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
309
317
|
} );
|
|
310
318
|
|
|
311
|
-
it( 'uses
|
|
319
|
+
it( 'uses undefined input and terminate policy by default for child workflow execution', async () => {
|
|
312
320
|
const { workflow } = await import( './workflow.js' );
|
|
313
321
|
const { ParentClosePolicy } = await import( '@temporalio/workflow' );
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
installGlobalDispatcher( 'run-123' );
|
|
323
|
+
setWorkflowInfo( { memo: { traceInfo: { workflowId: 'root-workflow' } } } );
|
|
324
|
+
executeChildMock.mockResolvedValueOnce( 'done' );
|
|
316
325
|
|
|
317
326
|
const wf = workflow( workflowDefinition( {
|
|
318
327
|
name: 'no_input_child_wf',
|
|
@@ -323,92 +332,59 @@ describe( 'workflow()', () => {
|
|
|
323
332
|
|
|
324
333
|
await expect( wf() ).resolves.toBe( 'done' );
|
|
325
334
|
expect( executeChildMock ).toHaveBeenCalledWith( 'no_input_child_wf', expect.objectContaining( {
|
|
326
|
-
args: [],
|
|
335
|
+
args: [ undefined, { activityOptions: undefined } ],
|
|
327
336
|
parentClosePolicy: ParentClosePolicy.TERMINATE,
|
|
328
|
-
memo: {
|
|
337
|
+
memo: { traceInfo: { workflowId: 'root-workflow' } }
|
|
329
338
|
} ) );
|
|
330
339
|
} );
|
|
331
340
|
|
|
332
|
-
it( '
|
|
341
|
+
it( 'propagates executeChild errors without root ApplicationFailure wrapping', async () => {
|
|
333
342
|
const { workflow } = await import( './workflow.js' );
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: getTraceDestinations } );
|
|
338
|
-
executeChildMock.mockResolvedValueOnce( { output: { child: 'ok' } } );
|
|
339
|
-
const childFn = vi.fn();
|
|
343
|
+
const error = new Error( 'child failed' );
|
|
344
|
+
installGlobalDispatcher( 'run-123' );
|
|
345
|
+
executeChildMock.mockRejectedValueOnce( error );
|
|
340
346
|
|
|
341
|
-
const
|
|
342
|
-
name: 'indirect_child_wf',
|
|
343
|
-
inputSchema: z.object( { id: z.number() } ),
|
|
344
|
-
outputSchema: z.object( { child: z.string() } ),
|
|
345
|
-
fn: childFn
|
|
346
|
-
} ) );
|
|
347
|
-
const parentWorkflow = workflow( workflowDefinition( {
|
|
348
|
-
name: 'indirect_parent_wf',
|
|
349
|
-
outputSchema: z.object( { child: z.string() } ),
|
|
350
|
-
fn: async () => invokeWorkflowFromHelper( childWorkflow, { id: 1 }, {
|
|
351
|
-
activityOptions: {
|
|
352
|
-
retry: { maximumAttempts: 1 }
|
|
353
|
-
}
|
|
354
|
-
} )
|
|
355
|
-
} ) );
|
|
347
|
+
const wf = workflow( workflowDefinition( { name: 'failing_child_wf' } ) );
|
|
356
348
|
|
|
357
|
-
await expect(
|
|
358
|
-
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
359
|
-
output: { child: 'ok' },
|
|
360
|
-
trace: { destinations: {} }
|
|
361
|
-
} );
|
|
362
|
-
expect( childFn ).not.toHaveBeenCalled();
|
|
363
|
-
expect( executeChildMock ).toHaveBeenCalledWith( 'indirect_child_wf', expect.objectContaining( {
|
|
364
|
-
args: [ { id: 1 } ],
|
|
365
|
-
parentClosePolicy: ParentClosePolicy.TERMINATE,
|
|
366
|
-
memo: expect.objectContaining( {
|
|
367
|
-
stack: [ 'workflow-123' ],
|
|
368
|
-
traceInfo: info.memo.traceInfo,
|
|
369
|
-
activityOptions: expect.objectContaining( {
|
|
370
|
-
retry: expect.objectContaining( { maximumAttempts: 1 } )
|
|
371
|
-
} )
|
|
372
|
-
} )
|
|
373
|
-
} ) );
|
|
374
|
-
expect( getTraceDestinations ).toHaveBeenCalledWith( info.memo.traceInfo );
|
|
349
|
+
await expect( wf( {} ) ).rejects.toBe( error );
|
|
375
350
|
} );
|
|
351
|
+
} );
|
|
376
352
|
|
|
377
|
-
|
|
353
|
+
describe( 'workflow execution path', () => {
|
|
354
|
+
it( 'validates invocation options before reading workflow information', async () => {
|
|
378
355
|
const { workflow } = await import( './workflow.js' );
|
|
379
|
-
|
|
356
|
+
const error = new ValidationError( 'invalid invocation options' );
|
|
357
|
+
parseInvocationOptionsMock.mockImplementationOnce( () => {
|
|
358
|
+
throw error;
|
|
359
|
+
} );
|
|
360
|
+
const wf = workflow( workflowDefinition() );
|
|
380
361
|
|
|
381
|
-
|
|
382
|
-
name: 'renamed_root_wf',
|
|
383
|
-
aliases: [ 'old_root_wf' ],
|
|
384
|
-
outputSchema: z.object( { ok: z.boolean() } ),
|
|
385
|
-
fn: async () => ( { ok: true } )
|
|
386
|
-
} ) );
|
|
362
|
+
await expect( wf( {}, { invalid: true } ) ).rejects.toBe( error );
|
|
387
363
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
output: { ok: true },
|
|
391
|
-
trace: { destinations: { local: '/tmp/trace' } }
|
|
392
|
-
} );
|
|
393
|
-
expect( executeChildMock ).not.toHaveBeenCalled();
|
|
364
|
+
expect( upsertMemoMock ).not.toHaveBeenCalled();
|
|
365
|
+
expect( workflowInfoMock ).not.toHaveBeenCalled();
|
|
394
366
|
} );
|
|
395
367
|
|
|
396
|
-
it( '
|
|
368
|
+
it( 'rejects a global activity dispatcher left by another workflow run', async () => {
|
|
397
369
|
const { workflow } = await import( './workflow.js' );
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
370
|
+
const fn = vi.fn().mockResolvedValue( {} );
|
|
371
|
+
installGlobalDispatcher( 'stale-run' );
|
|
372
|
+
setWorkflowInfo( { runId: 'current-run' } );
|
|
401
373
|
|
|
402
|
-
const wf = workflow( workflowDefinition( {
|
|
374
|
+
const wf = workflow( workflowDefinition( {
|
|
375
|
+
name: 'contamination_wf',
|
|
376
|
+
fn
|
|
377
|
+
} ) );
|
|
403
378
|
|
|
404
|
-
await expect( wf( {} ) ).rejects.
|
|
379
|
+
await expect( wf( {} ) ).rejects.toThrow( /Contamination of the workflow Node global context/ );
|
|
380
|
+
expect( executeChildMock ).not.toHaveBeenCalled();
|
|
381
|
+
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
382
|
+
expect( fn ).not.toHaveBeenCalled();
|
|
405
383
|
} );
|
|
406
|
-
} );
|
|
407
384
|
|
|
408
|
-
|
|
409
|
-
it( 'initializes root memo, skips trace destinations when trace is disabled, validates output, and returns an envelope', async () => {
|
|
385
|
+
it( 'skips trace destinations when trace is disabled and returns raw output', async () => {
|
|
410
386
|
const { workflow } = await import( './workflow.js' );
|
|
411
|
-
const getTraceDestinations = vi.fn().mockResolvedValue(
|
|
387
|
+
const getTraceDestinations = vi.fn().mockResolvedValue( { local: '/tmp/root-trace' } );
|
|
412
388
|
const info = setWorkflowInfo( { workflowType: 'root_wf', memo: {} } );
|
|
413
389
|
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: getTraceDestinations } );
|
|
414
390
|
|
|
@@ -425,27 +401,82 @@ describe( 'workflow()', () => {
|
|
|
425
401
|
fn: async ( _, context ) => ( { ok: context.info.workflowId === 'workflow-123' } )
|
|
426
402
|
} ) );
|
|
427
403
|
|
|
428
|
-
await expect( wf( {} ) ).resolves.toEqual( {
|
|
429
|
-
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
430
|
-
output: { ok: true },
|
|
431
|
-
trace: { destinations: {} }
|
|
432
|
-
} );
|
|
433
|
-
expect( info.memo.stack ).toEqual( [ 'workflow-123' ] );
|
|
404
|
+
await expect( wf( {} ) ).resolves.toEqual( { ok: true } );
|
|
434
405
|
expect( info.memo.traceInfo ).toBeUndefined();
|
|
435
|
-
expect( info.memo.activityOptions ).
|
|
406
|
+
expect( info.memo.activityOptions ).toBeUndefined();
|
|
407
|
+
expect( proxyActivitiesMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
436
408
|
startToCloseTimeout: '5m',
|
|
437
409
|
heartbeatTimeout: '5m',
|
|
438
410
|
retry: expect.objectContaining( { maximumAttempts: 5 } )
|
|
439
411
|
} ) );
|
|
440
|
-
expect( proxyActivitiesMock ).toHaveBeenCalledWith( info.memo.activityOptions );
|
|
441
412
|
expect( getTraceDestinations ).not.toHaveBeenCalled();
|
|
413
|
+
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
414
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
415
|
+
activityOptions: proxyActivitiesMock.mock.calls[0][0]
|
|
416
|
+
} );
|
|
417
|
+
} );
|
|
418
|
+
|
|
419
|
+
it( 'resolves activity options by invocation, definition, inherited memo, then default precedence', async () => {
|
|
420
|
+
const { workflow } = await import( './workflow.js' );
|
|
421
|
+
setWorkflowInfo( {
|
|
422
|
+
workflowType: 'activity_options_wf',
|
|
423
|
+
memo: {
|
|
424
|
+
traceInfo: { workflowId: 'workflow-123' },
|
|
425
|
+
activityOptions: {
|
|
426
|
+
heartbeatTimeout: '30s',
|
|
427
|
+
retry: {
|
|
428
|
+
maximumInterval: '30s',
|
|
429
|
+
maximumAttempts: 4
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} );
|
|
434
|
+
|
|
435
|
+
const wf = workflow( workflowDefinition( {
|
|
436
|
+
name: 'activity_options_wf',
|
|
437
|
+
options: {
|
|
438
|
+
activityOptions: {
|
|
439
|
+
startToCloseTimeout: '5m',
|
|
440
|
+
retry: {
|
|
441
|
+
backoffCoefficient: 3,
|
|
442
|
+
maximumAttempts: 2
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} ) );
|
|
447
|
+
|
|
448
|
+
await wf( {}, {
|
|
449
|
+
activityOptions: {
|
|
450
|
+
heartbeatTimeout: '1m',
|
|
451
|
+
retry: {
|
|
452
|
+
initialInterval: '1s',
|
|
453
|
+
maximumAttempts: 9
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
} );
|
|
457
|
+
|
|
458
|
+
expect( proxyActivitiesMock ).toHaveBeenCalledWith( {
|
|
459
|
+
startToCloseTimeout: '5m',
|
|
460
|
+
heartbeatTimeout: '1m',
|
|
461
|
+
retry: {
|
|
462
|
+
initialInterval: '1s',
|
|
463
|
+
backoffCoefficient: 3,
|
|
464
|
+
maximumInterval: '30s',
|
|
465
|
+
maximumAttempts: 9,
|
|
466
|
+
nonRetryableErrorTypes: [ 'FatalError' ]
|
|
467
|
+
}
|
|
468
|
+
} );
|
|
469
|
+
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
470
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
471
|
+
activityOptions: proxyActivitiesMock.mock.calls[0][0],
|
|
472
|
+
trace: { local: '/tmp/trace' }
|
|
473
|
+
} );
|
|
442
474
|
} );
|
|
443
475
|
|
|
444
476
|
it( 'runs non-root workflow execution without rebuilding trace info or fetching trace destinations', async () => {
|
|
445
477
|
const { workflow } = await import( './workflow.js' );
|
|
446
|
-
const getTraceDestinations = vi.fn().mockResolvedValue(
|
|
478
|
+
const getTraceDestinations = vi.fn().mockResolvedValue( { local: '/tmp/trace' } );
|
|
447
479
|
const memo = {
|
|
448
|
-
stack: [ 'root-workflow' ],
|
|
449
480
|
traceInfo: { workflowId: 'root-workflow' },
|
|
450
481
|
activityOptions: {
|
|
451
482
|
startToCloseTimeout: '9m',
|
|
@@ -471,24 +502,27 @@ describe( 'workflow()', () => {
|
|
|
471
502
|
fn: async () => ( { ok: true } )
|
|
472
503
|
} ) );
|
|
473
504
|
|
|
474
|
-
await expect( wf( {} ) ).resolves.toEqual( {
|
|
475
|
-
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
476
|
-
output: { ok: true }
|
|
477
|
-
} );
|
|
478
|
-
expect( info.memo.stack ).toEqual( [ 'root-workflow', 'child-workflow' ] );
|
|
505
|
+
await expect( wf( {} ) ).resolves.toEqual( { ok: true } );
|
|
479
506
|
expect( info.memo.traceInfo ).toBe( memo.traceInfo );
|
|
480
|
-
expect(
|
|
481
|
-
startToCloseTimeout: '
|
|
482
|
-
|
|
507
|
+
expect( proxyActivitiesMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
508
|
+
startToCloseTimeout: '1m',
|
|
509
|
+
heartbeatTimeout: '5m',
|
|
510
|
+
retry: expect.objectContaining( { maximumAttempts: 2 } )
|
|
483
511
|
} ) );
|
|
484
|
-
expect( proxyActivitiesMock ).toHaveBeenCalledWith( info.memo.activityOptions );
|
|
485
512
|
expect( getTraceDestinations ).not.toHaveBeenCalled();
|
|
513
|
+
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
514
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
515
|
+
activityOptions: proxyActivitiesMock.mock.calls[0][0]
|
|
516
|
+
} );
|
|
486
517
|
} );
|
|
487
518
|
|
|
488
|
-
it( '
|
|
519
|
+
it( 'stores empty trace destinations in memo and returns raw output', async () => {
|
|
489
520
|
const { workflow } = await import( './workflow.js' );
|
|
490
|
-
setWorkflowInfo( {
|
|
491
|
-
|
|
521
|
+
setWorkflowInfo( {
|
|
522
|
+
workflowType: 'no_trace_dest_wf',
|
|
523
|
+
memo: { traceInfo: { workflowId: 'workflow-123' } }
|
|
524
|
+
} );
|
|
525
|
+
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( {} ) } );
|
|
492
526
|
|
|
493
527
|
const wf = workflow( workflowDefinition( {
|
|
494
528
|
name: 'no_trace_dest_wf',
|
|
@@ -496,10 +530,11 @@ describe( 'workflow()', () => {
|
|
|
496
530
|
fn: async () => ( { ok: true } )
|
|
497
531
|
} ) );
|
|
498
532
|
|
|
499
|
-
await expect( wf( {} ) ).resolves.toEqual( {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
533
|
+
await expect( wf( {} ) ).resolves.toEqual( { ok: true } );
|
|
534
|
+
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
535
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
536
|
+
activityOptions: proxyActivitiesMock.mock.calls[0][0],
|
|
537
|
+
trace: {}
|
|
503
538
|
} );
|
|
504
539
|
} );
|
|
505
540
|
|
|
@@ -516,29 +551,38 @@ describe( 'workflow()', () => {
|
|
|
516
551
|
fn: async () => ( { result: 1 } )
|
|
517
552
|
} ) );
|
|
518
553
|
|
|
519
|
-
|
|
554
|
+
parseInputMock.mockImplementationOnce( () => {
|
|
520
555
|
throw inputError;
|
|
521
556
|
} );
|
|
522
557
|
await expect( wf( { value: 1 } ) ).rejects.toBe( inputError );
|
|
523
|
-
expect(
|
|
558
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
559
|
+
trace: { local: '/tmp/trace' }
|
|
560
|
+
} ) );
|
|
524
561
|
|
|
525
|
-
|
|
526
|
-
|
|
562
|
+
delete globalThis[INVOKE_ACTIVITY_SYMBOL];
|
|
563
|
+
setWorkflowInfo( {
|
|
564
|
+
workflowType: 'runtime_validation_wf',
|
|
565
|
+
memo: { traceInfo: { workflowId: 'workflow-123' } }
|
|
566
|
+
} );
|
|
567
|
+
upsertMemoMock.mockClear();
|
|
568
|
+
parseOutputMock.mockImplementationOnce( () => {
|
|
527
569
|
throw outputError;
|
|
528
570
|
} );
|
|
529
571
|
await expect( wf( { value: 'ok' } ) ).rejects.toBe( outputError );
|
|
530
|
-
expect(
|
|
572
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
573
|
+
trace: { local: '/tmp/trace' }
|
|
574
|
+
} ) );
|
|
531
575
|
} );
|
|
532
576
|
} );
|
|
533
577
|
|
|
534
578
|
describe( 'activity dispatchers', () => {
|
|
535
|
-
it( 'invokes workflow-scoped
|
|
579
|
+
it( 'invokes workflow-scoped activities and returns their raw outputs', async () => {
|
|
536
580
|
const { workflow } = await import( './workflow.js' );
|
|
537
581
|
setWorkflowInfo( { workflowType: 'dispatch_wf' } );
|
|
538
|
-
const step = vi.fn().mockResolvedValue(
|
|
539
|
-
const evaluator = vi.fn().mockResolvedValue(
|
|
582
|
+
const step = vi.fn().mockResolvedValue( 'step-output' );
|
|
583
|
+
const evaluator = vi.fn().mockResolvedValue( 'eval-output' );
|
|
540
584
|
mockActivities( {
|
|
541
|
-
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue(
|
|
585
|
+
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( {} ),
|
|
542
586
|
'dispatch_wf#stepA': step,
|
|
543
587
|
'dispatch_wf#evalA': evaluator
|
|
544
588
|
} );
|
|
@@ -546,49 +590,44 @@ describe( 'workflow()', () => {
|
|
|
546
590
|
const wf = workflow( workflowDefinition( {
|
|
547
591
|
name: 'dispatch_wf',
|
|
548
592
|
outputSchema: z.object( { stepResult: z.string(), evalResult: z.string() } ),
|
|
549
|
-
async
|
|
593
|
+
fn: async () => {
|
|
550
594
|
return {
|
|
551
|
-
stepResult: await
|
|
552
|
-
evalResult: await
|
|
595
|
+
stepResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'stepA', { a: 1 }, { b: 2 } ),
|
|
596
|
+
evalResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'evalA', { c: 3 } )
|
|
553
597
|
};
|
|
554
598
|
}
|
|
555
599
|
} ) );
|
|
556
600
|
|
|
557
|
-
await expect( wf( {} ) ).resolves.toEqual( {
|
|
558
|
-
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
559
|
-
output: { stepResult: 'step-output', evalResult: 'eval-output' },
|
|
560
|
-
trace: { destinations: {} }
|
|
561
|
-
} );
|
|
601
|
+
await expect( wf( {} ) ).resolves.toEqual( { stepResult: 'step-output', evalResult: 'eval-output' } );
|
|
562
602
|
expect( step ).toHaveBeenCalledWith( { a: 1 }, { b: 2 } );
|
|
563
603
|
expect( evaluator ).toHaveBeenCalledWith( { c: 3 } );
|
|
564
604
|
} );
|
|
565
605
|
|
|
566
|
-
it( 'invokes shared
|
|
606
|
+
it( 'invokes shared activities through the workflow namespace', async () => {
|
|
567
607
|
const { workflow } = await import( './workflow.js' );
|
|
568
608
|
setWorkflowInfo( { workflowType: 'shared_dispatch_wf' } );
|
|
569
|
-
const sharedStep = vi.fn().mockResolvedValue(
|
|
570
|
-
const sharedEvaluator = vi.fn().mockResolvedValue(
|
|
609
|
+
const sharedStep = vi.fn().mockResolvedValue( 'shared-step-output' );
|
|
610
|
+
const sharedEvaluator = vi.fn().mockResolvedValue( 'shared-eval-output' );
|
|
571
611
|
mockActivities( {
|
|
572
|
-
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue(
|
|
573
|
-
|
|
574
|
-
|
|
612
|
+
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( {} ),
|
|
613
|
+
'shared_dispatch_wf#stepA': sharedStep,
|
|
614
|
+
'shared_dispatch_wf#evalA': sharedEvaluator
|
|
575
615
|
} );
|
|
576
616
|
|
|
577
617
|
const wf = workflow( workflowDefinition( {
|
|
578
618
|
name: 'shared_dispatch_wf',
|
|
579
619
|
outputSchema: z.object( { stepResult: z.string(), evalResult: z.string() } ),
|
|
580
|
-
async
|
|
620
|
+
fn: async () => {
|
|
581
621
|
return {
|
|
582
|
-
stepResult: await
|
|
583
|
-
evalResult: await
|
|
622
|
+
stepResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'stepA' ),
|
|
623
|
+
evalResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'evalA', { x: 1 } )
|
|
584
624
|
};
|
|
585
625
|
}
|
|
586
626
|
} ) );
|
|
587
627
|
|
|
588
628
|
await expect( wf( {} ) ).resolves.toEqual( {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
trace: { destinations: {} }
|
|
629
|
+
stepResult: 'shared-step-output',
|
|
630
|
+
evalResult: 'shared-eval-output'
|
|
592
631
|
} );
|
|
593
632
|
expect( sharedStep ).toHaveBeenCalledWith();
|
|
594
633
|
expect( sharedEvaluator ).toHaveBeenCalledWith( { x: 1 } );
|
|
@@ -596,9 +635,10 @@ describe( 'workflow()', () => {
|
|
|
596
635
|
} );
|
|
597
636
|
|
|
598
637
|
describe( 'error handling', () => {
|
|
599
|
-
it( '
|
|
638
|
+
it( 'stores root trace destinations in memo and preserves workflow errors', async () => {
|
|
600
639
|
const { workflow } = await import( './workflow.js' );
|
|
601
|
-
const error = new Error( 'root failed' );
|
|
640
|
+
const error = new Error( 'root failed with details' );
|
|
641
|
+
error.details = [ { domain: { reason: 'bad-input' } } ];
|
|
602
642
|
setWorkflowInfo( { workflowType: 'root_error_wf' } );
|
|
603
643
|
|
|
604
644
|
const wf = workflow( workflowDefinition( {
|
|
@@ -610,64 +650,13 @@ describe( 'workflow()', () => {
|
|
|
610
650
|
|
|
611
651
|
const thrown = await wf( {} ).catch( e => e );
|
|
612
652
|
expect( thrown ).toBe( error );
|
|
613
|
-
expect( error[METADATA_ACCESS_SYMBOL] ).toEqual( { trace: { destinations: { local: '/tmp/trace' } } } );
|
|
614
|
-
} );
|
|
615
|
-
|
|
616
|
-
it( 'preserves existing error details when attaching root trace metadata', async () => {
|
|
617
|
-
const { workflow } = await import( './workflow.js' );
|
|
618
|
-
const error = new Error( 'root failed with details' );
|
|
619
|
-
error.details = [ { domain: { reason: 'bad-input' } } ];
|
|
620
|
-
setWorkflowInfo( { workflowType: 'root_error_existing_details_wf' } );
|
|
621
|
-
|
|
622
|
-
const wf = workflow( workflowDefinition( {
|
|
623
|
-
name: 'root_error_existing_details_wf',
|
|
624
|
-
fn: async () => {
|
|
625
|
-
throw error;
|
|
626
|
-
}
|
|
627
|
-
} ) );
|
|
628
|
-
|
|
629
|
-
await expect( wf( {} ) ).rejects.toBe( error );
|
|
630
653
|
expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
|
|
631
|
-
expect(
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
it( 'attaches empty trace destinations to root workflow errors when no destinations are available', async () => {
|
|
635
|
-
const { workflow } = await import( './workflow.js' );
|
|
636
|
-
setWorkflowInfo( { workflowType: 'root_error_no_trace_wf' } );
|
|
637
|
-
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( activityOutput( {} ) ) } );
|
|
638
|
-
const error = new Error( 'root failed without trace' );
|
|
639
|
-
|
|
640
|
-
const wf = workflow( workflowDefinition( {
|
|
641
|
-
name: 'root_error_no_trace_wf',
|
|
642
|
-
fn: async () => {
|
|
643
|
-
throw error;
|
|
644
|
-
}
|
|
654
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
655
|
+
trace: { local: '/tmp/trace' }
|
|
645
656
|
} ) );
|
|
646
|
-
|
|
647
|
-
await expect( wf( {} ) ).rejects.toBe( error );
|
|
648
|
-
expect( error[METADATA_ACCESS_SYMBOL] ).toEqual( { trace: { destinations: {} } } );
|
|
649
657
|
} );
|
|
650
658
|
|
|
651
|
-
it( 'preserves existing
|
|
652
|
-
const { workflow } = await import( './workflow.js' );
|
|
653
|
-
setWorkflowInfo( { workflowType: 'root_error_existing_details_no_trace_wf' } );
|
|
654
|
-
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( activityOutput( {} ) ) } );
|
|
655
|
-
const error = new Error( 'root failed without trace' );
|
|
656
|
-
error.details = [ { domain: { reason: 'bad-input' } } ];
|
|
657
|
-
|
|
658
|
-
const wf = workflow( workflowDefinition( {
|
|
659
|
-
name: 'root_error_existing_details_no_trace_wf',
|
|
660
|
-
fn: async () => {
|
|
661
|
-
throw error;
|
|
662
|
-
}
|
|
663
|
-
} ) );
|
|
664
|
-
|
|
665
|
-
await expect( wf( {} ) ).rejects.toBe( error );
|
|
666
|
-
expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
|
|
667
|
-
expect( error[METADATA_ACCESS_SYMBOL] ).toEqual( { trace: { destinations: {} } } );
|
|
668
|
-
} );
|
|
669
|
-
|
|
670
|
-
it( 'attaches trace metadata to existing root ApplicationFailure without wrapping it', async () => {
|
|
659
|
+
it( 'preserves existing root ApplicationFailure metadata', async () => {
|
|
671
660
|
const { workflow } = await import( './workflow.js' );
|
|
672
661
|
const { ApplicationFailure } = await import( '@temporalio/workflow' );
|
|
673
662
|
setWorkflowInfo( { workflowType: 'root_application_failure_wf' } );
|
|
@@ -690,7 +679,9 @@ describe( 'workflow()', () => {
|
|
|
690
679
|
expect( thrown ).toBe( error );
|
|
691
680
|
expect( error.type ).toBe( 'OriginalType' );
|
|
692
681
|
expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
|
|
693
|
-
expect(
|
|
682
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
683
|
+
trace: { local: '/tmp/trace' }
|
|
684
|
+
} ) );
|
|
694
685
|
} );
|
|
695
686
|
|
|
696
687
|
it( 'rethrows non-root workflow errors without ApplicationFailure wrapping', async () => {
|
|
@@ -699,7 +690,7 @@ describe( 'workflow()', () => {
|
|
|
699
690
|
setWorkflowInfo( {
|
|
700
691
|
workflowId: 'nested-workflow',
|
|
701
692
|
root: { workflowId: 'root-workflow', runId: 'root-run' },
|
|
702
|
-
memo: {
|
|
693
|
+
memo: { traceInfo: { workflowId: 'root-workflow' } }
|
|
703
694
|
} );
|
|
704
695
|
|
|
705
696
|
const wf = workflow( workflowDefinition( {
|
|
@@ -710,7 +701,10 @@ describe( 'workflow()', () => {
|
|
|
710
701
|
} ) );
|
|
711
702
|
|
|
712
703
|
await expect( wf( {} ) ).rejects.toBe( error );
|
|
713
|
-
expect(
|
|
704
|
+
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
705
|
+
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
706
|
+
activityOptions: proxyActivitiesMock.mock.calls[0][0]
|
|
707
|
+
} );
|
|
714
708
|
} );
|
|
715
709
|
} );
|
|
716
710
|
} );
|