@outputai/core 0.10.1-next.b7b2fbe.0 → 0.10.1-next.eaf62a3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/consts.js +2 -1
- package/src/helpers/object.js +48 -35
- package/src/helpers/object.spec.js +123 -75
- package/src/interface/workflow.js +92 -85
- package/src/interface/workflow.spec.js +135 -142
- package/src/sdk/helpers/objects.d.ts +24 -19
- package/src/worker/interceptors/workflow.js +5 -5
- package/src/worker/interceptors/workflow.spec.js +7 -7
- package/src/worker/loader/activities.js +56 -40
- package/src/worker/loader/activities.spec.js +21 -22
- package/src/worker/loader/workflows.spec.js +1 -2
- package/src/worker/webpack_loaders/consts.js +6 -0
- package/src/worker/webpack_loaders/tools.js +1 -146
- package/src/worker/webpack_loaders/tools.spec.js +0 -23
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +24 -32
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +82 -279
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +6 -7
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +57 -119
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.js +88 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +165 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +96 -29
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +110 -583
- package/src/worker/webpack_loaders/{npm_workflow_export_resolve.js → workflow_validator/npm_workflow_export_resolve.js} +1 -1
- package/src/worker/webpack_loaders/{npm_workflow_export_resolve.spec.js → workflow_validator/npm_workflow_export_resolve.spec.js} +1 -1
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +0 -193
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +0 -123
package/package.json
CHANGED
package/src/consts.js
CHANGED
|
@@ -4,11 +4,12 @@ export const ACTIVITY_SEND_HTTP_REQUEST = '__internal#sendHttpRequest';
|
|
|
4
4
|
export const ACTIVITY_WRAPPER_VERSION_FIELD = '__output_activity_wrapper_version';
|
|
5
5
|
export const ACTIVITY_LOGGER_SYMBOL = Symbol.for( '__activity_logger' );
|
|
6
6
|
export const METADATA_ACCESS_SYMBOL = Symbol( '__metadata' );
|
|
7
|
-
export const SHARED_STEP_PREFIX = '$shared';
|
|
8
7
|
export const WORKFLOW_CATALOG = '$catalog';
|
|
9
8
|
export const WORKFLOWS_INDEX_FILENAME = '__workflows_entrypoint.js';
|
|
10
9
|
export const WORKFLOW_WRAPPER_VERSION_FIELD = '__output_workflow_wrapper_version';
|
|
11
10
|
|
|
11
|
+
export const INVOKE_ACTIVITY_SYMBOL = Symbol.for( '@outputai/core:__invoke_activity' );
|
|
12
|
+
|
|
12
13
|
export const ComponentType = {
|
|
13
14
|
EVALUATOR: 'evaluator',
|
|
14
15
|
INTERNAL_STEP: 'internal_step',
|
package/src/helpers/object.js
CHANGED
|
@@ -24,58 +24,71 @@ export const clone = v => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Creates a new object merging object
|
|
28
|
-
*
|
|
29
|
-
* - Object "
|
|
30
|
-
* - Object "
|
|
27
|
+
* Creates a new object recursively merging the rightmost object over the previous one using a resolver function.
|
|
28
|
+
* Give two objects, (L)eft and (R)right
|
|
29
|
+
* - Object "R" will overwrite fields on object "L" based on the result of the resolver function;
|
|
30
|
+
* - Object "R" fields that don't exist on object "L" will be added;
|
|
31
|
+
* - Object "L" fields that don't exist on object "R" will be preserved;
|
|
31
32
|
*
|
|
32
|
-
* If "
|
|
33
|
+
* If "R" isn't an object, a new object equal to "L" is returned.
|
|
33
34
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* The resolver function will define the final value of the merge, it receives two args value "L" and "R".
|
|
36
|
+
*
|
|
37
|
+
*
|
|
38
|
+
* @param {object} base - The base object
|
|
39
|
+
* @param {...(object|function)} args - Target objects followed by the resolver function
|
|
37
40
|
* @returns {object} A new object
|
|
38
41
|
*/
|
|
39
|
-
export const deepMergeWithResolver = (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
export const deepMergeWithResolver = ( base, ...args ) => {
|
|
43
|
+
const objects = args.slice( 0, -1 );
|
|
44
|
+
const resolver = args.at( -1 );
|
|
45
|
+
|
|
46
|
+
if ( !isPlainObject( base ) ) {
|
|
47
|
+
throw new Error( 'First argument (base object) is not an object.' );
|
|
42
48
|
}
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
|
|
50
|
+
if ( typeof resolver !== 'function' ) {
|
|
51
|
+
throw new Error( 'Last argument (resolver) is not a function.' );
|
|
45
52
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
|
|
54
|
+
return objects.reduce( ( merged, object ) => {
|
|
55
|
+
if ( !isPlainObject( object ) ) {
|
|
56
|
+
return merged;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for ( const [ k, v ] of Object.entries( object ) ) {
|
|
60
|
+
if ( isPlainObject( v ) && isPlainObject( merged[k] ) ) {
|
|
61
|
+
merged[k] = deepMergeWithResolver( merged[k], v, resolver );
|
|
62
|
+
} else if ( Object.hasOwn( merged, k ) ) {
|
|
63
|
+
merged[k] = resolver( merged[k], v );
|
|
64
|
+
} else {
|
|
65
|
+
merged[k] = v;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return merged;
|
|
69
|
+
}, clone( base ) );
|
|
59
70
|
};
|
|
60
71
|
|
|
61
72
|
/**
|
|
62
|
-
* Creates a new object merging
|
|
63
|
-
*
|
|
64
|
-
* - Object "
|
|
65
|
-
* - Object "
|
|
73
|
+
* Creates a new object recursively merging the rightmost object over the previous one.
|
|
74
|
+
* Give two objects, (L)eft and (R)right
|
|
75
|
+
* - Object "R" will overwrite fields on object "L";
|
|
76
|
+
* - Object "R" fields that don't exist on object "L" will be added;
|
|
77
|
+
* - Object "L" fields that don't exist on object "R" will be preserved;
|
|
66
78
|
*
|
|
67
|
-
* If "
|
|
79
|
+
* If "R" isn't an object, a new object equal to "L" is returned.
|
|
68
80
|
*
|
|
69
|
-
* @param {object}
|
|
70
|
-
* @param {object}
|
|
81
|
+
* @param {object} base - The base object
|
|
82
|
+
* @param {...object} rest - The target objects
|
|
71
83
|
* @returns {object} A new object
|
|
72
84
|
*/
|
|
73
|
-
export const deepMerge = (
|
|
85
|
+
export const deepMerge = ( base, ...rest ) =>
|
|
86
|
+
deepMergeWithResolver( base, ...rest, ( _, b ) => b );
|
|
74
87
|
|
|
75
88
|
/**
|
|
76
89
|
* Adds an non-writable, non-configurable and non-enumerable property to an object
|
|
77
90
|
* @param {object} obj
|
|
78
|
-
* @param {string} key
|
|
91
|
+
* @param {string|Symbol} key
|
|
79
92
|
* @param {any} value
|
|
80
93
|
* @returns
|
|
81
94
|
*/
|
|
@@ -95,98 +95,86 @@ describe( 'clone', () => {
|
|
|
95
95
|
} );
|
|
96
96
|
|
|
97
97
|
describe( 'deepMerge', () => {
|
|
98
|
-
it( '
|
|
98
|
+
it( 'returns a clone when only the base object is provided', () => {
|
|
99
99
|
const a = {
|
|
100
|
-
|
|
101
|
-
b: {
|
|
102
|
-
c: 2
|
|
103
|
-
}
|
|
100
|
+
nested: { value: 1 }
|
|
104
101
|
};
|
|
105
|
-
const
|
|
106
|
-
a: false,
|
|
107
|
-
b: {
|
|
108
|
-
c: true
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
expect( deepMerge( a, b ) ).toEqual( {
|
|
112
|
-
a: false,
|
|
113
|
-
b: {
|
|
114
|
-
c: true
|
|
115
|
-
}
|
|
116
|
-
} );
|
|
117
|
-
} );
|
|
102
|
+
const result = deepMerge( a );
|
|
118
103
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const b = {
|
|
124
|
-
a: false,
|
|
125
|
-
b: true
|
|
126
|
-
};
|
|
127
|
-
expect( deepMerge( a, b ) ).toEqual( {
|
|
128
|
-
a: false,
|
|
129
|
-
b: true
|
|
104
|
+
a.nested.value = 2;
|
|
105
|
+
|
|
106
|
+
expect( result ).toEqual( {
|
|
107
|
+
nested: { value: 1 }
|
|
130
108
|
} );
|
|
131
109
|
} );
|
|
132
110
|
|
|
133
|
-
it( '
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
111
|
+
it( 'Throws when first argument is not a plain object', () => {
|
|
112
|
+
expect( () => deepMerge( null ) ).toThrow( Error );
|
|
113
|
+
} );
|
|
114
|
+
|
|
115
|
+
it( 'merges multiple objects from left to right using rightmost values', () => {
|
|
116
|
+
expect( deepMerge(
|
|
117
|
+
{
|
|
118
|
+
a: 1,
|
|
119
|
+
nested: {
|
|
120
|
+
a: 'base',
|
|
121
|
+
kept: true
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
b: 2,
|
|
126
|
+
nested: {
|
|
127
|
+
a: 'first',
|
|
128
|
+
b: 'first'
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
a: 3,
|
|
133
|
+
nested: {
|
|
134
|
+
b: 'second',
|
|
135
|
+
c: 'second'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
) ).toEqual( {
|
|
139
|
+
a: 3,
|
|
140
|
+
b: 2,
|
|
141
|
+
nested: {
|
|
142
|
+
a: 'first',
|
|
143
|
+
b: 'second',
|
|
144
|
+
c: 'second',
|
|
145
|
+
kept: true
|
|
146
|
+
}
|
|
143
147
|
} );
|
|
144
148
|
} );
|
|
145
149
|
|
|
146
|
-
it( '
|
|
147
|
-
|
|
148
|
-
a: 1
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
150
|
+
it( 'ignores non-object values among multiple overlays', () => {
|
|
151
|
+
expect( deepMerge(
|
|
152
|
+
{ a: 1 },
|
|
153
|
+
null,
|
|
154
|
+
{ b: 2 },
|
|
155
|
+
undefined,
|
|
156
|
+
{ a: 3 }
|
|
157
|
+
) ).toEqual( {
|
|
158
|
+
a: 3,
|
|
159
|
+
b: 2
|
|
160
|
+
} );
|
|
157
161
|
} );
|
|
162
|
+
} );
|
|
158
163
|
|
|
159
|
-
|
|
164
|
+
describe( 'deepMergeWithResolver', () => {
|
|
165
|
+
it( 'returns a clone when only the base object and resolver are provided', () => {
|
|
160
166
|
const a = {
|
|
161
|
-
|
|
167
|
+
nested: { value: 1 }
|
|
162
168
|
};
|
|
163
|
-
|
|
164
|
-
expect( deepMerge( a, undefined ) ).toEqual( { a: 1 } );
|
|
165
|
-
} );
|
|
169
|
+
const result = deepMergeWithResolver( a, ( x, y ) => x + y );
|
|
166
170
|
|
|
167
|
-
|
|
168
|
-
const a = {
|
|
169
|
-
a: 1
|
|
170
|
-
};
|
|
171
|
-
const result = deepMerge( a, null );
|
|
172
|
-
a.a = 2;
|
|
173
|
-
expect( result.a ).toEqual( 1 );
|
|
174
|
-
} );
|
|
171
|
+
a.nested.value = 2;
|
|
175
172
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
expect( () => deepMerge( 'a' ) ).toThrow( Error );
|
|
180
|
-
expect( () => deepMerge( true ) ).toThrow( Error );
|
|
181
|
-
expect( () => deepMerge( /a/ ) ).toThrow( Error );
|
|
182
|
-
expect( () => deepMerge( [] ) ).toThrow( Error );
|
|
183
|
-
expect( () => deepMerge( class Foo {}, class Foo {} ) ).toThrow( Error );
|
|
184
|
-
expect( () => deepMerge( Number.constructor, Number.constructor ) ).toThrow( Error );
|
|
185
|
-
expect( () => deepMerge( Number.constructor.prototype, Number.constructor.prototype ) ).toThrow( Error );
|
|
173
|
+
expect( result ).toEqual( {
|
|
174
|
+
nested: { value: 1 }
|
|
175
|
+
} );
|
|
186
176
|
} );
|
|
187
|
-
} );
|
|
188
177
|
|
|
189
|
-
describe( 'deepMergeWithResolver', () => {
|
|
190
178
|
it( 'uses resolver for existing leaf values, including nested leaves', () => {
|
|
191
179
|
const a = {
|
|
192
180
|
cost: { total: 1 },
|
|
@@ -235,6 +223,66 @@ describe( 'deepMergeWithResolver', () => {
|
|
|
235
223
|
expect( () => deepMergeWithResolver( [], {}, ( x, y ) => x + y ) ).toThrow( Error );
|
|
236
224
|
expect( () => deepMergeWithResolver( 'a', {}, ( x, y ) => x + y ) ).toThrow( Error );
|
|
237
225
|
} );
|
|
226
|
+
|
|
227
|
+
it( 'throws when last argument is not a resolver function', () => {
|
|
228
|
+
expect( () => deepMergeWithResolver( { a: 1 }, { a: 2 } ) )
|
|
229
|
+
.toThrow( 'Last argument (resolver) is not a function.' );
|
|
230
|
+
} );
|
|
231
|
+
|
|
232
|
+
it( 'merges multiple objects using the resolver from left to right', () => {
|
|
233
|
+
const resolver = vi.fn( ( x, y ) => `${ x }:${ y }` );
|
|
234
|
+
|
|
235
|
+
expect( deepMergeWithResolver(
|
|
236
|
+
{
|
|
237
|
+
a: 'base',
|
|
238
|
+
nested: {
|
|
239
|
+
count: 1,
|
|
240
|
+
kept: 'yes'
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
a: 'first',
|
|
245
|
+
nested: {
|
|
246
|
+
count: 2,
|
|
247
|
+
firstOnly: true
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
a: 'second',
|
|
252
|
+
nested: {
|
|
253
|
+
count: 3,
|
|
254
|
+
secondOnly: true
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
resolver
|
|
258
|
+
) ).toEqual( {
|
|
259
|
+
a: 'base:first:second',
|
|
260
|
+
nested: {
|
|
261
|
+
count: '1:2:3',
|
|
262
|
+
firstOnly: true,
|
|
263
|
+
kept: 'yes',
|
|
264
|
+
secondOnly: true
|
|
265
|
+
}
|
|
266
|
+
} );
|
|
267
|
+
expect( resolver ).toHaveBeenCalledTimes( 4 );
|
|
268
|
+
} );
|
|
269
|
+
|
|
270
|
+
it( 'ignores non-object values among multiple resolver overlays', () => {
|
|
271
|
+
const resolver = vi.fn( ( x, y ) => x + y );
|
|
272
|
+
|
|
273
|
+
expect( deepMergeWithResolver(
|
|
274
|
+
{ a: 1 },
|
|
275
|
+
null,
|
|
276
|
+
{ a: 2 },
|
|
277
|
+
undefined,
|
|
278
|
+
{ b: 3 },
|
|
279
|
+
resolver
|
|
280
|
+
) ).toEqual( {
|
|
281
|
+
a: 3,
|
|
282
|
+
b: 3
|
|
283
|
+
} );
|
|
284
|
+
expect( resolver ).toHaveBeenCalledOnce();
|
|
285
|
+
} );
|
|
238
286
|
} );
|
|
239
287
|
|
|
240
288
|
describe( 'isPlainObject', () => {
|
|
@@ -7,104 +7,111 @@ import { TraceInfo } from '#helpers/trace_info';
|
|
|
7
7
|
import { deepMerge } from '#helpers/object';
|
|
8
8
|
import { defaultOptions } from './workflow_activity_options.js';
|
|
9
9
|
import { createWorkflow } from '#helpers/component';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
METADATA_ACCESS_SYMBOL,
|
|
13
|
-
SHARED_STEP_PREFIX,
|
|
14
|
-
WORKFLOW_WRAPPER_VERSION_FIELD
|
|
15
|
-
} from '#consts';
|
|
10
|
+
import { FatalError } from '#errors';
|
|
11
|
+
import * as C from '#consts';
|
|
16
12
|
|
|
17
13
|
/**
|
|
18
|
-
*
|
|
14
|
+
* Execute the workflow without temporal, using the fn handler function.
|
|
15
|
+
* This is important to allow for workflows to have unit tests
|
|
19
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
|
+
/**
|
|
25
|
+
* Add a global dispatcher function to be used to invoke activities.
|
|
26
|
+
* This will replace direct activity invocation in the user code by the webpack loader.
|
|
27
|
+
*
|
|
28
|
+
* Important: Keep this as a configurable global assignment (configurable=true, enumerable=true),
|
|
29
|
+
* so Temporal's reusable VM can delete it when switching workflow scopes.
|
|
30
|
+
*/
|
|
31
|
+
const createGlobalDispatcher = ( { runId, workflowType, activities } ) => {
|
|
32
|
+
const dispatcher = async ( activityType, ...args ) => activities[`${workflowType}#${activityType}`]( ...args ).then( r => r.output );
|
|
33
|
+
dispatcher.runId = runId;
|
|
34
|
+
globalThis[C.INVOKE_ACTIVITY_SYMBOL] = dispatcher;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Validate if the global dispatcher wasn't set by another workflow, indicating global context contamination. */
|
|
38
|
+
const checkGlobalContextContamination = runId => {
|
|
39
|
+
const globalContextRunId = globalThis?.[C.INVOKE_ACTIVITY_SYMBOL]?.runId;
|
|
40
|
+
if ( globalContextRunId && globalContextRunId !== runId ) {
|
|
41
|
+
throw new FatalError( 'Contamination of the workflow Node global context.' +
|
|
42
|
+
` Var "globalThis[${String( C.INVOKE_ACTIVITY_SYMBOL )}]" was set by another workflow (${globalContextRunId})` );
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Create a new workflow and return a wrapper function around its fn handler */
|
|
20
47
|
export function workflow( { name, description, inputSchema, outputSchema, fn, options = {}, aliases = [] } ) {
|
|
21
48
|
WorkflowValidator.validateDefinition( { name, description, inputSchema, outputSchema, fn, options, aliases } );
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
// Disable trace can only be defined at the definition level
|
|
51
|
+
const disableTrace = options.disableTrace ?? defaultOptions.disableTrace;
|
|
24
52
|
const validator = new WorkflowValidator( { name, inputSchema, outputSchema } );
|
|
25
53
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
description,
|
|
29
|
-
inputSchema,
|
|
30
|
-
outputSchema,
|
|
31
|
-
options,
|
|
32
|
-
aliases,
|
|
33
|
-
handler: async ( input, extra = {} ) => {
|
|
34
|
-
validator.validateInvocationOptions( extra );
|
|
35
|
-
|
|
36
|
-
// this returns a plain function, for example, in unit tests
|
|
37
|
-
if ( !inWorkflowContext() ) {
|
|
38
|
-
validator.validateInput( input );
|
|
39
|
-
const output = await fn( input, deepMerge( WorkflowContext.build(), extra?.context ) );
|
|
40
|
-
validator.validateOutput( output );
|
|
41
|
-
return output;
|
|
42
|
-
}
|
|
54
|
+
const handler = async ( input, invocationOptions = {} ) => {
|
|
55
|
+
validator.validateInvocationOptions( invocationOptions );
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if ( isChild ) {
|
|
51
|
-
const result = await executeChild( name, {
|
|
52
|
-
args: undefined === input ? [] : [ input ],
|
|
53
|
-
workflowId: `${workflowId}-${toUrlSafeBase64( uuid4() )}`,
|
|
54
|
-
parentClosePolicy: ParentClosePolicy[extra?.detached ? 'ABANDON' : 'TERMINATE'],
|
|
55
|
-
memo: {
|
|
56
|
-
...memo, // Preserve memo and mix activityOptions, if provided
|
|
57
|
-
...( extra?.activityOptions && {
|
|
58
|
-
activityOptions: deepMerge( memo?.activityOptions ?? {}, extra?.activityOptions )
|
|
59
|
-
} )
|
|
60
|
-
}
|
|
61
|
-
} );
|
|
62
|
-
return result.output;
|
|
63
|
-
}
|
|
57
|
+
// If called outside Temporal workflow context, just execute the handler function
|
|
58
|
+
if ( !inWorkflowContext() ) {
|
|
59
|
+
return executeWithoutTemporal( { input, validator, handler: fn, contextOverrides: invocationOptions?.context } );
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { workflowId, runId, memo, root } = workflowInfo();
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
checkGlobalContextContamination( runId );
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
// If the parent workflow already installed the activity dispatcher, it means that calls to workflow() will trigger child workflows
|
|
67
|
+
const isChildWorkflowCall = !!globalThis[C.INVOKE_ACTIVITY_SYMBOL];
|
|
68
|
+
if ( isChildWorkflowCall ) {
|
|
69
|
+
const parentClosePolicy = ParentClosePolicy[invocationOptions?.detached ? 'ABANDON' : 'TERMINATE'];
|
|
70
|
+
const childWorkflowId = `${workflowId}-${toUrlSafeBase64( uuid4() )}`;
|
|
71
|
+
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();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Resolve the activity options: invocation options > definition options > parent options > default options
|
|
83
|
+
const activityOptions = deepMerge(
|
|
84
|
+
defaultOptions.activityOptions, // default
|
|
85
|
+
memo?.parentActivityOptions, // parent options
|
|
86
|
+
options?.activityOptions, // definition options
|
|
87
|
+
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 );
|
|
92
|
+
|
|
93
|
+
createGlobalDispatcher( { runId, workflowType: name, activities } );
|
|
94
|
+
|
|
95
|
+
const traceDestinations = isRoot && {
|
|
96
|
+
trace: {
|
|
97
|
+
destinations: disableTrace ? {} : await activities[C.ACTIVITY_GET_TRACE_DESTINATIONS]( memo.traceInfo ).then( r => r.output ) ?? {}
|
|
73
98
|
}
|
|
99
|
+
};
|
|
74
100
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Creates an activity caller based on a prefix
|
|
86
|
-
const createCaller = prefix => async ( t, ...args ) => ( await steps[`${prefix}#${t}`]( ...args ) ).output;
|
|
87
|
-
|
|
88
|
-
// This are functions used by the AST to replace direct activity (step/evaluator) calls
|
|
89
|
-
const dispatchers = {
|
|
90
|
-
invokeStep: createCaller( name ),
|
|
91
|
-
invokeSharedStep: createCaller( SHARED_STEP_PREFIX ),
|
|
92
|
-
invokeEvaluator: createCaller( name ),
|
|
93
|
-
invokeSharedEvaluator: createCaller( SHARED_STEP_PREFIX )
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// The workflow function execution with "this" set with the dispatchers
|
|
97
|
-
const output = await fn.call( dispatchers, input, WorkflowContext.build() );
|
|
98
|
-
validator.validateOutput( output );
|
|
99
|
-
|
|
100
|
-
return { [WORKFLOW_WRAPPER_VERSION_FIELD]: 1, output, ...traceDestinations };
|
|
101
|
-
} catch ( error ) {
|
|
102
|
-
if ( traceDestinations ) {
|
|
103
|
-
// Append the trace destinations so it is carried to interceptor
|
|
104
|
-
error[METADATA_ACCESS_SYMBOL] = traceDestinations;
|
|
105
|
-
}
|
|
106
|
-
throw error;
|
|
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;
|
|
107
111
|
}
|
|
112
|
+
throw error;
|
|
108
113
|
}
|
|
109
|
-
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return createWorkflow( { name, description, inputSchema, outputSchema, options, aliases, handler } );
|
|
110
117
|
}
|