@outputai/core 0.10.1-next.fc0a41f.0 → 0.11.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 (63) hide show
  1. package/package.json +12 -7
  2. package/src/consts.js +5 -2
  3. package/src/errors.d.ts +12 -2
  4. package/src/errors.js +17 -3
  5. package/src/errors.spec.js +19 -0
  6. package/src/helpers/activity_options.js +21 -0
  7. package/src/helpers/activity_options.spec.js +55 -0
  8. package/src/helpers/error_serializer.js +236 -0
  9. package/src/helpers/error_serializer.spec.js +413 -0
  10. package/src/helpers/errors.js +55 -13
  11. package/src/helpers/errors.spec.js +166 -66
  12. package/src/helpers/fetch.js +1 -38
  13. package/src/helpers/fetch.spec.js +1 -39
  14. package/src/helpers/function.js +13 -0
  15. package/src/helpers/function.spec.js +23 -1
  16. package/src/helpers/redact.js +51 -0
  17. package/src/helpers/redact.spec.js +66 -0
  18. package/src/helpers/string.js +10 -0
  19. package/src/helpers/string.spec.js +36 -1
  20. package/src/hooks/index.d.ts +35 -44
  21. package/src/hooks/index.js +2 -1
  22. package/src/hooks/index.spec.js +15 -7
  23. package/src/interface/evaluator.d.ts +10 -9
  24. package/src/interface/evaluator.js +1 -6
  25. package/src/interface/evaluator.spec.js +10 -10
  26. package/src/interface/step.d.ts +16 -15
  27. package/src/interface/step.js +1 -6
  28. package/src/interface/step.spec.js +10 -10
  29. package/src/interface/validations/index.js +33 -53
  30. package/src/interface/validations/index.spec.js +25 -17
  31. package/src/interface/webhook.js +1 -2
  32. package/src/interface/webhook.spec.js +25 -19
  33. package/src/interface/workflow.d.ts +25 -18
  34. package/src/interface/workflow.js +24 -52
  35. package/src/interface/workflow.spec.js +109 -108
  36. package/src/interface/workflow_activity_options.js +2 -5
  37. package/src/temporal/client.js +47 -0
  38. package/src/temporal/client.spec.js +124 -0
  39. package/src/temporal/index.d.ts +32 -0
  40. package/src/temporal/index.js +1 -0
  41. package/src/tracing/trace_engine.js +9 -11
  42. package/src/tracing/trace_engine.spec.js +67 -28
  43. package/src/worker/catalog_workflow/catalog_job.js +3 -3
  44. package/src/worker/catalog_workflow/catalog_job.spec.js +1 -1
  45. package/src/worker/index.js +10 -1
  46. package/src/worker/index.spec.js +23 -8
  47. package/src/worker/interceptors/activity.js +32 -19
  48. package/src/worker/interceptors/activity.spec.js +71 -166
  49. package/src/worker/interceptors/workflow.js +53 -11
  50. package/src/worker/interceptors/workflow.spec.js +176 -49
  51. package/src/worker/loader/workflows.js +16 -3
  52. package/src/worker/loader/workflows.spec.js +37 -5
  53. package/src/worker/log_hooks.js +13 -10
  54. package/src/worker/log_hooks.spec.js +12 -4
  55. package/src/worker/sinks.js +3 -1
  56. package/src/worker/sinks.spec.js +21 -4
  57. package/src/worker/telemetry.js +2 -1
  58. package/src/worker/temporal_logger.js +33 -0
  59. package/src/worker/temporal_logger.spec.js +90 -0
  60. package/src/helpers/aggregations.js +0 -44
  61. package/src/helpers/aggregations.spec.js +0 -92
  62. package/src/tracing/tools/utils.js +0 -42
  63. package/src/tracing/tools/utils.spec.js +0 -83
@@ -53,10 +53,12 @@ describe( 'interface validators', () => {
53
53
  it( 'validates workflow definitions, input, output, and invocation options', () => {
54
54
  expect( () => WorkflowValidator.validateDefinition( workflowArgs ) ).not.toThrow();
55
55
  const validator = new WorkflowValidator( workflowArgs );
56
+ const input = { value: 'ok' };
57
+ const output = { result: 'ok' };
56
58
 
57
- expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
58
- expect( () => validator.validateOutput( { result: 'ok' } ) ).not.toThrow();
59
- expect( () => validator.validateInvocationOptions( {
59
+ expect( validator.parseInput( input ) ).toEqual( input );
60
+ expect( validator.parseOutput( output ) ).toEqual( output );
61
+ expect( () => validator.parseInvocationOptions( {
60
62
  detached: true,
61
63
  activityOptions: { retry: { maximumAttempts: 1 } },
62
64
  context: { info: { workflowId: 'test-workflow' } }
@@ -71,10 +73,10 @@ describe( 'interface validators', () => {
71
73
 
72
74
  const validator = new WorkflowValidator( workflowArgs );
73
75
 
74
- expect( () => validator.validateInput( { value: 1 } ) ).toThrow( ValidationError );
75
- expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Workflow "valid_workflow" input validation failed/ );
76
- expect( () => validator.validateOutput( { result: 1 } ) ).toThrow( /Workflow "valid_workflow" output validation failed/ );
77
- expect( () => validator.validateInvocationOptions( {
76
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( ValidationError );
77
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( /Workflow "valid_workflow" input validation failed/ );
78
+ expect( () => validator.parseOutput( { result: 1 } ) ).toThrow( /Workflow "valid_workflow" output validation failed/ );
79
+ expect( () => validator.parseInvocationOptions( {
78
80
  options: { activityOptions: { retry: { maximumAttempts: 1 } } }
79
81
  } ) ).toThrow( /Workflow "valid_workflow" invocation options validation failed/ );
80
82
  } );
@@ -84,9 +86,11 @@ describe( 'interface validators', () => {
84
86
  name: 'schema_less_workflow',
85
87
  fn
86
88
  } );
89
+ const input = { anything: true };
90
+ const output = { anything: true };
87
91
 
88
- expect( () => validator.validateInput( { anything: true } ) ).not.toThrow();
89
- expect( () => validator.validateOutput( { anything: true } ) ).not.toThrow();
92
+ expect( validator.parseInput( input ) ).toBe( input );
93
+ expect( validator.parseOutput( output ) ).toBe( output );
90
94
  } );
91
95
  } );
92
96
 
@@ -94,9 +98,11 @@ describe( 'interface validators', () => {
94
98
  it( 'validates step definitions, input, and output', () => {
95
99
  expect( () => StepValidator.validateDefinition( stepArgs ) ).not.toThrow();
96
100
  const validator = new StepValidator( stepArgs );
101
+ const input = { value: 'ok' };
102
+ const output = { result: 'ok' };
97
103
 
98
- expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
99
- expect( () => validator.validateOutput( { result: 'ok' } ) ).not.toThrow();
104
+ expect( validator.parseInput( input ) ).toEqual( input );
105
+ expect( validator.parseOutput( output ) ).toEqual( output );
100
106
  } );
101
107
 
102
108
  it( 'throws ValidationError with useful prefixes for invalid step data', () => {
@@ -106,8 +112,8 @@ describe( 'interface validators', () => {
106
112
 
107
113
  const validator = new StepValidator( stepArgs );
108
114
 
109
- expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Step "valid_step" input validation failed/ );
110
- expect( () => validator.validateOutput( { result: 1 } ) ).toThrow( /Step "valid_step" output validation failed/ );
115
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( /Step "valid_step" input validation failed/ );
116
+ expect( () => validator.parseOutput( { result: 1 } ) ).toThrow( /Step "valid_step" output validation failed/ );
111
117
  } );
112
118
  } );
113
119
 
@@ -115,9 +121,11 @@ describe( 'interface validators', () => {
115
121
  it( 'validates evaluator definitions, input, and EvaluationResult output', () => {
116
122
  expect( () => EvaluatorValidator.validateDefinition( evaluatorArgs ) ).not.toThrow();
117
123
  const validator = new EvaluatorValidator( evaluatorArgs );
124
+ const input = { value: 'ok' };
125
+ const output = new EvaluationResult( { value: 'pass', confidence: 1 } );
118
126
 
119
- expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
120
- expect( () => validator.validateOutput( new EvaluationResult( { value: 'pass', confidence: 1 } ) ) ).not.toThrow();
127
+ expect( validator.parseInput( input ) ).toEqual( input );
128
+ expect( validator.parseOutput( output ) ).toEqual( output );
121
129
  } );
122
130
 
123
131
  it( 'throws ValidationError for output schemas and invalid evaluator output', () => {
@@ -128,8 +136,8 @@ describe( 'interface validators', () => {
128
136
 
129
137
  const validator = new EvaluatorValidator( evaluatorArgs );
130
138
 
131
- expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Evaluator "valid_evaluator" input validation failed/ );
132
- expect( () => validator.validateOutput( { value: 'pass', confidence: 1 } ) ).toThrow(
139
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( /Evaluator "valid_evaluator" input validation failed/ );
140
+ expect( () => validator.parseOutput( { value: 'pass', confidence: 1 } ) ).toThrow(
133
141
  /Evaluator "valid_evaluator" output validation failed/
134
142
  );
135
143
  } );
@@ -19,7 +19,7 @@ import { validateRequestPayload } from './validations/index.js';
19
19
  */
20
20
  export async function sendHttpRequest( { url, method = 'GET', payload = undefined, headers = undefined, responseOptions = {} } ) {
21
21
  validateRequestPayload( { method, url, payload, headers, responseOptions } );
22
- const res = await proxyActivities( {
22
+ return proxyActivities( {
23
23
  startToCloseTimeout: '3m',
24
24
  retry: {
25
25
  initialInterval: '15s',
@@ -27,7 +27,6 @@ export async function sendHttpRequest( { url, method = 'GET', payload = undefine
27
27
  nonRetryableErrorTypes: [ FatalError.name ]
28
28
  }
29
29
  } )[ACTIVITY_SEND_HTTP_REQUEST]( { method, url, payload, headers, responseOptions } );
30
- return res.output;
31
30
  };
32
31
 
33
32
  /**
@@ -1,24 +1,14 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
-
3
- // Mocks for module aliases used by webhook.js
4
- vi.mock( '#consts', () => ( {
5
- ACTIVITY_SEND_HTTP_REQUEST: '__internal#sendHttpRequest'
6
- } ) );
2
+ import { ACTIVITY_SEND_HTTP_REQUEST } from '#consts';
7
3
 
8
4
  const validateRequestPayloadMock = vi.fn();
9
5
  vi.mock( './validations/index.js', () => ( {
10
6
  validateRequestPayload: validateRequestPayloadMock
11
7
  } ) );
12
8
 
13
- const activityEnvelope = output => ( {
14
- __output_activity_wrapper_version: 1,
15
- output,
16
- aggregations: null
17
- } );
18
-
19
9
  // Minimal, legible mock of @temporalio/workflow APIs used by webhook.js
20
10
  const activityFnMock = vi.fn();
21
- const proxyActivitiesMock = vi.fn( () => ( { ['__internal#sendHttpRequest']: activityFnMock } ) );
11
+ const proxyActivitiesMock = vi.fn( () => ( { [ACTIVITY_SEND_HTTP_REQUEST]: activityFnMock } ) );
22
12
 
23
13
  const storedHandlers = new Map();
24
14
  const defineSignalMock = name => name;
@@ -76,7 +66,7 @@ describe( 'interface/webhook', () => {
76
66
  headers: { 'content-type': 'application/json' },
77
67
  body: { ok: true }
78
68
  };
79
- activityFnMock.mockResolvedValueOnce( activityEnvelope( fakeSerializedResponse ) );
69
+ activityFnMock.mockResolvedValueOnce( fakeSerializedResponse );
80
70
 
81
71
  const args = { url: 'https://example.com/api', method: 'GET' };
82
72
  const res = await sendHttpRequest( args );
@@ -112,7 +102,7 @@ describe( 'interface/webhook', () => {
112
102
  it( 'sendHttpRequest forwards response options', async () => {
113
103
  const { sendHttpRequest } = await import( './webhook.js' );
114
104
 
115
- activityFnMock.mockResolvedValueOnce( activityEnvelope( { ok: true } ) );
105
+ activityFnMock.mockResolvedValueOnce( { ok: true } );
116
106
 
117
107
  const args = {
118
108
  url: 'https://example.com/api',
@@ -137,15 +127,14 @@ describe( 'interface/webhook', () => {
137
127
  it( 'sendPostRequestAndAwaitWebhook posts wrapped payload and resolves on resume signal', async () => {
138
128
  const { sendPostRequestAndAwaitWebhook } = await import( './webhook.js' );
139
129
 
140
- // Make the inner activity resolve (through sendHttpRequest)
141
- activityFnMock.mockResolvedValueOnce( activityEnvelope( {
130
+ activityFnMock.mockResolvedValueOnce( {
142
131
  url: 'https://webhook.site',
143
132
  status: 200,
144
133
  statusText: 'OK',
145
134
  ok: true,
146
135
  headers: {},
147
136
  body: null
148
- } ) );
137
+ } );
149
138
 
150
139
  const url = 'https://webhook.site/ingest';
151
140
  const promise = sendPostRequestAndAwaitWebhook( { url, payload: { x: 1 }, headers: { a: 'b' } } );
@@ -158,7 +147,24 @@ describe( 'interface/webhook', () => {
158
147
  expect( callArgs.headers ).toEqual( { a: 'b' } );
159
148
  expect( callArgs.responseOptions ).toEqual( {} );
160
149
 
161
- // Returns a promise (async function) for the eventual webhook result
162
- expect( typeof promise.then ).toBe( 'function' );
150
+ await vi.waitFor( () => expect( storedHandlers.has( 'resume' ) ).toBe( true ) );
151
+
152
+ expect( sinks.trace.start ).toHaveBeenCalledWith( {
153
+ id: 'wf-123-https://webhook.site/ingest-uuid-mock',
154
+ name: 'resume',
155
+ kind: 'webhook'
156
+ } );
157
+
158
+ const webhookPayload = { status: 'completed' };
159
+ storedHandlers.get( 'resume' )( webhookPayload );
160
+
161
+ await expect( promise ).resolves.toEqual( webhookPayload );
162
+ expect( sinks.trace.end ).toHaveBeenCalledWith( {
163
+ id: 'wf-123-https://webhook.site/ingest-uuid-mock',
164
+ details: webhookPayload
165
+ } );
166
+
167
+ storedHandlers.get( 'resume' )( { status: 'duplicate' } );
168
+ expect( sinks.trace.end ).toHaveBeenCalledTimes( 1 );
163
169
  } );
164
170
  } );
@@ -21,6 +21,8 @@ export type WorkflowContext<
21
21
  * It acts as a checkpoint when the workflow gets too long or approaches certain scaling limits.
22
22
  *
23
23
  * It accepts input with the same schema as the parent workflow function (`inputSchema`).
24
+ * The next run parses that input like any other workflow start; pass wire-format values if
25
+ * `inputSchema` transforms are not safe to apply twice.
24
26
  *
25
27
  * Calling this function must be the last statement in the workflow, accompanied by a `return`:
26
28
  *
@@ -38,7 +40,7 @@ export type WorkflowContext<
38
40
  * @returns The workflow output type for type-checking; never returns at runtime.
39
41
  */
40
42
  continueAsNew: InputSchema extends AnyZodSchema ?
41
- ( input: z.infer<InputSchema> ) => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ) :
43
+ ( input: z.input<InputSchema> ) => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ) :
42
44
  () => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ),
43
45
 
44
46
  /**
@@ -122,39 +124,44 @@ export type WorkflowOptions = {
122
124
  /**
123
125
  * The handler function of a workflow.
124
126
  *
125
- * @param input - The workflow input; it matches the schema defined by `inputSchema`.
127
+ * @param input - Parsed workflow input (`z.infer<inputSchema>`).
126
128
  * @param context - A context object with tools and information.
127
129
  *
128
- * @returns A value matching the schema defined by `outputSchema`.
130
+ * @returns A value accepted by `outputSchema` before parse (`z.input<outputSchema>`).
129
131
  */
130
132
  export type WorkflowFunction<
131
133
  InputSchema extends AnyZodSchema | undefined = undefined,
132
134
  OutputSchema extends AnyZodSchema | undefined = undefined
133
135
  > = InputSchema extends AnyZodSchema ?
134
136
  ( input: z.infer<InputSchema>, context: WorkflowContext<InputSchema, OutputSchema> ) =>
135
- Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
137
+ Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void> :
136
138
  ( input: undefined | null, context: WorkflowContext<InputSchema, OutputSchema> ) =>
137
- Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
139
+ Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void>;
138
140
 
139
141
  /**
140
142
  * A wrapper around the user defined `fn` handler function.
141
143
  *
142
- * It accepts the same input and returns the same value, calling the user function inside.
144
+ * Callers pass values accepted by `inputSchema` (`z.input`). The wrapper parses input,
145
+ * invokes `fn`, parses output, and returns `z.infer<outputSchema>`.
143
146
  *
144
147
  * The second argument is a WorkflowInvocationOptions object, allowing workflows configuration overwrite.
145
148
  *
146
- * It adds input and output validation based on the `inputSchema`, `outputSchema`.
147
- *
148
- * @param input - The workflow input; it matches the schema defined by `inputSchema`.
149
+ * @param input - The workflow input before `inputSchema` parse.
149
150
  * @param options - Additional options for the invocation.
150
- * @returns A value matching the schema defined by `outputSchema`.
151
+ * @returns The workflow output after `outputSchema` parse.
151
152
  */
152
- export type WorkflowFunctionWrapper<WorkflowFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
153
- [Parameters<WorkflowFunction>[0]] extends [undefined | null] ?
154
- ( input?: undefined | null, options?: WorkflowInvocationOptions ) =>
155
- ReturnType<WorkflowFunction> :
156
- ( input: Parameters<WorkflowFunction>[0], options?: WorkflowInvocationOptions ) =>
157
- ReturnType<WorkflowFunction>;
153
+ export type WorkflowFunctionWrapper<
154
+ InputSchema extends AnyZodSchema | undefined = undefined,
155
+ OutputSchema extends AnyZodSchema | undefined = undefined
156
+ > = InputSchema extends AnyZodSchema ?
157
+ (
158
+ input: z.input<InputSchema>,
159
+ options?: WorkflowInvocationOptions
160
+ ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
161
+ (
162
+ input?: undefined | null,
163
+ options?: WorkflowInvocationOptions
164
+ ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
158
165
 
159
166
  /**
160
167
  * Creates a workflow.
@@ -270,7 +277,7 @@ export type WorkflowFunctionWrapper<WorkflowFunction extends ( ...args: any ) =>
270
277
  * @param params.outputSchema - Zod schema for workflow output
271
278
  * @param params.fn - A function containing the workflow code
272
279
  * @param params.options - Optional workflow options.
273
- * @returns The same handler function set at `fn` with a different signature
280
+ * @returns A wrapper that parses input/output around `fn`
274
281
  */
275
282
  export declare function workflow<
276
283
  InputSchema extends AnyZodSchema | undefined = undefined,
@@ -288,4 +295,4 @@ export declare function workflow<
288
295
  * while maintaining backward compatibility with existing callers.
289
296
  */
290
297
  aliases?: string[];
291
- } ): WorkflowFunctionWrapper<WorkflowFunction<InputSchema, OutputSchema>>;
298
+ } ): WorkflowFunctionWrapper<InputSchema, OutputSchema>;
@@ -1,26 +1,15 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
- import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, uuid4, ParentClosePolicy } from '@temporalio/workflow';
2
+ import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, uuid4, ParentClosePolicy, upsertMemo } from '@temporalio/workflow';
3
3
  import { WorkflowValidator } from './validations/index.js';
4
4
  import { toUrlSafeBase64 } from '#helpers/string';
5
5
  import { WorkflowContext } from '#helpers/workflow_context';
6
- import { TraceInfo } from '#helpers/trace_info';
7
6
  import { deepMerge } from '#helpers/object';
8
7
  import { defaultOptions } from './workflow_activity_options.js';
9
8
  import { createWorkflow } from '#helpers/component';
9
+ import { enforceActivityOptions } from '#helpers/activity_options';
10
10
  import { FatalError } from '#errors';
11
11
  import * as C from '#consts';
12
12
 
13
- /**
14
- * Execute the workflow without temporal, using the fn handler function.
15
- * This is important to allow for workflows to have unit tests
16
- */
17
- const executeWithoutTemporal = async ( { input, validator, handler, contextOverrides = {} } ) => {
18
- validator.validateInput( input );
19
- const output = await handler( input, deepMerge( WorkflowContext.build(), contextOverrides ) );
20
- validator.validateOutput( output );
21
- return output;
22
- };
23
-
24
13
  /**
25
14
  * Add a global dispatcher function to be used to invoke activities.
26
15
  * This will replace direct activity invocation in the user code by the webpack loader.
@@ -29,7 +18,7 @@ const executeWithoutTemporal = async ( { input, validator, handler, contextOverr
29
18
  * so Temporal's reusable VM can delete it when switching workflow scopes.
30
19
  */
31
20
  const createGlobalDispatcher = ( { runId, workflowType, activities } ) => {
32
- const dispatcher = async ( activityType, ...args ) => activities[`${workflowType}#${activityType}`]( ...args ).then( r => r.output );
21
+ const dispatcher = async ( activityType, ...args ) => activities[`${workflowType}#${activityType}`]( ...args );
33
22
  dispatcher.runId = runId;
34
23
  globalThis[C.INVOKE_ACTIVITY_SYMBOL] = dispatcher;
35
24
  };
@@ -47,19 +36,20 @@ const checkGlobalContextContamination = runId => {
47
36
  export function workflow( { name, description, inputSchema, outputSchema, fn, options = {}, aliases = [] } ) {
48
37
  WorkflowValidator.validateDefinition( { name, description, inputSchema, outputSchema, fn, options, aliases } );
49
38
 
50
- // Disable trace can only be defined at the definition level
51
- const disableTrace = options.disableTrace ?? defaultOptions.disableTrace;
52
39
  const validator = new WorkflowValidator( { name, inputSchema, outputSchema } );
53
40
 
54
- const handler = async ( input, invocationOptions = {} ) => {
55
- validator.validateInvocationOptions( invocationOptions );
41
+ const handler = async ( input, rawInvocationOptions = {} ) => {
42
+ const invocationOptions = validator.parseInvocationOptions( rawInvocationOptions );
56
43
 
57
44
  // If called outside Temporal workflow context, just execute the handler function
58
45
  if ( !inWorkflowContext() ) {
59
- return executeWithoutTemporal( { input, validator, handler: fn, contextOverrides: invocationOptions?.context } );
46
+ return validator.parseOutput(
47
+ await fn( validator.parseInput( input ), deepMerge( WorkflowContext.build(), invocationOptions?.context ) )
48
+ );
60
49
  }
61
50
 
62
51
  const { workflowId, runId, memo, root } = workflowInfo();
52
+ const isRoot = !root;
63
53
 
64
54
  checkGlobalContextContamination( runId );
65
55
 
@@ -69,48 +59,30 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
69
59
  const parentClosePolicy = ParentClosePolicy[invocationOptions?.detached ? 'ABANDON' : 'TERMINATE'];
70
60
  const childWorkflowId = `${workflowId}-${toUrlSafeBase64( uuid4() )}`;
71
61
  const args = [ input, { activityOptions: invocationOptions?.activityOptions } ];
72
- return executeChild( name, { args, workflowId: childWorkflowId, parentClosePolicy, memo } ).then( r => r.output );
73
- }
74
-
75
- const isRoot = !root; // Check if this is the root most workflow
76
-
77
- // Trace info is only added in the root and only when trace is not disabled
78
- if ( isRoot && !disableTrace ) {
79
- memo.traceInfo = TraceInfo.build();
62
+ return executeChild( name, { args, workflowId: childWorkflowId, parentClosePolicy, memo } );
80
63
  }
81
64
 
82
- // Resolve the activity options: invocation options > definition options > parent options > default options
83
- const activityOptions = deepMerge(
65
+ // Resolve the activity options: invocation options > definition options > parent options > default options, then enforce final SDK options
66
+ const activityOptions = enforceActivityOptions( deepMerge(
84
67
  defaultOptions.activityOptions, // default
85
- memo?.parentActivityOptions, // parent options
68
+ memo?.activityOptions, // parent options
86
69
  options?.activityOptions, // definition options
87
70
  invocationOptions.activityOptions // invocation options
88
- );
89
- // Resolved activity options are added to memo so child workflow executions can continue the policy chain.
90
- memo.parentActivityOptions = activityOptions;
91
- const activities = proxyActivities( activityOptions );
71
+ ) );
92
72
 
73
+ const activities = proxyActivities( activityOptions );
93
74
  createGlobalDispatcher( { runId, workflowType: name, activities } );
94
75
 
95
- const traceDestinations = isRoot && {
96
- trace: {
97
- destinations: disableTrace ? {} : await activities[C.ACTIVITY_GET_TRACE_DESTINATIONS]( memo.traceInfo ).then( r => r.output ) ?? {}
98
- }
99
- };
76
+ upsertMemo( {
77
+ activityOptions, // Resolved activity options are added to memo so child workflow executions can continue the policy chain
78
+ ...( isRoot && memo.traceInfo && {
79
+ trace: await activities[C.ACTIVITY_GET_TRACE_DESTINATIONS]( memo.traceInfo )
80
+ } )
81
+ } );
100
82
 
101
- try {
102
- validator.validateInput( input );
103
- const output = await fn( input, WorkflowContext.build() );
104
- validator.validateOutput( output );
105
-
106
- return { [C.WORKFLOW_WRAPPER_VERSION_FIELD]: 1, output, ...traceDestinations };
107
- } catch ( error ) {
108
- if ( traceDestinations ) {
109
- // Append the trace destinations so it is carried to interceptor
110
- error[C.METADATA_ACCESS_SYMBOL] = traceDestinations;
111
- }
112
- throw error;
113
- }
83
+ return validator.parseOutput(
84
+ await fn( validator.parseInput( input ), WorkflowContext.build() )
85
+ );
114
86
  };
115
87
 
116
88
  return createWorkflow( { name, description, inputSchema, outputSchema, options, aliases, handler } );