@outputai/core 0.10.0 → 0.10.1-next.09ed166.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 +1 -1
- package/src/bus.js +58 -20
- package/src/bus.spec.js +135 -77
- package/src/consts.js +2 -1
- package/src/helpers/object.js +48 -35
- package/src/helpers/object.spec.js +123 -75
- package/src/hooks/index.d.ts +48 -45
- package/src/hooks/index.js +49 -44
- package/src/hooks/index.spec.js +74 -102
- package/src/interface/workflow.js +92 -85
- package/src/interface/workflow.spec.js +135 -142
- 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/worker/global_functions.js +2 -2
- package/src/worker/global_functions.spec.js +3 -3
- package/src/worker/index.js +3 -3
- package/src/worker/index.spec.js +6 -6
- package/src/worker/interceptors/activity.js +4 -4
- package/src/worker/interceptors/activity.spec.js +9 -9
- package/src/worker/interceptors/workflow.js +5 -5
- package/src/worker/interceptors/workflow.spec.js +7 -7
- package/src/worker/loader/activities.js +56 -40
- package/src/worker/loader/activities.spec.js +21 -22
- package/src/worker/loader/workflows.spec.js +1 -2
- package/src/worker/log_hooks.js +9 -9
- package/src/worker/log_hooks.spec.js +2 -2
- package/src/worker/sinks.js +8 -8
- package/src/worker/sinks.spec.js +9 -9
- 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/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
|
@@ -3,8 +3,8 @@ import { z } from 'zod';
|
|
|
3
3
|
import {
|
|
4
4
|
ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
5
5
|
ACTIVITY_WRAPPER_VERSION_FIELD,
|
|
6
|
+
INVOKE_ACTIVITY_SYMBOL,
|
|
6
7
|
METADATA_ACCESS_SYMBOL,
|
|
7
|
-
SHARED_STEP_PREFIX,
|
|
8
8
|
WORKFLOW_WRAPPER_VERSION_FIELD
|
|
9
9
|
} from '#consts';
|
|
10
10
|
import { ValidationError } from '#errors';
|
|
@@ -91,6 +91,13 @@ const mockActivities = handlers => {
|
|
|
91
91
|
return activities;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
const installGlobalDispatcher = runId => {
|
|
95
|
+
const dispatcher = vi.fn();
|
|
96
|
+
dispatcher.runId = runId;
|
|
97
|
+
globalThis[INVOKE_ACTIVITY_SYMBOL] = dispatcher;
|
|
98
|
+
return dispatcher;
|
|
99
|
+
};
|
|
100
|
+
|
|
94
101
|
const workflowDefinition = overrides => ( {
|
|
95
102
|
name: 'test_wf',
|
|
96
103
|
description: 'Test workflow',
|
|
@@ -100,11 +107,10 @@ const workflowDefinition = overrides => ( {
|
|
|
100
107
|
...overrides
|
|
101
108
|
} );
|
|
102
109
|
|
|
103
|
-
const invokeWorkflowFromHelper = ( wf, input, options ) => wf( input, options );
|
|
104
|
-
|
|
105
110
|
describe( 'workflow()', () => {
|
|
106
111
|
beforeEach( () => {
|
|
107
112
|
vi.clearAllMocks();
|
|
113
|
+
delete globalThis[INVOKE_ACTIVITY_SYMBOL];
|
|
108
114
|
inWorkflowContextMock.mockReturnValue( true );
|
|
109
115
|
executeChildMock.mockResolvedValue( { output: {} } );
|
|
110
116
|
setWorkflowInfo();
|
|
@@ -256,15 +262,18 @@ describe( 'workflow()', () => {
|
|
|
256
262
|
} );
|
|
257
263
|
|
|
258
264
|
describe( 'child workflow trigger path', () => {
|
|
259
|
-
it( 'starts a child workflow
|
|
265
|
+
it( 'starts a child workflow and forwards child-safe invocation options', async () => {
|
|
260
266
|
const { workflow } = await import( './workflow.js' );
|
|
261
267
|
const { ParentClosePolicy } = await import( '@temporalio/workflow' );
|
|
268
|
+
installGlobalDispatcher( 'run-123' );
|
|
262
269
|
const memo = {
|
|
263
|
-
stack: [ 'root-workflow', 'workflow-123' ],
|
|
264
270
|
traceInfo: { workflowId: 'root-workflow' },
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
retry: {
|
|
271
|
+
parentActivityOptions: {
|
|
272
|
+
heartbeatTimeout: '30s',
|
|
273
|
+
retry: {
|
|
274
|
+
maximumInterval: '20s',
|
|
275
|
+
maximumAttempts: 4
|
|
276
|
+
}
|
|
268
277
|
}
|
|
269
278
|
};
|
|
270
279
|
setWorkflowInfo( { memo } );
|
|
@@ -279,6 +288,7 @@ describe( 'workflow()', () => {
|
|
|
279
288
|
|
|
280
289
|
await expect( wf( { id: 1 }, {
|
|
281
290
|
detached: true,
|
|
291
|
+
context: { testOnly: true },
|
|
282
292
|
activityOptions: {
|
|
283
293
|
startToCloseTimeout: '2m',
|
|
284
294
|
retry: { maximumAttempts: 7 }
|
|
@@ -286,6 +296,7 @@ describe( 'workflow()', () => {
|
|
|
286
296
|
} ) ).resolves.toEqual( { child: 'ok' } );
|
|
287
297
|
expect( validateInvocationOptionsMock ).toHaveBeenCalledWith( {
|
|
288
298
|
detached: true,
|
|
299
|
+
context: { testOnly: true },
|
|
289
300
|
activityOptions: {
|
|
290
301
|
startToCloseTimeout: '2m',
|
|
291
302
|
retry: { maximumAttempts: 7 }
|
|
@@ -293,25 +304,28 @@ describe( 'workflow()', () => {
|
|
|
293
304
|
} );
|
|
294
305
|
|
|
295
306
|
expect( executeChildMock ).toHaveBeenCalledWith( 'child_target_wf', {
|
|
296
|
-
args: [ {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
memo: {
|
|
300
|
-
stack: [ 'root-workflow', 'workflow-123' ],
|
|
301
|
-
traceInfo: { workflowId: 'root-workflow' },
|
|
307
|
+
args: [ {
|
|
308
|
+
id: 1
|
|
309
|
+
}, {
|
|
302
310
|
activityOptions: {
|
|
303
311
|
startToCloseTimeout: '2m',
|
|
304
312
|
retry: { maximumAttempts: 7 }
|
|
305
313
|
}
|
|
306
|
-
}
|
|
314
|
+
} ],
|
|
315
|
+
workflowId: expect.stringMatching( /^workflow-123-/ ),
|
|
316
|
+
parentClosePolicy: ParentClosePolicy.ABANDON,
|
|
317
|
+
memo
|
|
307
318
|
} );
|
|
319
|
+
expect( executeChildMock.mock.calls[0][1].args[1] ).not.toHaveProperty( 'detached' );
|
|
320
|
+
expect( executeChildMock.mock.calls[0][1].args[1] ).not.toHaveProperty( 'context' );
|
|
308
321
|
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
309
322
|
} );
|
|
310
323
|
|
|
311
|
-
it( 'uses
|
|
324
|
+
it( 'uses undefined input and terminate policy by default for child workflow execution', async () => {
|
|
312
325
|
const { workflow } = await import( './workflow.js' );
|
|
313
326
|
const { ParentClosePolicy } = await import( '@temporalio/workflow' );
|
|
314
|
-
|
|
327
|
+
installGlobalDispatcher( 'run-123' );
|
|
328
|
+
setWorkflowInfo( { memo: { traceInfo: { workflowId: 'root-workflow' } } } );
|
|
315
329
|
executeChildMock.mockResolvedValueOnce( { output: 'done' } );
|
|
316
330
|
|
|
317
331
|
const wf = workflow( workflowDefinition( {
|
|
@@ -323,89 +337,42 @@ describe( 'workflow()', () => {
|
|
|
323
337
|
|
|
324
338
|
await expect( wf() ).resolves.toBe( 'done' );
|
|
325
339
|
expect( executeChildMock ).toHaveBeenCalledWith( 'no_input_child_wf', expect.objectContaining( {
|
|
326
|
-
args: [],
|
|
340
|
+
args: [ undefined, { activityOptions: undefined } ],
|
|
327
341
|
parentClosePolicy: ParentClosePolicy.TERMINATE,
|
|
328
|
-
memo: {
|
|
342
|
+
memo: { traceInfo: { workflowId: 'root-workflow' } }
|
|
329
343
|
} ) );
|
|
330
344
|
} );
|
|
331
345
|
|
|
332
|
-
it( '
|
|
346
|
+
it( 'propagates executeChild errors without root ApplicationFailure wrapping', async () => {
|
|
333
347
|
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();
|
|
348
|
+
const error = new Error( 'child failed' );
|
|
349
|
+
installGlobalDispatcher( 'run-123' );
|
|
350
|
+
executeChildMock.mockRejectedValueOnce( error );
|
|
340
351
|
|
|
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
|
-
} ) );
|
|
352
|
+
const wf = workflow( workflowDefinition( { name: 'failing_child_wf' } ) );
|
|
356
353
|
|
|
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 );
|
|
354
|
+
await expect( wf( {} ) ).rejects.toBe( error );
|
|
375
355
|
} );
|
|
356
|
+
} );
|
|
376
357
|
|
|
377
|
-
|
|
358
|
+
describe( 'workflow execution path', () => {
|
|
359
|
+
it( 'rejects a global activity dispatcher left by another workflow run', async () => {
|
|
378
360
|
const { workflow } = await import( './workflow.js' );
|
|
379
|
-
|
|
361
|
+
const fn = vi.fn().mockResolvedValue( {} );
|
|
362
|
+
installGlobalDispatcher( 'stale-run' );
|
|
363
|
+
setWorkflowInfo( { runId: 'current-run' } );
|
|
380
364
|
|
|
381
365
|
const wf = workflow( workflowDefinition( {
|
|
382
|
-
name: '
|
|
383
|
-
|
|
384
|
-
outputSchema: z.object( { ok: z.boolean() } ),
|
|
385
|
-
fn: async () => ( { ok: true } )
|
|
366
|
+
name: 'contamination_wf',
|
|
367
|
+
fn
|
|
386
368
|
} ) );
|
|
387
369
|
|
|
388
|
-
await expect( wf( {} ) ).
|
|
389
|
-
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
390
|
-
output: { ok: true },
|
|
391
|
-
trace: { destinations: { local: '/tmp/trace' } }
|
|
392
|
-
} );
|
|
370
|
+
await expect( wf( {} ) ).rejects.toThrow( /Contamination of the workflow Node global context/ );
|
|
393
371
|
expect( executeChildMock ).not.toHaveBeenCalled();
|
|
372
|
+
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
373
|
+
expect( fn ).not.toHaveBeenCalled();
|
|
394
374
|
} );
|
|
395
375
|
|
|
396
|
-
it( 'propagates executeChild errors without root ApplicationFailure wrapping', async () => {
|
|
397
|
-
const { workflow } = await import( './workflow.js' );
|
|
398
|
-
const error = new Error( 'child failed' );
|
|
399
|
-
setWorkflowInfo( { memo: { stack: [ 'workflow-123' ] } } );
|
|
400
|
-
executeChildMock.mockRejectedValueOnce( error );
|
|
401
|
-
|
|
402
|
-
const wf = workflow( workflowDefinition( { name: 'failing_child_wf' } ) );
|
|
403
|
-
|
|
404
|
-
await expect( wf( {} ) ).rejects.toBe( error );
|
|
405
|
-
} );
|
|
406
|
-
} );
|
|
407
|
-
|
|
408
|
-
describe( 'workflow execution path', () => {
|
|
409
376
|
it( 'initializes root memo, skips trace destinations when trace is disabled, validates output, and returns an envelope', async () => {
|
|
410
377
|
const { workflow } = await import( './workflow.js' );
|
|
411
378
|
const getTraceDestinations = vi.fn().mockResolvedValue( activityOutput( { local: '/tmp/root-trace' } ) );
|
|
@@ -430,24 +397,79 @@ describe( 'workflow()', () => {
|
|
|
430
397
|
output: { ok: true },
|
|
431
398
|
trace: { destinations: {} }
|
|
432
399
|
} );
|
|
433
|
-
expect( info.memo.stack ).toEqual( [ 'workflow-123' ] );
|
|
434
400
|
expect( info.memo.traceInfo ).toBeUndefined();
|
|
435
|
-
expect( info.memo.activityOptions ).
|
|
401
|
+
expect( info.memo.activityOptions ).toBeUndefined();
|
|
402
|
+
expect( info.memo.parentActivityOptions ).toEqual( expect.objectContaining( {
|
|
403
|
+
startToCloseTimeout: '5m',
|
|
404
|
+
heartbeatTimeout: '5m',
|
|
405
|
+
retry: expect.objectContaining( { maximumAttempts: 5 } )
|
|
406
|
+
} ) );
|
|
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();
|
|
442
413
|
} );
|
|
443
414
|
|
|
415
|
+
it( 'resolves activity options by invocation, definition, inherited memo, then default precedence', async () => {
|
|
416
|
+
const { workflow } = await import( './workflow.js' );
|
|
417
|
+
const info = setWorkflowInfo( {
|
|
418
|
+
workflowType: 'activity_options_wf',
|
|
419
|
+
memo: {
|
|
420
|
+
parentActivityOptions: {
|
|
421
|
+
heartbeatTimeout: '30s',
|
|
422
|
+
retry: {
|
|
423
|
+
maximumInterval: '30s',
|
|
424
|
+
maximumAttempts: 4
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
} );
|
|
429
|
+
|
|
430
|
+
const wf = workflow( workflowDefinition( {
|
|
431
|
+
name: 'activity_options_wf',
|
|
432
|
+
options: {
|
|
433
|
+
activityOptions: {
|
|
434
|
+
startToCloseTimeout: '5m',
|
|
435
|
+
retry: {
|
|
436
|
+
backoffCoefficient: 3,
|
|
437
|
+
maximumAttempts: 2
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} ) );
|
|
442
|
+
|
|
443
|
+
await wf( {}, {
|
|
444
|
+
activityOptions: {
|
|
445
|
+
heartbeatTimeout: '1m',
|
|
446
|
+
retry: {
|
|
447
|
+
initialInterval: '1s',
|
|
448
|
+
maximumAttempts: 9
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} );
|
|
452
|
+
|
|
453
|
+
expect( proxyActivitiesMock ).toHaveBeenCalledWith( {
|
|
454
|
+
startToCloseTimeout: '5m',
|
|
455
|
+
heartbeatTimeout: '1m',
|
|
456
|
+
retry: {
|
|
457
|
+
initialInterval: '1s',
|
|
458
|
+
backoffCoefficient: 3,
|
|
459
|
+
maximumInterval: '30s',
|
|
460
|
+
maximumAttempts: 9,
|
|
461
|
+
nonRetryableErrorTypes: [ ValidationError.name, 'FatalError' ]
|
|
462
|
+
}
|
|
463
|
+
} );
|
|
464
|
+
expect( info.memo.parentActivityOptions ).toEqual( proxyActivitiesMock.mock.calls[0][0] );
|
|
465
|
+
} );
|
|
466
|
+
|
|
444
467
|
it( 'runs non-root workflow execution without rebuilding trace info or fetching trace destinations', async () => {
|
|
445
468
|
const { workflow } = await import( './workflow.js' );
|
|
446
469
|
const getTraceDestinations = vi.fn().mockResolvedValue( activityOutput( { local: '/tmp/trace' } ) );
|
|
447
470
|
const memo = {
|
|
448
|
-
stack: [ 'root-workflow' ],
|
|
449
471
|
traceInfo: { workflowId: 'root-workflow' },
|
|
450
|
-
|
|
472
|
+
parentActivityOptions: {
|
|
451
473
|
startToCloseTimeout: '9m',
|
|
452
474
|
retry: { maximumAttempts: 8 }
|
|
453
475
|
}
|
|
@@ -475,13 +497,18 @@ describe( 'workflow()', () => {
|
|
|
475
497
|
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
476
498
|
output: { ok: true }
|
|
477
499
|
} );
|
|
478
|
-
expect( info.memo.stack ).toEqual( [ 'root-workflow', 'child-workflow' ] );
|
|
479
500
|
expect( info.memo.traceInfo ).toBe( memo.traceInfo );
|
|
480
|
-
expect( info.memo.activityOptions ).
|
|
481
|
-
|
|
482
|
-
|
|
501
|
+
expect( info.memo.activityOptions ).toBeUndefined();
|
|
502
|
+
expect( info.memo.parentActivityOptions ).toEqual( expect.objectContaining( {
|
|
503
|
+
startToCloseTimeout: '1m',
|
|
504
|
+
heartbeatTimeout: '5m',
|
|
505
|
+
retry: expect.objectContaining( { maximumAttempts: 2 } )
|
|
506
|
+
} ) );
|
|
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();
|
|
486
513
|
} );
|
|
487
514
|
|
|
@@ -522,6 +549,7 @@ describe( 'workflow()', () => {
|
|
|
522
549
|
await expect( wf( { value: 1 } ) ).rejects.toBe( inputError );
|
|
523
550
|
expect( inputError[METADATA_ACCESS_SYMBOL] ).toEqual( { trace: { destinations: { local: '/tmp/trace' } } } );
|
|
524
551
|
|
|
552
|
+
delete globalThis[INVOKE_ACTIVITY_SYMBOL];
|
|
525
553
|
setWorkflowInfo( { workflowType: 'runtime_validation_wf', memo: {} } );
|
|
526
554
|
validateOutputMock.mockImplementationOnce( () => {
|
|
527
555
|
throw outputError;
|
|
@@ -532,7 +560,7 @@ describe( 'workflow()', () => {
|
|
|
532
560
|
} );
|
|
533
561
|
|
|
534
562
|
describe( 'activity dispatchers', () => {
|
|
535
|
-
it( 'invokes workflow-scoped
|
|
563
|
+
it( 'invokes workflow-scoped activities and unwraps activity output', async () => {
|
|
536
564
|
const { workflow } = await import( './workflow.js' );
|
|
537
565
|
setWorkflowInfo( { workflowType: 'dispatch_wf' } );
|
|
538
566
|
const step = vi.fn().mockResolvedValue( activityOutput( 'step-output' ) );
|
|
@@ -546,10 +574,10 @@ describe( 'workflow()', () => {
|
|
|
546
574
|
const wf = workflow( workflowDefinition( {
|
|
547
575
|
name: 'dispatch_wf',
|
|
548
576
|
outputSchema: z.object( { stepResult: z.string(), evalResult: z.string() } ),
|
|
549
|
-
async
|
|
577
|
+
fn: async () => {
|
|
550
578
|
return {
|
|
551
|
-
stepResult: await
|
|
552
|
-
evalResult: await
|
|
579
|
+
stepResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'stepA', { a: 1 }, { b: 2 } ),
|
|
580
|
+
evalResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'evalA', { c: 3 } )
|
|
553
581
|
};
|
|
554
582
|
}
|
|
555
583
|
} ) );
|
|
@@ -563,24 +591,24 @@ describe( 'workflow()', () => {
|
|
|
563
591
|
expect( evaluator ).toHaveBeenCalledWith( { c: 3 } );
|
|
564
592
|
} );
|
|
565
593
|
|
|
566
|
-
it( 'invokes shared
|
|
594
|
+
it( 'invokes shared activities through the workflow namespace', async () => {
|
|
567
595
|
const { workflow } = await import( './workflow.js' );
|
|
568
596
|
setWorkflowInfo( { workflowType: 'shared_dispatch_wf' } );
|
|
569
597
|
const sharedStep = vi.fn().mockResolvedValue( activityOutput( 'shared-step-output' ) );
|
|
570
598
|
const sharedEvaluator = vi.fn().mockResolvedValue( activityOutput( 'shared-eval-output' ) );
|
|
571
599
|
mockActivities( {
|
|
572
600
|
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( activityOutput( null ) ),
|
|
573
|
-
|
|
574
|
-
|
|
601
|
+
'shared_dispatch_wf#stepA': sharedStep,
|
|
602
|
+
'shared_dispatch_wf#evalA': sharedEvaluator
|
|
575
603
|
} );
|
|
576
604
|
|
|
577
605
|
const wf = workflow( workflowDefinition( {
|
|
578
606
|
name: 'shared_dispatch_wf',
|
|
579
607
|
outputSchema: z.object( { stepResult: z.string(), evalResult: z.string() } ),
|
|
580
|
-
async
|
|
608
|
+
fn: async () => {
|
|
581
609
|
return {
|
|
582
|
-
stepResult: await
|
|
583
|
-
evalResult: await
|
|
610
|
+
stepResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'stepA' ),
|
|
611
|
+
evalResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'evalA', { x: 1 } )
|
|
584
612
|
};
|
|
585
613
|
}
|
|
586
614
|
} ) );
|
|
@@ -596,9 +624,10 @@ describe( 'workflow()', () => {
|
|
|
596
624
|
} );
|
|
597
625
|
|
|
598
626
|
describe( 'error handling', () => {
|
|
599
|
-
it( 'attaches root trace destinations to root workflow errors
|
|
627
|
+
it( 'attaches root trace destinations to root workflow errors and preserves existing details', async () => {
|
|
600
628
|
const { workflow } = await import( './workflow.js' );
|
|
601
|
-
const error = new Error( 'root failed' );
|
|
629
|
+
const error = new Error( 'root failed with details' );
|
|
630
|
+
error.details = [ { domain: { reason: 'bad-input' } } ];
|
|
602
631
|
setWorkflowInfo( { workflowType: 'root_error_wf' } );
|
|
603
632
|
|
|
604
633
|
const wf = workflow( workflowDefinition( {
|
|
@@ -610,23 +639,6 @@ describe( 'workflow()', () => {
|
|
|
610
639
|
|
|
611
640
|
const thrown = await wf( {} ).catch( e => e );
|
|
612
641
|
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
642
|
expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
|
|
631
643
|
expect( error[METADATA_ACCESS_SYMBOL] ).toEqual( { trace: { destinations: { local: '/tmp/trace' } } } );
|
|
632
644
|
} );
|
|
@@ -648,25 +660,6 @@ describe( 'workflow()', () => {
|
|
|
648
660
|
expect( error[METADATA_ACCESS_SYMBOL] ).toEqual( { trace: { destinations: {} } } );
|
|
649
661
|
} );
|
|
650
662
|
|
|
651
|
-
it( 'preserves existing error details when no trace destinations are available', async () => {
|
|
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
663
|
it( 'attaches trace metadata to existing root ApplicationFailure without wrapping it', async () => {
|
|
671
664
|
const { workflow } = await import( './workflow.js' );
|
|
672
665
|
const { ApplicationFailure } = await import( '@temporalio/workflow' );
|
|
@@ -699,7 +692,7 @@ describe( 'workflow()', () => {
|
|
|
699
692
|
setWorkflowInfo( {
|
|
700
693
|
workflowId: 'nested-workflow',
|
|
701
694
|
root: { workflowId: 'root-workflow', runId: 'root-run' },
|
|
702
|
-
memo: {
|
|
695
|
+
memo: { traceInfo: { workflowId: 'root-workflow' } }
|
|
703
696
|
} );
|
|
704
697
|
|
|
705
698
|
const wf = workflow( workflowDefinition( {
|
|
@@ -23,29 +23,34 @@ export declare const Objects: {
|
|
|
23
23
|
isPlainObject( object: unknown ): boolean,
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Creates a new object by merging
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
31
|
-
* @param
|
|
32
|
-
* @param
|
|
33
|
-
* @throws {Error} If
|
|
26
|
+
* Creates a new object by recursively merging overlays onto a base object.
|
|
27
|
+
* Later objects overwrite fields from earlier objects.
|
|
28
|
+
*
|
|
29
|
+
* Non-object overlays are ignored.
|
|
30
|
+
*
|
|
31
|
+
* @param base - The base object.
|
|
32
|
+
* @param overlays - The overriding objects, applied from left to right.
|
|
33
|
+
* @throws {Error} If `base` is not a plain object.
|
|
34
34
|
* @returns A new merged object.
|
|
35
35
|
*/
|
|
36
|
-
deepMerge(
|
|
36
|
+
deepMerge( base: object, ...overlays: Array<object | null | undefined> ): object,
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Creates a new object by merging
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* -
|
|
43
|
-
*
|
|
44
|
-
* @param
|
|
45
|
-
* @param
|
|
46
|
-
* @
|
|
47
|
-
* @throws {Error} If either `a` or `b` is not a plain object.
|
|
39
|
+
* Creates a new object by recursively merging overlays onto a base object.
|
|
40
|
+
* Existing leaf values are resolved by the resolver function.
|
|
41
|
+
*
|
|
42
|
+
* Non-object overlays are ignored.
|
|
43
|
+
*
|
|
44
|
+
* @param base - The base object.
|
|
45
|
+
* @param args - The overriding objects, applied from left to right, followed by the resolver function.
|
|
46
|
+
* @throws {Error} If `base` is not a plain object.
|
|
48
47
|
* @returns A new merged object.
|
|
49
48
|
*/
|
|
50
|
-
deepMergeWithResolver(
|
|
49
|
+
deepMergeWithResolver(
|
|
50
|
+
base: object,
|
|
51
|
+
...args: [
|
|
52
|
+
...overlays: Array<object | null | undefined>,
|
|
53
|
+
resolver: ( a: unknown, b: unknown ) => unknown
|
|
54
|
+
]
|
|
55
|
+
): object
|
|
51
56
|
};
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
* Tools to interact with Events
|
|
3
3
|
*/
|
|
4
4
|
export declare const Event: {
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
|
-
* Emits
|
|
7
|
-
*
|
|
8
|
-
* When called inside an Output activity context, the framework automatically
|
|
9
|
-
* attaches `activityInfo`, `workflowDetails`, and `outputActivityKind` onto the emitted payload.
|
|
7
|
+
* Emits an event on step message bus.
|
|
10
8
|
*
|
|
11
9
|
* @param eventName - The name of the event to emit
|
|
12
|
-
* @param payload - An optional payload to send to the event
|
|
10
|
+
* @param payload - An optional payload to send to the event.
|
|
13
11
|
*/
|
|
14
|
-
emit( eventName: string, payload?: object ):
|
|
12
|
+
emit( eventName: string, payload?: object ): boolean;
|
|
15
13
|
};
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Storage } from '#async_storage';
|
|
1
|
+
import { stepEventBus } from '#bus';
|
|
3
2
|
|
|
4
3
|
export const Event = {
|
|
5
|
-
emit: ( eventName, payload ) => {
|
|
6
|
-
const ctx = Storage.load();
|
|
7
|
-
|
|
8
|
-
messageBus.emit( `external:${eventName}`, {
|
|
9
|
-
...payload ?? {},
|
|
10
|
-
...( ctx && {
|
|
11
|
-
activityInfo: ctx.activityInfo,
|
|
12
|
-
workflowDetails: ctx.workflowDetails,
|
|
13
|
-
outputActivityKind: ctx.outputActivityKind
|
|
14
|
-
} )
|
|
15
|
-
} );
|
|
16
|
-
}
|
|
4
|
+
emit: ( eventName, payload ) => stepEventBus.emit( `sdk:${eventName}`, payload )
|
|
17
5
|
};
|
|
18
|
-
|