@outputai/core 0.14.0 → 0.14.1-next.51e8690.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 +14 -3
- 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,710 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import {
|
|
4
|
-
ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
5
|
-
INVOKE_ACTIVITY_SYMBOL
|
|
6
|
-
} from '#consts';
|
|
7
|
-
import { ValidationError } from '#errors';
|
|
8
|
-
|
|
9
|
-
const inWorkflowContextMock = vi.hoisted( () => vi.fn() );
|
|
10
|
-
const proxyActivitiesMock = vi.hoisted( () => vi.fn() );
|
|
11
|
-
const executeChildMock = vi.hoisted( () => vi.fn() );
|
|
12
|
-
const workflowInfoMock = vi.hoisted( () => vi.fn() );
|
|
13
|
-
const upsertMemoMock = vi.hoisted( () => vi.fn() );
|
|
14
|
-
const continueAsNewMock = vi.hoisted( () => vi.fn() );
|
|
15
|
-
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
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 ) );
|
|
19
|
-
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
20
|
-
const createWorkflowMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
21
|
-
|
|
22
|
-
vi.mock( './validations/index.js', () => {
|
|
23
|
-
class WorkflowValidator {
|
|
24
|
-
static validateDefinition( ...args ) {
|
|
25
|
-
return validateDefinitionMock( ...args );
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
constructor( ...args ) {
|
|
29
|
-
validatorConstructorMock( ...args );
|
|
30
|
-
this.parseInput = parseInputMock;
|
|
31
|
-
this.parseOutput = parseOutputMock;
|
|
32
|
-
this.parseInvocationOptions = parseInvocationOptionsMock;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { WorkflowValidator };
|
|
37
|
-
} );
|
|
38
|
-
|
|
39
|
-
vi.mock( '#helpers/component', () => ( {
|
|
40
|
-
createWorkflow: createWorkflowMock
|
|
41
|
-
} ) );
|
|
42
|
-
|
|
43
|
-
vi.mock( '@temporalio/workflow', async importOriginal => {
|
|
44
|
-
const actual = await importOriginal();
|
|
45
|
-
return {
|
|
46
|
-
...actual,
|
|
47
|
-
inWorkflowContext: inWorkflowContextMock,
|
|
48
|
-
proxyActivities: proxyActivitiesMock,
|
|
49
|
-
executeChild: executeChildMock,
|
|
50
|
-
workflowInfo: workflowInfoMock,
|
|
51
|
-
upsertMemo: upsertMemoMock,
|
|
52
|
-
continueAsNew: continueAsNewMock,
|
|
53
|
-
uuid4: () => '550e8400e29b41d4a716446655440000'
|
|
54
|
-
};
|
|
55
|
-
} );
|
|
56
|
-
|
|
57
|
-
const baseWorkflowInfo = () => ( {
|
|
58
|
-
workflowId: 'workflow-123',
|
|
59
|
-
workflowType: 'test_wf',
|
|
60
|
-
runId: 'run-123',
|
|
61
|
-
startTime: new Date( '2025-01-01T00:00:00.000Z' ),
|
|
62
|
-
memo: { traceInfo: { workflowId: 'workflow-123' } },
|
|
63
|
-
continueAsNewSuggested: false
|
|
64
|
-
} );
|
|
65
|
-
|
|
66
|
-
const setWorkflowInfo = overrides => {
|
|
67
|
-
const base = baseWorkflowInfo();
|
|
68
|
-
const info = {
|
|
69
|
-
...base,
|
|
70
|
-
...overrides,
|
|
71
|
-
memo: overrides?.memo ?? base.memo
|
|
72
|
-
};
|
|
73
|
-
workflowInfoMock.mockImplementation( () => info );
|
|
74
|
-
return info;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const createActivities = handlers => new Proxy( {}, {
|
|
78
|
-
get: ( _, prop ) => typeof prop === 'string' ?
|
|
79
|
-
handlers[prop] ?? vi.fn().mockResolvedValue( undefined ) :
|
|
80
|
-
undefined
|
|
81
|
-
} );
|
|
82
|
-
|
|
83
|
-
const mockActivities = handlers => {
|
|
84
|
-
const activities = createActivities( handlers );
|
|
85
|
-
proxyActivitiesMock.mockReturnValue( activities );
|
|
86
|
-
return activities;
|
|
87
|
-
};
|
|
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
|
-
|
|
96
|
-
const workflowDefinition = overrides => ( {
|
|
97
|
-
name: 'test_wf',
|
|
98
|
-
description: 'Test workflow',
|
|
99
|
-
inputSchema: z.object( {} ),
|
|
100
|
-
outputSchema: z.object( {} ),
|
|
101
|
-
fn: async () => ( {} ),
|
|
102
|
-
...overrides
|
|
103
|
-
} );
|
|
104
|
-
|
|
105
|
-
describe( 'workflow()', () => {
|
|
106
|
-
beforeEach( () => {
|
|
107
|
-
vi.clearAllMocks();
|
|
108
|
-
delete globalThis[INVOKE_ACTIVITY_SYMBOL];
|
|
109
|
-
inWorkflowContextMock.mockReturnValue( true );
|
|
110
|
-
executeChildMock.mockResolvedValue( {} );
|
|
111
|
-
setWorkflowInfo();
|
|
112
|
-
mockActivities( {
|
|
113
|
-
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( { local: '/tmp/trace' } )
|
|
114
|
-
} );
|
|
115
|
-
} );
|
|
116
|
-
|
|
117
|
-
it( 'validates workflow definition at creation time', async () => {
|
|
118
|
-
const { workflow } = await import( './workflow.js' );
|
|
119
|
-
const inputSchema = z.object( {} );
|
|
120
|
-
const outputSchema = z.object( {} );
|
|
121
|
-
const fn = async () => ( {} );
|
|
122
|
-
const options = { activityOptions: { retry: { maximumAttempts: 1 } } };
|
|
123
|
-
|
|
124
|
-
workflow( {
|
|
125
|
-
name: 'definition_wf',
|
|
126
|
-
description: 'Definition workflow',
|
|
127
|
-
inputSchema,
|
|
128
|
-
outputSchema,
|
|
129
|
-
fn,
|
|
130
|
-
options,
|
|
131
|
-
aliases: [ 'old_definition_wf' ]
|
|
132
|
-
} );
|
|
133
|
-
|
|
134
|
-
expect( validateDefinitionMock ).toHaveBeenCalledWith( {
|
|
135
|
-
name: 'definition_wf',
|
|
136
|
-
description: 'Definition workflow',
|
|
137
|
-
inputSchema,
|
|
138
|
-
outputSchema,
|
|
139
|
-
fn,
|
|
140
|
-
options,
|
|
141
|
-
aliases: [ 'old_definition_wf' ]
|
|
142
|
-
} );
|
|
143
|
-
expect( validatorConstructorMock ).toHaveBeenCalledWith( {
|
|
144
|
-
name: 'definition_wf',
|
|
145
|
-
inputSchema,
|
|
146
|
-
outputSchema
|
|
147
|
-
} );
|
|
148
|
-
} );
|
|
149
|
-
|
|
150
|
-
it( 'propagates definition validation errors', async () => {
|
|
151
|
-
const { workflow } = await import( './workflow.js' );
|
|
152
|
-
const error = new ValidationError( 'invalid definition' );
|
|
153
|
-
validateDefinitionMock.mockImplementationOnce( () => {
|
|
154
|
-
throw error;
|
|
155
|
-
} );
|
|
156
|
-
|
|
157
|
-
expect( () => workflow( workflowDefinition( { name: 'invalid_name' } ) ) ).toThrow( error );
|
|
158
|
-
} );
|
|
159
|
-
|
|
160
|
-
it( 'creates a workflow component with definition metadata', async () => {
|
|
161
|
-
const { workflow } = await import( './workflow.js' );
|
|
162
|
-
const inputSchema = z.object( { value: z.string() } );
|
|
163
|
-
const outputSchema = z.object( { ok: z.boolean() } );
|
|
164
|
-
|
|
165
|
-
const wf = workflow( workflowDefinition( {
|
|
166
|
-
name: 'metadata_wf',
|
|
167
|
-
description: 'Metadata workflow',
|
|
168
|
-
inputSchema,
|
|
169
|
-
outputSchema,
|
|
170
|
-
aliases: [ 'metadata_alias' ],
|
|
171
|
-
fn: async () => ( { ok: true } )
|
|
172
|
-
} ) );
|
|
173
|
-
|
|
174
|
-
expect( createWorkflowMock ).toHaveBeenCalledWith( {
|
|
175
|
-
name: 'metadata_wf',
|
|
176
|
-
description: 'Metadata workflow',
|
|
177
|
-
inputSchema,
|
|
178
|
-
outputSchema,
|
|
179
|
-
options: {},
|
|
180
|
-
aliases: [ 'metadata_alias' ],
|
|
181
|
-
handler: expect.any( Function )
|
|
182
|
-
} );
|
|
183
|
-
expect( wf ).toBe( createWorkflowMock.mock.calls[0][0].handler );
|
|
184
|
-
} );
|
|
185
|
-
|
|
186
|
-
describe( 'outside Temporal workflow context', () => {
|
|
187
|
-
beforeEach( () => {
|
|
188
|
-
inWorkflowContextMock.mockReturnValue( false );
|
|
189
|
-
} );
|
|
190
|
-
|
|
191
|
-
it( 'runs as a plain function with real test WorkflowContext and merged extra context', async () => {
|
|
192
|
-
const { workflow } = await import( './workflow.js' );
|
|
193
|
-
|
|
194
|
-
const wf = workflow( workflowDefinition( {
|
|
195
|
-
name: 'plain_wf',
|
|
196
|
-
inputSchema: z.object( { suffix: z.string() } ),
|
|
197
|
-
outputSchema: z.object( { value: z.string(), extra: z.string() } ),
|
|
198
|
-
fn: async ( input, context ) => ( {
|
|
199
|
-
value: `${context.info.workflowId}${input.suffix}`,
|
|
200
|
-
extra: context.extra
|
|
201
|
-
} )
|
|
202
|
-
} ) );
|
|
203
|
-
|
|
204
|
-
await expect( wf( { suffix: '-ok' }, { context: { extra: 'custom' } } ) ).resolves.toEqual( {
|
|
205
|
-
value: 'test-workflow-ok',
|
|
206
|
-
extra: 'custom'
|
|
207
|
-
} );
|
|
208
|
-
expect( parseInvocationOptionsMock ).toHaveBeenCalledWith( { context: { extra: 'custom' } } );
|
|
209
|
-
expect( parseInputMock ).toHaveBeenCalledWith( { suffix: '-ok' } );
|
|
210
|
-
expect( parseOutputMock ).toHaveBeenCalledWith( { value: 'test-workflow-ok', extra: 'custom' } );
|
|
211
|
-
expect( workflowInfoMock ).not.toHaveBeenCalled();
|
|
212
|
-
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
213
|
-
} );
|
|
214
|
-
|
|
215
|
-
it( 'does not run fn when plain function input validation fails', async () => {
|
|
216
|
-
const { workflow } = await import( './workflow.js' );
|
|
217
|
-
const error = new ValidationError( 'invalid input' );
|
|
218
|
-
parseInputMock.mockImplementationOnce( () => {
|
|
219
|
-
throw error;
|
|
220
|
-
} );
|
|
221
|
-
const fn = vi.fn();
|
|
222
|
-
|
|
223
|
-
const wf = workflow( workflowDefinition( {
|
|
224
|
-
name: 'plain_validation_wf',
|
|
225
|
-
inputSchema: z.object( { value: z.string() } ),
|
|
226
|
-
outputSchema: z.object( { result: z.string() } ),
|
|
227
|
-
fn
|
|
228
|
-
} ) );
|
|
229
|
-
|
|
230
|
-
await expect( wf( { value: 1 } ) ).rejects.toBe( error );
|
|
231
|
-
expect( fn ).not.toHaveBeenCalled();
|
|
232
|
-
expect( parseOutputMock ).not.toHaveBeenCalled();
|
|
233
|
-
} );
|
|
234
|
-
|
|
235
|
-
it( 'propagates plain function output validation errors after fn runs', async () => {
|
|
236
|
-
const { workflow } = await import( './workflow.js' );
|
|
237
|
-
const error = new ValidationError( 'invalid output' );
|
|
238
|
-
parseOutputMock.mockImplementationOnce( () => {
|
|
239
|
-
throw error;
|
|
240
|
-
} );
|
|
241
|
-
const output = { result: 1 };
|
|
242
|
-
const fn = vi.fn().mockResolvedValue( output );
|
|
243
|
-
|
|
244
|
-
const wf = workflow( workflowDefinition( {
|
|
245
|
-
name: 'plain_output_validation_wf',
|
|
246
|
-
inputSchema: z.object( { value: z.string() } ),
|
|
247
|
-
outputSchema: z.object( { result: z.string() } ),
|
|
248
|
-
fn
|
|
249
|
-
} ) );
|
|
250
|
-
|
|
251
|
-
await expect( wf( { value: 'ok' } ) ).rejects.toBe( error );
|
|
252
|
-
expect( fn ).toHaveBeenCalledWith( { value: 'ok' }, expect.objectContaining( {
|
|
253
|
-
info: { workflowId: 'test-workflow', runId: 'test-run' }
|
|
254
|
-
} ) );
|
|
255
|
-
expect( parseOutputMock ).toHaveBeenCalledWith( output );
|
|
256
|
-
} );
|
|
257
|
-
} );
|
|
258
|
-
|
|
259
|
-
describe( 'child workflow trigger path', () => {
|
|
260
|
-
it( 'starts a child workflow and forwards child-safe invocation options', async () => {
|
|
261
|
-
const { workflow } = await import( './workflow.js' );
|
|
262
|
-
const { ParentClosePolicy } = await import( '@temporalio/workflow' );
|
|
263
|
-
installGlobalDispatcher( 'run-123' );
|
|
264
|
-
const memo = {
|
|
265
|
-
traceInfo: { workflowId: 'root-workflow' },
|
|
266
|
-
activityOptions: {
|
|
267
|
-
heartbeatTimeout: '30s',
|
|
268
|
-
retry: {
|
|
269
|
-
maximumInterval: '20s',
|
|
270
|
-
maximumAttempts: 4
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
setWorkflowInfo( { memo } );
|
|
275
|
-
executeChildMock.mockResolvedValueOnce( { child: 'ok' } );
|
|
276
|
-
|
|
277
|
-
const wf = workflow( workflowDefinition( {
|
|
278
|
-
name: 'child_target_wf',
|
|
279
|
-
inputSchema: z.object( { id: z.number() } ),
|
|
280
|
-
outputSchema: z.object( { child: z.string() } ),
|
|
281
|
-
fn: vi.fn()
|
|
282
|
-
} ) );
|
|
283
|
-
|
|
284
|
-
await expect( wf( { id: 1 }, {
|
|
285
|
-
detached: true,
|
|
286
|
-
context: { testOnly: true },
|
|
287
|
-
activityOptions: {
|
|
288
|
-
startToCloseTimeout: '2m',
|
|
289
|
-
retry: { maximumAttempts: 7 }
|
|
290
|
-
}
|
|
291
|
-
} ) ).resolves.toEqual( { child: 'ok' } );
|
|
292
|
-
expect( parseInvocationOptionsMock ).toHaveBeenCalledWith( {
|
|
293
|
-
detached: true,
|
|
294
|
-
context: { testOnly: true },
|
|
295
|
-
activityOptions: {
|
|
296
|
-
startToCloseTimeout: '2m',
|
|
297
|
-
retry: { maximumAttempts: 7 }
|
|
298
|
-
}
|
|
299
|
-
} );
|
|
300
|
-
|
|
301
|
-
expect( executeChildMock ).toHaveBeenCalledWith( 'child_target_wf', {
|
|
302
|
-
args: [ {
|
|
303
|
-
id: 1
|
|
304
|
-
}, {
|
|
305
|
-
activityOptions: {
|
|
306
|
-
startToCloseTimeout: '2m',
|
|
307
|
-
retry: { maximumAttempts: 7 }
|
|
308
|
-
}
|
|
309
|
-
} ],
|
|
310
|
-
workflowId: expect.stringMatching( /^workflow-123-/ ),
|
|
311
|
-
parentClosePolicy: ParentClosePolicy.ABANDON,
|
|
312
|
-
memo
|
|
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' );
|
|
316
|
-
expect( proxyActivitiesMock ).not.toHaveBeenCalled();
|
|
317
|
-
} );
|
|
318
|
-
|
|
319
|
-
it( 'uses undefined input and terminate policy by default for child workflow execution', async () => {
|
|
320
|
-
const { workflow } = await import( './workflow.js' );
|
|
321
|
-
const { ParentClosePolicy } = await import( '@temporalio/workflow' );
|
|
322
|
-
installGlobalDispatcher( 'run-123' );
|
|
323
|
-
setWorkflowInfo( { memo: { traceInfo: { workflowId: 'root-workflow' } } } );
|
|
324
|
-
executeChildMock.mockResolvedValueOnce( 'done' );
|
|
325
|
-
|
|
326
|
-
const wf = workflow( workflowDefinition( {
|
|
327
|
-
name: 'no_input_child_wf',
|
|
328
|
-
inputSchema: undefined,
|
|
329
|
-
outputSchema: z.string(),
|
|
330
|
-
fn: vi.fn()
|
|
331
|
-
} ) );
|
|
332
|
-
|
|
333
|
-
await expect( wf() ).resolves.toBe( 'done' );
|
|
334
|
-
expect( executeChildMock ).toHaveBeenCalledWith( 'no_input_child_wf', expect.objectContaining( {
|
|
335
|
-
args: [ undefined, { activityOptions: undefined } ],
|
|
336
|
-
parentClosePolicy: ParentClosePolicy.TERMINATE,
|
|
337
|
-
memo: { traceInfo: { workflowId: 'root-workflow' } }
|
|
338
|
-
} ) );
|
|
339
|
-
} );
|
|
340
|
-
|
|
341
|
-
it( 'propagates executeChild errors without root ApplicationFailure wrapping', async () => {
|
|
342
|
-
const { workflow } = await import( './workflow.js' );
|
|
343
|
-
const error = new Error( 'child failed' );
|
|
344
|
-
installGlobalDispatcher( 'run-123' );
|
|
345
|
-
executeChildMock.mockRejectedValueOnce( error );
|
|
346
|
-
|
|
347
|
-
const wf = workflow( workflowDefinition( { name: 'failing_child_wf' } ) );
|
|
348
|
-
|
|
349
|
-
await expect( wf( {} ) ).rejects.toBe( error );
|
|
350
|
-
} );
|
|
351
|
-
} );
|
|
352
|
-
|
|
353
|
-
describe( 'workflow execution path', () => {
|
|
354
|
-
it( 'validates invocation options before reading workflow information', async () => {
|
|
355
|
-
const { workflow } = await import( './workflow.js' );
|
|
356
|
-
const error = new ValidationError( 'invalid invocation options' );
|
|
357
|
-
parseInvocationOptionsMock.mockImplementationOnce( () => {
|
|
358
|
-
throw error;
|
|
359
|
-
} );
|
|
360
|
-
const wf = workflow( workflowDefinition() );
|
|
361
|
-
|
|
362
|
-
await expect( wf( {}, { invalid: true } ) ).rejects.toBe( error );
|
|
363
|
-
|
|
364
|
-
expect( upsertMemoMock ).not.toHaveBeenCalled();
|
|
365
|
-
expect( workflowInfoMock ).not.toHaveBeenCalled();
|
|
366
|
-
} );
|
|
367
|
-
|
|
368
|
-
it( 'rejects a global activity dispatcher left by another workflow run', async () => {
|
|
369
|
-
const { workflow } = await import( './workflow.js' );
|
|
370
|
-
const fn = vi.fn().mockResolvedValue( {} );
|
|
371
|
-
installGlobalDispatcher( 'stale-run' );
|
|
372
|
-
setWorkflowInfo( { runId: 'current-run' } );
|
|
373
|
-
|
|
374
|
-
const wf = workflow( workflowDefinition( {
|
|
375
|
-
name: 'contamination_wf',
|
|
376
|
-
fn
|
|
377
|
-
} ) );
|
|
378
|
-
|
|
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();
|
|
383
|
-
} );
|
|
384
|
-
|
|
385
|
-
it( 'skips trace destinations when trace is disabled and returns raw output', async () => {
|
|
386
|
-
const { workflow } = await import( './workflow.js' );
|
|
387
|
-
const getTraceDestinations = vi.fn().mockResolvedValue( { local: '/tmp/root-trace' } );
|
|
388
|
-
const info = setWorkflowInfo( { workflowType: 'root_wf', memo: {} } );
|
|
389
|
-
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: getTraceDestinations } );
|
|
390
|
-
|
|
391
|
-
const wf = workflow( workflowDefinition( {
|
|
392
|
-
name: 'root_wf',
|
|
393
|
-
outputSchema: z.object( { ok: z.boolean() } ),
|
|
394
|
-
options: {
|
|
395
|
-
disableTrace: true,
|
|
396
|
-
activityOptions: {
|
|
397
|
-
startToCloseTimeout: '5m',
|
|
398
|
-
retry: { maximumAttempts: 5 }
|
|
399
|
-
}
|
|
400
|
-
},
|
|
401
|
-
fn: async ( _, context ) => ( { ok: context.info.workflowId === 'workflow-123' } )
|
|
402
|
-
} ) );
|
|
403
|
-
|
|
404
|
-
await expect( wf( {} ) ).resolves.toEqual( { ok: true } );
|
|
405
|
-
expect( info.memo.traceInfo ).toBeUndefined();
|
|
406
|
-
expect( info.memo.activityOptions ).toBeUndefined();
|
|
407
|
-
expect( proxyActivitiesMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
408
|
-
startToCloseTimeout: '5m',
|
|
409
|
-
heartbeatTimeout: '5m',
|
|
410
|
-
retry: expect.objectContaining( { maximumAttempts: 5 } )
|
|
411
|
-
} ) );
|
|
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
|
-
} );
|
|
474
|
-
} );
|
|
475
|
-
|
|
476
|
-
it( 'runs non-root workflow execution without rebuilding trace info or fetching trace destinations', async () => {
|
|
477
|
-
const { workflow } = await import( './workflow.js' );
|
|
478
|
-
const getTraceDestinations = vi.fn().mockResolvedValue( { local: '/tmp/trace' } );
|
|
479
|
-
const memo = {
|
|
480
|
-
traceInfo: { workflowId: 'root-workflow' },
|
|
481
|
-
activityOptions: {
|
|
482
|
-
startToCloseTimeout: '9m',
|
|
483
|
-
retry: { maximumAttempts: 8 }
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: getTraceDestinations } );
|
|
487
|
-
const info = setWorkflowInfo( {
|
|
488
|
-
workflowId: 'child-workflow',
|
|
489
|
-
root: { workflowId: 'root-workflow', runId: 'root-run' },
|
|
490
|
-
memo
|
|
491
|
-
} );
|
|
492
|
-
|
|
493
|
-
const wf = workflow( workflowDefinition( {
|
|
494
|
-
name: 'nested_wf',
|
|
495
|
-
options: {
|
|
496
|
-
activityOptions: {
|
|
497
|
-
startToCloseTimeout: '1m',
|
|
498
|
-
retry: { maximumAttempts: 2 }
|
|
499
|
-
}
|
|
500
|
-
},
|
|
501
|
-
outputSchema: z.object( { ok: z.boolean() } ),
|
|
502
|
-
fn: async () => ( { ok: true } )
|
|
503
|
-
} ) );
|
|
504
|
-
|
|
505
|
-
await expect( wf( {} ) ).resolves.toEqual( { ok: true } );
|
|
506
|
-
expect( info.memo.traceInfo ).toBe( memo.traceInfo );
|
|
507
|
-
expect( proxyActivitiesMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
508
|
-
startToCloseTimeout: '1m',
|
|
509
|
-
heartbeatTimeout: '5m',
|
|
510
|
-
retry: expect.objectContaining( { maximumAttempts: 2 } )
|
|
511
|
-
} ) );
|
|
512
|
-
expect( getTraceDestinations ).not.toHaveBeenCalled();
|
|
513
|
-
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
514
|
-
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
515
|
-
activityOptions: proxyActivitiesMock.mock.calls[0][0]
|
|
516
|
-
} );
|
|
517
|
-
} );
|
|
518
|
-
|
|
519
|
-
it( 'stores empty trace destinations in memo and returns raw output', async () => {
|
|
520
|
-
const { workflow } = await import( './workflow.js' );
|
|
521
|
-
setWorkflowInfo( {
|
|
522
|
-
workflowType: 'no_trace_dest_wf',
|
|
523
|
-
memo: { traceInfo: { workflowId: 'workflow-123' } }
|
|
524
|
-
} );
|
|
525
|
-
mockActivities( { [ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( {} ) } );
|
|
526
|
-
|
|
527
|
-
const wf = workflow( workflowDefinition( {
|
|
528
|
-
name: 'no_trace_dest_wf',
|
|
529
|
-
outputSchema: z.object( { ok: z.boolean() } ),
|
|
530
|
-
fn: async () => ( { ok: true } )
|
|
531
|
-
} ) );
|
|
532
|
-
|
|
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: {}
|
|
538
|
-
} );
|
|
539
|
-
} );
|
|
540
|
-
|
|
541
|
-
it( 'validates input and output inside workflow context', async () => {
|
|
542
|
-
const { workflow } = await import( './workflow.js' );
|
|
543
|
-
const inputError = new ValidationError( 'invalid workflow input' );
|
|
544
|
-
const outputError = new ValidationError( 'invalid workflow output' );
|
|
545
|
-
setWorkflowInfo( { workflowType: 'runtime_validation_wf' } );
|
|
546
|
-
|
|
547
|
-
const wf = workflow( workflowDefinition( {
|
|
548
|
-
name: 'runtime_validation_wf',
|
|
549
|
-
inputSchema: z.object( { value: z.string() } ),
|
|
550
|
-
outputSchema: z.object( { result: z.string() } ),
|
|
551
|
-
fn: async () => ( { result: 1 } )
|
|
552
|
-
} ) );
|
|
553
|
-
|
|
554
|
-
parseInputMock.mockImplementationOnce( () => {
|
|
555
|
-
throw inputError;
|
|
556
|
-
} );
|
|
557
|
-
await expect( wf( { value: 1 } ) ).rejects.toBe( inputError );
|
|
558
|
-
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
559
|
-
trace: { local: '/tmp/trace' }
|
|
560
|
-
} ) );
|
|
561
|
-
|
|
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( () => {
|
|
569
|
-
throw outputError;
|
|
570
|
-
} );
|
|
571
|
-
await expect( wf( { value: 'ok' } ) ).rejects.toBe( outputError );
|
|
572
|
-
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
573
|
-
trace: { local: '/tmp/trace' }
|
|
574
|
-
} ) );
|
|
575
|
-
} );
|
|
576
|
-
} );
|
|
577
|
-
|
|
578
|
-
describe( 'activity dispatchers', () => {
|
|
579
|
-
it( 'invokes workflow-scoped activities and returns their raw outputs', async () => {
|
|
580
|
-
const { workflow } = await import( './workflow.js' );
|
|
581
|
-
setWorkflowInfo( { workflowType: 'dispatch_wf' } );
|
|
582
|
-
const step = vi.fn().mockResolvedValue( 'step-output' );
|
|
583
|
-
const evaluator = vi.fn().mockResolvedValue( 'eval-output' );
|
|
584
|
-
mockActivities( {
|
|
585
|
-
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( {} ),
|
|
586
|
-
'dispatch_wf#stepA': step,
|
|
587
|
-
'dispatch_wf#evalA': evaluator
|
|
588
|
-
} );
|
|
589
|
-
|
|
590
|
-
const wf = workflow( workflowDefinition( {
|
|
591
|
-
name: 'dispatch_wf',
|
|
592
|
-
outputSchema: z.object( { stepResult: z.string(), evalResult: z.string() } ),
|
|
593
|
-
fn: async () => {
|
|
594
|
-
return {
|
|
595
|
-
stepResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'stepA', { a: 1 }, { b: 2 } ),
|
|
596
|
-
evalResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'evalA', { c: 3 } )
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
} ) );
|
|
600
|
-
|
|
601
|
-
await expect( wf( {} ) ).resolves.toEqual( { stepResult: 'step-output', evalResult: 'eval-output' } );
|
|
602
|
-
expect( step ).toHaveBeenCalledWith( { a: 1 }, { b: 2 } );
|
|
603
|
-
expect( evaluator ).toHaveBeenCalledWith( { c: 3 } );
|
|
604
|
-
} );
|
|
605
|
-
|
|
606
|
-
it( 'invokes shared activities through the workflow namespace', async () => {
|
|
607
|
-
const { workflow } = await import( './workflow.js' );
|
|
608
|
-
setWorkflowInfo( { workflowType: 'shared_dispatch_wf' } );
|
|
609
|
-
const sharedStep = vi.fn().mockResolvedValue( 'shared-step-output' );
|
|
610
|
-
const sharedEvaluator = vi.fn().mockResolvedValue( 'shared-eval-output' );
|
|
611
|
-
mockActivities( {
|
|
612
|
-
[ACTIVITY_GET_TRACE_DESTINATIONS]: vi.fn().mockResolvedValue( {} ),
|
|
613
|
-
'shared_dispatch_wf#stepA': sharedStep,
|
|
614
|
-
'shared_dispatch_wf#evalA': sharedEvaluator
|
|
615
|
-
} );
|
|
616
|
-
|
|
617
|
-
const wf = workflow( workflowDefinition( {
|
|
618
|
-
name: 'shared_dispatch_wf',
|
|
619
|
-
outputSchema: z.object( { stepResult: z.string(), evalResult: z.string() } ),
|
|
620
|
-
fn: async () => {
|
|
621
|
-
return {
|
|
622
|
-
stepResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'stepA' ),
|
|
623
|
-
evalResult: await globalThis[INVOKE_ACTIVITY_SYMBOL]( 'evalA', { x: 1 } )
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
} ) );
|
|
627
|
-
|
|
628
|
-
await expect( wf( {} ) ).resolves.toEqual( {
|
|
629
|
-
stepResult: 'shared-step-output',
|
|
630
|
-
evalResult: 'shared-eval-output'
|
|
631
|
-
} );
|
|
632
|
-
expect( sharedStep ).toHaveBeenCalledWith();
|
|
633
|
-
expect( sharedEvaluator ).toHaveBeenCalledWith( { x: 1 } );
|
|
634
|
-
} );
|
|
635
|
-
} );
|
|
636
|
-
|
|
637
|
-
describe( 'error handling', () => {
|
|
638
|
-
it( 'stores root trace destinations in memo and preserves workflow errors', async () => {
|
|
639
|
-
const { workflow } = await import( './workflow.js' );
|
|
640
|
-
const error = new Error( 'root failed with details' );
|
|
641
|
-
error.details = [ { domain: { reason: 'bad-input' } } ];
|
|
642
|
-
setWorkflowInfo( { workflowType: 'root_error_wf' } );
|
|
643
|
-
|
|
644
|
-
const wf = workflow( workflowDefinition( {
|
|
645
|
-
name: 'root_error_wf',
|
|
646
|
-
fn: async () => {
|
|
647
|
-
throw error;
|
|
648
|
-
}
|
|
649
|
-
} ) );
|
|
650
|
-
|
|
651
|
-
const thrown = await wf( {} ).catch( e => e );
|
|
652
|
-
expect( thrown ).toBe( error );
|
|
653
|
-
expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
|
|
654
|
-
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
655
|
-
trace: { local: '/tmp/trace' }
|
|
656
|
-
} ) );
|
|
657
|
-
} );
|
|
658
|
-
|
|
659
|
-
it( 'preserves existing root ApplicationFailure metadata', async () => {
|
|
660
|
-
const { workflow } = await import( './workflow.js' );
|
|
661
|
-
const { ApplicationFailure } = await import( '@temporalio/workflow' );
|
|
662
|
-
setWorkflowInfo( { workflowType: 'root_application_failure_wf' } );
|
|
663
|
-
const error = ApplicationFailure.create( {
|
|
664
|
-
message: 'root application failed',
|
|
665
|
-
type: 'OriginalType',
|
|
666
|
-
nonRetryable: true,
|
|
667
|
-
details: [ { domain: { reason: 'bad-input' } } ]
|
|
668
|
-
} );
|
|
669
|
-
|
|
670
|
-
const wf = workflow( workflowDefinition( {
|
|
671
|
-
name: 'root_application_failure_wf',
|
|
672
|
-
fn: async () => {
|
|
673
|
-
throw error;
|
|
674
|
-
}
|
|
675
|
-
} ) );
|
|
676
|
-
|
|
677
|
-
const thrown = await wf( {} ).catch( e => e );
|
|
678
|
-
|
|
679
|
-
expect( thrown ).toBe( error );
|
|
680
|
-
expect( error.type ).toBe( 'OriginalType' );
|
|
681
|
-
expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
|
|
682
|
-
expect( upsertMemoMock ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
683
|
-
trace: { local: '/tmp/trace' }
|
|
684
|
-
} ) );
|
|
685
|
-
} );
|
|
686
|
-
|
|
687
|
-
it( 'rethrows non-root workflow errors without ApplicationFailure wrapping', async () => {
|
|
688
|
-
const { workflow } = await import( './workflow.js' );
|
|
689
|
-
const error = new Error( 'nested failed' );
|
|
690
|
-
setWorkflowInfo( {
|
|
691
|
-
workflowId: 'nested-workflow',
|
|
692
|
-
root: { workflowId: 'root-workflow', runId: 'root-run' },
|
|
693
|
-
memo: { traceInfo: { workflowId: 'root-workflow' } }
|
|
694
|
-
} );
|
|
695
|
-
|
|
696
|
-
const wf = workflow( workflowDefinition( {
|
|
697
|
-
name: 'nested_error_wf',
|
|
698
|
-
fn: async () => {
|
|
699
|
-
throw error;
|
|
700
|
-
}
|
|
701
|
-
} ) );
|
|
702
|
-
|
|
703
|
-
await expect( wf( {} ) ).rejects.toBe( error );
|
|
704
|
-
expect( upsertMemoMock ).toHaveBeenCalledOnce();
|
|
705
|
-
expect( upsertMemoMock ).toHaveBeenCalledWith( {
|
|
706
|
-
activityOptions: proxyActivitiesMock.mock.calls[0][0]
|
|
707
|
-
} );
|
|
708
|
-
} );
|
|
709
|
-
} );
|
|
710
|
-
} );
|