@outputai/core 0.14.0 → 0.14.1-next.2947341.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.
Files changed (100) hide show
  1. package/dist/credentials/credentials.d.ts +6 -0
  2. package/dist/credentials/credentials.js +87 -0
  3. package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
  4. package/dist/credentials/encrypted_yaml_provider.js +77 -0
  5. package/dist/credentials/encryption.d.ts +3 -0
  6. package/dist/credentials/encryption.js +6 -0
  7. package/dist/credentials/errors.d.ts +13 -0
  8. package/dist/credentials/errors.js +39 -0
  9. package/dist/credentials/index.d.ts +6 -0
  10. package/dist/credentials/index.js +5 -0
  11. package/dist/credentials/init.d.ts +1 -0
  12. package/dist/credentials/init.js +7 -0
  13. package/dist/credentials/paths.d.ts +7 -0
  14. package/dist/credentials/paths.js +14 -0
  15. package/dist/credentials/provider_registry.d.ts +3 -0
  16. package/dist/credentials/provider_registry.js +6 -0
  17. package/dist/credentials/types.d.ts +12 -0
  18. package/dist/credentials/types.js +1 -0
  19. package/package.json +25 -14
  20. package/src/async_storage.d.ts +4 -0
  21. package/src/consts.js +0 -2
  22. package/src/helpers/object.d.ts +2 -0
  23. package/src/hooks/index.d.ts +0 -8
  24. package/src/hooks/index.js +0 -3
  25. package/src/logger/index.d.ts +4 -0
  26. package/src/worker/index.js +3 -1
  27. package/bin/healthcheck.spec.js +0 -90
  28. package/src/bus.spec.js +0 -196
  29. package/src/errors.spec.js +0 -19
  30. package/src/helpers/activity_options.spec.js +0 -55
  31. package/src/helpers/component.spec.js +0 -54
  32. package/src/helpers/error_serializer.spec.js +0 -479
  33. package/src/helpers/errors.spec.js +0 -189
  34. package/src/helpers/fetch.spec.js +0 -292
  35. package/src/helpers/function.spec.js +0 -24
  36. package/src/helpers/object.spec.js +0 -425
  37. package/src/helpers/promise.spec.js +0 -62
  38. package/src/helpers/redact.spec.js +0 -66
  39. package/src/helpers/string.spec.js +0 -99
  40. package/src/helpers/temporal_context.spec.ts +0 -83
  41. package/src/helpers/trace_info.spec.js +0 -45
  42. package/src/helpers/workflow_context.spec.js +0 -46
  43. package/src/hooks/index.spec.js +0 -265
  44. package/src/hooks/pending_hooks.spec.js +0 -62
  45. package/src/interface/evaluator.spec.js +0 -685
  46. package/src/interface/logger.spec.js +0 -172
  47. package/src/interface/step.spec.js +0 -128
  48. package/src/interface/validations/index.spec.js +0 -197
  49. package/src/interface/validations/schemas.spec.js +0 -229
  50. package/src/interface/webhook.spec.js +0 -170
  51. package/src/interface/workflow.spec.js +0 -710
  52. package/src/interface/workflow_utils.spec.js +0 -271
  53. package/src/interface/zod_integration.spec.js +0 -646
  54. package/src/internal_activities/index.spec.js +0 -186
  55. package/src/logger/development.spec.js +0 -87
  56. package/src/logger/index.spec.js +0 -27
  57. package/src/logger/production.spec.js +0 -71
  58. package/src/sdk/helpers/component_metadata.spec.js +0 -30
  59. package/src/sdk/helpers/objects.spec.js +0 -16
  60. package/src/sdk/helpers/path.spec.js +0 -102
  61. package/src/sdk/runtime/context.spec.js +0 -37
  62. package/src/sdk/runtime/events.spec.js +0 -40
  63. package/src/temporal/client.spec.js +0 -124
  64. package/src/tracing/processors/local/index.spec.js +0 -204
  65. package/src/tracing/processors/s3/configs.spec.js +0 -64
  66. package/src/tracing/processors/s3/index.spec.js +0 -185
  67. package/src/tracing/processors/s3/redis_client.spec.js +0 -179
  68. package/src/tracing/processors/s3/s3_client.spec.js +0 -68
  69. package/src/tracing/tools/build_trace_tree.spec.js +0 -216
  70. package/src/tracing/trace_engine.spec.js +0 -299
  71. package/src/worker/bundle.spec.js +0 -53
  72. package/src/worker/bundler_options.spec.js +0 -62
  73. package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
  74. package/src/worker/catalog_workflow/index.spec.js +0 -222
  75. package/src/worker/configs.spec.js +0 -213
  76. package/src/worker/configs_tuner_schema.spec.js +0 -127
  77. package/src/worker/connection_monitor.spec.js +0 -328
  78. package/src/worker/global_functions.spec.js +0 -55
  79. package/src/worker/index.spec.js +0 -526
  80. package/src/worker/interceptors/activity.spec.js +0 -369
  81. package/src/worker/interceptors/headers.spec.js +0 -73
  82. package/src/worker/interceptors/workflow.spec.js +0 -357
  83. package/src/worker/interruption.spec.js +0 -177
  84. package/src/worker/loader/activities.spec.js +0 -212
  85. package/src/worker/loader/hooks.spec.js +0 -64
  86. package/src/worker/loader/matchers.spec.js +0 -140
  87. package/src/worker/loader/tools.spec.js +0 -595
  88. package/src/worker/loader/workflows.spec.js +0 -287
  89. package/src/worker/log_hooks.spec.js +0 -303
  90. package/src/worker/shutdown.spec.js +0 -197
  91. package/src/worker/sinks.spec.js +0 -220
  92. package/src/worker/telemetry.spec.js +0 -80
  93. package/src/worker/temporal_logger.spec.js +0 -90
  94. package/src/worker/webpack_loaders/tools.spec.js +0 -314
  95. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
  96. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
  97. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
  98. package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
  99. package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
  100. 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
- } );