@outputai/core 0.1.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/LICENSE +201 -0
- package/README.md +11 -0
- package/bin/healthcheck.mjs +36 -0
- package/bin/healthcheck.spec.js +90 -0
- package/bin/worker.sh +26 -0
- package/package.json +67 -0
- package/src/activity_integration/context.d.ts +27 -0
- package/src/activity_integration/context.js +17 -0
- package/src/activity_integration/context.spec.js +42 -0
- package/src/activity_integration/events.d.ts +7 -0
- package/src/activity_integration/events.js +10 -0
- package/src/activity_integration/index.d.ts +9 -0
- package/src/activity_integration/index.js +3 -0
- package/src/activity_integration/tracing.d.ts +32 -0
- package/src/activity_integration/tracing.js +37 -0
- package/src/async_storage.js +19 -0
- package/src/bus.js +3 -0
- package/src/consts.js +32 -0
- package/src/errors.d.ts +15 -0
- package/src/errors.js +14 -0
- package/src/hooks/index.d.ts +28 -0
- package/src/hooks/index.js +32 -0
- package/src/index.d.ts +49 -0
- package/src/index.js +4 -0
- package/src/interface/evaluation_result.d.ts +173 -0
- package/src/interface/evaluation_result.js +215 -0
- package/src/interface/evaluator.d.ts +70 -0
- package/src/interface/evaluator.js +34 -0
- package/src/interface/evaluator.spec.js +565 -0
- package/src/interface/index.d.ts +9 -0
- package/src/interface/index.js +26 -0
- package/src/interface/step.d.ts +138 -0
- package/src/interface/step.js +22 -0
- package/src/interface/types.d.ts +27 -0
- package/src/interface/validations/runtime.js +20 -0
- package/src/interface/validations/runtime.spec.js +29 -0
- package/src/interface/validations/schema_utils.js +8 -0
- package/src/interface/validations/schema_utils.spec.js +67 -0
- package/src/interface/validations/static.js +136 -0
- package/src/interface/validations/static.spec.js +366 -0
- package/src/interface/webhook.d.ts +84 -0
- package/src/interface/webhook.js +64 -0
- package/src/interface/webhook.spec.js +122 -0
- package/src/interface/workflow.d.ts +273 -0
- package/src/interface/workflow.js +128 -0
- package/src/interface/workflow.spec.js +467 -0
- package/src/interface/workflow_context.js +31 -0
- package/src/interface/workflow_utils.d.ts +76 -0
- package/src/interface/workflow_utils.js +50 -0
- package/src/interface/workflow_utils.spec.js +190 -0
- package/src/interface/zod_integration.spec.js +646 -0
- package/src/internal_activities/index.js +66 -0
- package/src/internal_activities/index.spec.js +102 -0
- package/src/logger.js +73 -0
- package/src/tracing/internal_interface.js +71 -0
- package/src/tracing/processors/local/index.js +111 -0
- package/src/tracing/processors/local/index.spec.js +149 -0
- package/src/tracing/processors/s3/configs.js +31 -0
- package/src/tracing/processors/s3/configs.spec.js +64 -0
- package/src/tracing/processors/s3/index.js +114 -0
- package/src/tracing/processors/s3/index.spec.js +153 -0
- package/src/tracing/processors/s3/redis_client.js +62 -0
- package/src/tracing/processors/s3/redis_client.spec.js +185 -0
- package/src/tracing/processors/s3/s3_client.js +27 -0
- package/src/tracing/processors/s3/s3_client.spec.js +62 -0
- package/src/tracing/tools/build_trace_tree.js +83 -0
- package/src/tracing/tools/build_trace_tree.spec.js +135 -0
- package/src/tracing/tools/utils.js +21 -0
- package/src/tracing/tools/utils.spec.js +14 -0
- package/src/tracing/trace_engine.js +97 -0
- package/src/tracing/trace_engine.spec.js +199 -0
- package/src/utils/index.d.ts +134 -0
- package/src/utils/index.js +2 -0
- package/src/utils/resolve_invocation_dir.js +34 -0
- package/src/utils/resolve_invocation_dir.spec.js +102 -0
- package/src/utils/utils.js +211 -0
- package/src/utils/utils.spec.js +448 -0
- package/src/worker/bundler_options.js +43 -0
- package/src/worker/catalog_workflow/catalog.js +114 -0
- package/src/worker/catalog_workflow/index.js +54 -0
- package/src/worker/catalog_workflow/index.spec.js +196 -0
- package/src/worker/catalog_workflow/workflow.js +24 -0
- package/src/worker/configs.js +49 -0
- package/src/worker/configs.spec.js +130 -0
- package/src/worker/index.js +89 -0
- package/src/worker/index.spec.js +177 -0
- package/src/worker/interceptors/activity.js +62 -0
- package/src/worker/interceptors/activity.spec.js +212 -0
- package/src/worker/interceptors/workflow.js +70 -0
- package/src/worker/interceptors/workflow.spec.js +167 -0
- package/src/worker/interceptors.js +10 -0
- package/src/worker/loader.js +151 -0
- package/src/worker/loader.spec.js +236 -0
- package/src/worker/loader_tools.js +132 -0
- package/src/worker/loader_tools.spec.js +156 -0
- package/src/worker/log_hooks.js +95 -0
- package/src/worker/log_hooks.spec.js +217 -0
- package/src/worker/sandboxed_utils.js +18 -0
- package/src/worker/shutdown.js +26 -0
- package/src/worker/shutdown.spec.js +82 -0
- package/src/worker/sinks.js +74 -0
- package/src/worker/start_catalog.js +36 -0
- package/src/worker/start_catalog.spec.js +118 -0
- package/src/worker/webpack_loaders/consts.js +9 -0
- package/src/worker/webpack_loaders/tools.js +548 -0
- package/src/worker/webpack_loaders/tools.spec.js +330 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +221 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +336 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +61 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +216 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +196 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +123 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +205 -0
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +613 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import parser from '@babel/parser';
|
|
2
|
+
import { resolve as resolvePath } from 'node:path';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import {
|
|
5
|
+
blockStatement,
|
|
6
|
+
callExpression,
|
|
7
|
+
functionExpression,
|
|
8
|
+
identifier,
|
|
9
|
+
isArrowFunctionExpression,
|
|
10
|
+
isAssignmentPattern,
|
|
11
|
+
isBlockStatement,
|
|
12
|
+
isCallExpression,
|
|
13
|
+
isExportNamedDeclaration,
|
|
14
|
+
isFunctionExpression,
|
|
15
|
+
isIdentifier,
|
|
16
|
+
isVariableDeclarator,
|
|
17
|
+
isStringLiteral,
|
|
18
|
+
isVariableDeclaration,
|
|
19
|
+
isObjectExpression,
|
|
20
|
+
memberExpression,
|
|
21
|
+
returnStatement,
|
|
22
|
+
stringLiteral,
|
|
23
|
+
thisExpression,
|
|
24
|
+
isExportDefaultDeclaration,
|
|
25
|
+
isFunctionDeclaration
|
|
26
|
+
} from '@babel/types';
|
|
27
|
+
import { ComponentFile, NodeType } from './consts.js';
|
|
28
|
+
|
|
29
|
+
// Path pattern regexes - shared across multiple helper functions
|
|
30
|
+
const STEPS_FILE_REGEX = /(^|\/)steps\.js$/;
|
|
31
|
+
const STEPS_FOLDER_REGEX = /\/steps\/[^/]+\.js$/;
|
|
32
|
+
const EVALUATORS_FILE_REGEX = /(^|\/)evaluators\.js$/;
|
|
33
|
+
const EVALUATORS_FOLDER_REGEX = /\/evaluators\/[^/]+\.js$/;
|
|
34
|
+
const PATH_TRAVERSAL_REGEX = /\.\.\//;
|
|
35
|
+
const SHARED_PATH_REGEX = /\/shared\//;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a relative module specifier against a base directory.
|
|
39
|
+
* @param {string} fileDir - Base directory to resolve from.
|
|
40
|
+
* @param {string} rel - Relative path/specifier.
|
|
41
|
+
* @returns {string} Absolute path.
|
|
42
|
+
*/
|
|
43
|
+
export const toAbsolutePath = ( fileDir, rel ) => resolvePath( fileDir, rel );
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Parse JavaScript/TypeScript source into a Babel AST.
|
|
47
|
+
* @param {string} source - Source code to parse.
|
|
48
|
+
* @param {string} filename - Virtual filename for sourcemaps and diagnostics.
|
|
49
|
+
* @returns {import('@babel/types').File} Parsed AST.
|
|
50
|
+
*/
|
|
51
|
+
export const parse = ( source, filename ) => parser.parse( source, {
|
|
52
|
+
sourceType: 'module',
|
|
53
|
+
sourceFilename: filename,
|
|
54
|
+
plugins: [ 'jsx' ]
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extract top-level constant string bindings (e.g., const NAME = 'value').
|
|
59
|
+
* @param {import('@babel/types').File} ast - Parsed file AST.
|
|
60
|
+
* @returns {Map<string, string>} Map from identifier to string literal value.
|
|
61
|
+
*/
|
|
62
|
+
export const extractTopLevelStringConsts = ast =>
|
|
63
|
+
ast.program.body
|
|
64
|
+
.filter( node => isVariableDeclaration( node ) && node.kind === NodeType.CONST )
|
|
65
|
+
.reduce( ( map, node ) => {
|
|
66
|
+
node.declarations
|
|
67
|
+
.filter( dec => isIdentifier( dec.id ) && isStringLiteral( dec.init ) )
|
|
68
|
+
.forEach( dec => map.set( dec.id.name, dec.init.value ) );
|
|
69
|
+
return map;
|
|
70
|
+
}, new Map() );
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve an object key name from an Identifier or StringLiteral.
|
|
74
|
+
* @param {import('@babel/types').Expression} node - Object key node.
|
|
75
|
+
* @returns {string|null} Key name or null when unsupported.
|
|
76
|
+
*/
|
|
77
|
+
export const getObjectKeyName = node => {
|
|
78
|
+
if ( isIdentifier( node ) ) {
|
|
79
|
+
return node.name;
|
|
80
|
+
}
|
|
81
|
+
if ( isStringLiteral( node ) ) {
|
|
82
|
+
return node.value;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract the local identifier name from a destructured ObjectProperty.
|
|
89
|
+
* Supports: { a } and { a: local } and { a: local = default }.
|
|
90
|
+
* @param {import('@babel/types').ObjectProperty} prop - Object property.
|
|
91
|
+
* @returns {string|null} Local identifier name or null.
|
|
92
|
+
*/
|
|
93
|
+
export const getLocalNameFromDestructuredProperty = prop => {
|
|
94
|
+
if ( isIdentifier( prop.value ) ) {
|
|
95
|
+
return prop.value.name;
|
|
96
|
+
}
|
|
97
|
+
if ( isAssignmentPattern( prop.value ) && isIdentifier( prop.value.left ) ) {
|
|
98
|
+
return prop.value.left.name;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert an ArrowFunctionExpression to a FunctionExpression.
|
|
105
|
+
* Wraps expression bodies in a block with a return statement.
|
|
106
|
+
* @param {import('@babel/types').ArrowFunctionExpression} arrow - Arrow function.
|
|
107
|
+
* @returns {import('@babel/types').FunctionExpression} Function expression.
|
|
108
|
+
*/
|
|
109
|
+
export const toFunctionExpression = arrow => {
|
|
110
|
+
const body = isBlockStatement( arrow.body ) ? arrow.body : blockStatement( [ returnStatement( arrow.body ) ] );
|
|
111
|
+
return functionExpression( null, arrow.params, body, arrow.generator ?? false, arrow.async ?? false );
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a module specifier or request string points to steps.js or is in a steps folder.
|
|
116
|
+
* Matches: steps.js, /steps.js, /steps/*.js
|
|
117
|
+
* This matches LOCAL steps only (no path traversal).
|
|
118
|
+
* @param {string} value - Module path or request string.
|
|
119
|
+
* @returns {boolean} True if it matches a local steps path.
|
|
120
|
+
*/
|
|
121
|
+
export const isStepsPath = value => {
|
|
122
|
+
// Exclude shared steps (paths with ../ or containing /shared/)
|
|
123
|
+
if ( PATH_TRAVERSAL_REGEX.test( value ) || SHARED_PATH_REGEX.test( value ) ) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return STEPS_FILE_REGEX.test( value ) || STEPS_FOLDER_REGEX.test( value );
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if a module specifier or request string points to shared steps.
|
|
131
|
+
* Shared steps are steps imported from outside the current workflow directory.
|
|
132
|
+
* Matches paths with ../ traversal or /shared/ and containing steps pattern.
|
|
133
|
+
* @param {string} value - Module path or request string.
|
|
134
|
+
* @returns {boolean} True if it matches a shared steps path.
|
|
135
|
+
*/
|
|
136
|
+
export const isSharedStepsPath = value => {
|
|
137
|
+
const hasStepsPattern = STEPS_FILE_REGEX.test( value ) || STEPS_FOLDER_REGEX.test( value );
|
|
138
|
+
if ( !hasStepsPattern ) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return PATH_TRAVERSAL_REGEX.test( value ) || SHARED_PATH_REGEX.test( value );
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if a path matches any steps pattern (local or shared).
|
|
146
|
+
* Used for validation purposes.
|
|
147
|
+
* @param {string} value - Module path or request string.
|
|
148
|
+
* @returns {boolean} True if it matches any steps path pattern.
|
|
149
|
+
*/
|
|
150
|
+
export const isAnyStepsPath = value =>
|
|
151
|
+
STEPS_FILE_REGEX.test( value ) || STEPS_FOLDER_REGEX.test( value );
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if a module specifier or request string points to evaluators.js or is in an evaluators folder.
|
|
155
|
+
* Matches: evaluators.js, /evaluators.js, /evaluators/*.js
|
|
156
|
+
* @param {string} value - Module path or request string.
|
|
157
|
+
* @returns {boolean} True if it matches an evaluators path.
|
|
158
|
+
*/
|
|
159
|
+
export const isEvaluatorsPath = value => {
|
|
160
|
+
// Exclude shared evaluators (paths with ../ or containing /shared/)
|
|
161
|
+
if ( PATH_TRAVERSAL_REGEX.test( value ) || SHARED_PATH_REGEX.test( value ) ) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return EVALUATORS_FILE_REGEX.test( value ) || EVALUATORS_FOLDER_REGEX.test( value );
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if a module specifier or request string points to shared evaluators.
|
|
169
|
+
* Shared evaluators are evaluators imported from outside the current workflow directory.
|
|
170
|
+
* Matches paths with ../ traversal or /shared/ and containing evaluators pattern.
|
|
171
|
+
* @param {string} value - Module path or request string.
|
|
172
|
+
* @returns {boolean} True if it matches a shared evaluators path.
|
|
173
|
+
*/
|
|
174
|
+
export const isSharedEvaluatorsPath = value => {
|
|
175
|
+
const hasEvaluatorsPattern = EVALUATORS_FILE_REGEX.test( value ) || EVALUATORS_FOLDER_REGEX.test( value );
|
|
176
|
+
if ( !hasEvaluatorsPattern ) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return PATH_TRAVERSAL_REGEX.test( value ) || SHARED_PATH_REGEX.test( value );
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if a path matches any evaluators pattern (local or shared).
|
|
184
|
+
* Used for validation purposes.
|
|
185
|
+
* @param {string} value - Module path or request string.
|
|
186
|
+
* @returns {boolean} True if it matches any evaluators path pattern.
|
|
187
|
+
*/
|
|
188
|
+
export const isAnyEvaluatorsPath = value =>
|
|
189
|
+
EVALUATORS_FILE_REGEX.test( value ) || EVALUATORS_FOLDER_REGEX.test( value );
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if a module specifier or request string points to workflow.js.
|
|
193
|
+
* @param {string} value - Module path or request string.
|
|
194
|
+
* @returns {boolean} True if it matches workflow.js.
|
|
195
|
+
*/
|
|
196
|
+
export const isWorkflowPath = value => /(^|\/)workflow\.js$/.test( value );
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Check if a path is a component file (steps, evaluators, or workflow).
|
|
200
|
+
* @param {string} value - Module path or request string.
|
|
201
|
+
* @returns {boolean} True if it matches any component file path.
|
|
202
|
+
*/
|
|
203
|
+
export const isComponentFile = value =>
|
|
204
|
+
isAnyStepsPath( value ) || isAnyEvaluatorsPath( value ) || isWorkflowPath( value );
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Determine file kind based on its path.
|
|
208
|
+
* Returns the component type if it's a component file, null otherwise.
|
|
209
|
+
* @param {string} path
|
|
210
|
+
* @returns {'workflow'|'steps'|'evaluators'|null}
|
|
211
|
+
*/
|
|
212
|
+
export const getFileKind = path => {
|
|
213
|
+
if ( isAnyStepsPath( path ) ) {
|
|
214
|
+
return ComponentFile.STEPS;
|
|
215
|
+
}
|
|
216
|
+
if ( isAnyEvaluatorsPath( path ) ) {
|
|
217
|
+
return ComponentFile.EVALUATORS;
|
|
218
|
+
}
|
|
219
|
+
if ( isWorkflowPath( path ) ) {
|
|
220
|
+
return ComponentFile.WORKFLOW;
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create a `this.method(literalName, ...args)` CallExpression.
|
|
227
|
+
* @param {string} method - Method name on `this`.
|
|
228
|
+
* @param {string} literalName - First string literal argument.
|
|
229
|
+
* @param {import('@babel/types').Expression[]} args - Remaining call arguments.
|
|
230
|
+
* @returns {import('@babel/types').CallExpression} Call expression node.
|
|
231
|
+
*/
|
|
232
|
+
export const createThisMethodCall = ( method, literalName, args ) =>
|
|
233
|
+
callExpression( memberExpression( thisExpression(), identifier( method ) ), [ stringLiteral( literalName ), ...args ] );
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Build a CallExpression that binds `this` at the call site:
|
|
237
|
+
* fn(arg1, arg2) -> fn.call(this, arg1, arg2)
|
|
238
|
+
*
|
|
239
|
+
* When to use:
|
|
240
|
+
* - Inside workflow `fn` rewriting, local call-chain functions must receive the dynamic `this`
|
|
241
|
+
* so that emitted `this.invokeStep(...)` and similar calls inside them operate correctly.
|
|
242
|
+
*
|
|
243
|
+
* Example:
|
|
244
|
+
* // Input AST intent:
|
|
245
|
+
* foo(a, b);
|
|
246
|
+
*
|
|
247
|
+
* // Rewritten AST:
|
|
248
|
+
* foo.call(this, a, b);
|
|
249
|
+
*
|
|
250
|
+
* @param {string} calleeName - Identifier name of the function being called (e.g., 'foo').
|
|
251
|
+
* @param {import('@babel/types').Expression[]} args - Original call arguments.
|
|
252
|
+
* @returns {import('@babel/types').CallExpression} CallExpression node representing `callee.call(this, ...args)`.
|
|
253
|
+
*/
|
|
254
|
+
export const bindThisAtCallSite = ( calleeName, args ) =>
|
|
255
|
+
callExpression( memberExpression( identifier( calleeName ), identifier( 'call' ) ), [ thisExpression(), ...args ] );
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Resolve an options object's name property to a string.
|
|
259
|
+
* Accepts literal strings or top-level const string identifiers.
|
|
260
|
+
* @param {import('@babel/types').Expression} optionsNode - The call options object.
|
|
261
|
+
* @param {Map<string,string>} consts - Top-level const string bindings.
|
|
262
|
+
* @param {string} errorMessagePrefix - Prefix used when throwing validation errors.
|
|
263
|
+
* @returns {string} Resolved name.
|
|
264
|
+
* @throws {Error} When name is missing or not a supported static form.
|
|
265
|
+
*/
|
|
266
|
+
export const resolveNameFromOptions = ( optionsNode, consts, errorMessagePrefix ) => {
|
|
267
|
+
// If it is not an object
|
|
268
|
+
if ( !isObjectExpression( optionsNode ) ) {
|
|
269
|
+
throw new Error( `${errorMessagePrefix}: Missing properties` );
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Look specifically for the 'name' property
|
|
273
|
+
for ( const prop of optionsNode.properties ) {
|
|
274
|
+
if ( getObjectKeyName( prop.key ) !== 'name' ) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const val = prop.value;
|
|
279
|
+
// if it is a string literal: jackpot
|
|
280
|
+
if ( isStringLiteral( val ) ) {
|
|
281
|
+
return val.value;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// if it is an identifier, it needs to be deterministic (top-level const)
|
|
285
|
+
if ( isIdentifier( val ) ) {
|
|
286
|
+
if ( consts.has( val.name ) ) {
|
|
287
|
+
return consts.get( val.name );
|
|
288
|
+
}
|
|
289
|
+
throw new Error( `${errorMessagePrefix}: Name identifier "${val.name}" is not a top-level const string` );
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
throw new Error( `${errorMessagePrefix}: Name must be a string literal or a top-level const string` );
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
throw new Error( `${errorMessagePrefix}: Missing required name property` ); // No name field found
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Resolve a name from the first argument of a factory call.
|
|
300
|
+
* Handles two patterns:
|
|
301
|
+
* - String literal first arg: `verify('name', fn)` → returns 'name'
|
|
302
|
+
* - Identifier referencing top-level const: `verify(NAME, fn)` → resolves const
|
|
303
|
+
* - Object with name property: `evaluator({ name: '...' })` → delegates to resolveNameFromOptions
|
|
304
|
+
*
|
|
305
|
+
* @param {import('@babel/types').Expression} argNode - First argument to the factory call.
|
|
306
|
+
* @param {Map<string,string>} consts - Top-level const string bindings.
|
|
307
|
+
* @param {string} errorMessagePrefix - Prefix used when throwing validation errors.
|
|
308
|
+
* @returns {string} Resolved name.
|
|
309
|
+
* @throws {Error} When name is missing or not a supported static form.
|
|
310
|
+
*/
|
|
311
|
+
export const resolveNameFromArg = ( argNode, consts, errorMessagePrefix ) => {
|
|
312
|
+
if ( isStringLiteral( argNode ) ) {
|
|
313
|
+
return argNode.value;
|
|
314
|
+
}
|
|
315
|
+
if ( isIdentifier( argNode ) && consts.has( argNode.name ) ) {
|
|
316
|
+
return consts.get( argNode.name );
|
|
317
|
+
}
|
|
318
|
+
return resolveNameFromOptions( argNode, consts, errorMessagePrefix );
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Build a map of exported component identifiers to declared names by scanning a module.
|
|
323
|
+
* Matches any `export const X = identifier(...)` pattern — the callee name is intentionally
|
|
324
|
+
* unchecked because:
|
|
325
|
+
* - File path scoping (steps.js, evaluators.js) already constrains which files are parsed
|
|
326
|
+
* - External packages may define custom factory wrappers (e.g., verify() wraps evaluator())
|
|
327
|
+
* - Runtime metadata validation is the authoritative check for component type
|
|
328
|
+
* - Name extraction (resolveNameFromArg) rejects calls without a static name argument
|
|
329
|
+
*
|
|
330
|
+
* @param {object} params
|
|
331
|
+
* @param {string} params.path - Absolute path to the module file.
|
|
332
|
+
* @param {Map<string, Map<string,string>>} params.cache - Cache for memoizing results by file path.
|
|
333
|
+
* @param {string} params.invalidMessagePrefix - Prefix used in thrown errors when name is invalid.
|
|
334
|
+
* @returns {Map<string,string>} Map of `exportedIdentifier` -> `declaredName`.
|
|
335
|
+
* @throws {Error} When names are missing, dynamic, or otherwise non-static.
|
|
336
|
+
*/
|
|
337
|
+
const buildComponentNameMap = ( { path, cache, invalidMessagePrefix } ) => {
|
|
338
|
+
if ( cache.has( path ) ) {
|
|
339
|
+
return cache.get( path );
|
|
340
|
+
}
|
|
341
|
+
const text = readFileSync( path, 'utf8' );
|
|
342
|
+
const ast = parse( text, path );
|
|
343
|
+
const consts = extractTopLevelStringConsts( ast );
|
|
344
|
+
|
|
345
|
+
const result = ast.program.body
|
|
346
|
+
.filter( node => isExportNamedDeclaration( node ) && isVariableDeclaration( node.declaration ) )
|
|
347
|
+
.reduce( ( map, node ) => {
|
|
348
|
+
node.declaration.declarations
|
|
349
|
+
.filter( dec => isIdentifier( dec.id ) && isCallExpression( dec.init ) && isIdentifier( dec.init.callee ) )
|
|
350
|
+
.map( dec => [
|
|
351
|
+
dec,
|
|
352
|
+
resolveNameFromArg( dec.init.arguments[0], consts, `${invalidMessagePrefix} ${path} for "${dec.id.name}"` )
|
|
353
|
+
] )
|
|
354
|
+
.forEach( ( [ dec, name ] ) => map.set( dec.id.name, name ) );
|
|
355
|
+
return map;
|
|
356
|
+
}, new Map() );
|
|
357
|
+
|
|
358
|
+
cache.set( path, result );
|
|
359
|
+
return result;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export const buildStepsNameMap = ( path, cache ) => buildComponentNameMap( {
|
|
363
|
+
path,
|
|
364
|
+
cache,
|
|
365
|
+
invalidMessagePrefix: 'Invalid step name in'
|
|
366
|
+
} );
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Build a map from exported shared step identifier to declared step name.
|
|
370
|
+
* Same as buildStepsNameMap but for shared steps.
|
|
371
|
+
*
|
|
372
|
+
* @param {string} path - Absolute path to the shared steps module file.
|
|
373
|
+
* @param {Map<string, Map<string,string>>} cache - Cache of computed step name maps.
|
|
374
|
+
* @returns {Map<string,string>} Exported identifier -> step name.
|
|
375
|
+
* @throws {Error} When a step name is invalid (non-static or missing).
|
|
376
|
+
*/
|
|
377
|
+
export const buildSharedStepsNameMap = ( path, cache ) => buildComponentNameMap( {
|
|
378
|
+
path,
|
|
379
|
+
cache,
|
|
380
|
+
invalidMessagePrefix: 'Invalid shared step name in'
|
|
381
|
+
} );
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Build a map from exported evaluator identifier to declared evaluator name.
|
|
385
|
+
* Matches `export const X = evaluator({ name: '...' })` and wrapper patterns
|
|
386
|
+
* like `export const X = verify('name', fn)`.
|
|
387
|
+
*
|
|
388
|
+
* @param {string} path - Absolute path to the evaluators module file.
|
|
389
|
+
* @param {Map<string, Map<string,string>>} cache - Cache of computed evaluator name maps.
|
|
390
|
+
* @returns {Map<string,string>} Exported identifier -> evaluator name.
|
|
391
|
+
* @throws {Error} When a evaluator name is invalid (non-static or missing).
|
|
392
|
+
*/
|
|
393
|
+
export const buildEvaluatorsNameMap = ( path, cache ) => buildComponentNameMap( {
|
|
394
|
+
path,
|
|
395
|
+
cache,
|
|
396
|
+
invalidMessagePrefix: 'Invalid evaluator name in'
|
|
397
|
+
} );
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Build a map from exported shared evaluator identifier to declared evaluator name.
|
|
401
|
+
* Same as buildEvaluatorsNameMap but for shared evaluators.
|
|
402
|
+
*
|
|
403
|
+
* @param {string} path - Absolute path to the shared evaluators module file.
|
|
404
|
+
* @param {Map<string, Map<string,string>>} cache - Cache of computed evaluator name maps.
|
|
405
|
+
* @returns {Map<string,string>} Exported identifier -> evaluator name.
|
|
406
|
+
* @throws {Error} When an evaluator name is invalid (non-static or missing).
|
|
407
|
+
*/
|
|
408
|
+
export const buildSharedEvaluatorsNameMap = ( path, cache ) => buildComponentNameMap( {
|
|
409
|
+
path,
|
|
410
|
+
cache,
|
|
411
|
+
invalidMessagePrefix: 'Invalid shared evaluator name in'
|
|
412
|
+
} );
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Build a structure with default and named workflow names from a workflow module.
|
|
416
|
+
* Extracts names from `workflow({ name: '...' })` calls.
|
|
417
|
+
* @param {string} path - Absolute path to the workflow module file.
|
|
418
|
+
* @param {Map<string, {default: (string|null), named: Map<string,string>}>} cache - Cache of workflow names.
|
|
419
|
+
* @returns {{ default: (string|null), named: Map<string,string> }} Names.
|
|
420
|
+
* @throws {Error} When a workflow name is invalid (non-static or missing).
|
|
421
|
+
*/
|
|
422
|
+
export const buildWorkflowNameMap = ( path, cache ) => {
|
|
423
|
+
if ( cache.has( path ) ) {
|
|
424
|
+
return cache.get( path );
|
|
425
|
+
}
|
|
426
|
+
const text = readFileSync( path, 'utf8' );
|
|
427
|
+
const ast = parse( text, path );
|
|
428
|
+
const consts = extractTopLevelStringConsts( ast );
|
|
429
|
+
|
|
430
|
+
const result = { default: null, named: new Map() };
|
|
431
|
+
|
|
432
|
+
for ( const node of ast.program.body ) {
|
|
433
|
+
|
|
434
|
+
// named exports
|
|
435
|
+
if ( isExportNamedDeclaration( node ) && isVariableDeclaration( node.declaration ) ) {
|
|
436
|
+
|
|
437
|
+
for ( const d of node.declaration.declarations ) {
|
|
438
|
+
if ( isIdentifier( d.id ) && isCallExpression( d.init ) && isIdentifier( d.init.callee ) ) {
|
|
439
|
+
const name = resolveNameFromArg( d.init.arguments[0], consts, `Invalid workflow name in ${path} for '${d.id.name}` );
|
|
440
|
+
if ( name ) {
|
|
441
|
+
result.named.set( d.id.name, name );
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// default exports
|
|
447
|
+
} else if (
|
|
448
|
+
isExportDefaultDeclaration( node ) &&
|
|
449
|
+
isCallExpression( node.declaration ) &&
|
|
450
|
+
isIdentifier( node.declaration.callee )
|
|
451
|
+
) {
|
|
452
|
+
result.default = resolveNameFromArg( node.declaration.arguments[0], consts, `Invalid default workflow name in ${path}` );
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
cache.set( path, result );
|
|
457
|
+
return result;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Determine whether a node represents a function body usable as a workflow `fn`.
|
|
462
|
+
*
|
|
463
|
+
* Why this matters:
|
|
464
|
+
* - Workflow `fn` needs a dynamic `this` so the rewriter can emit calls like `this.invokeStep(...)`.
|
|
465
|
+
* - Arrow functions do not have their own `this`; they capture `this` lexically, which breaks the runtime contract.
|
|
466
|
+
*
|
|
467
|
+
* Accepts:
|
|
468
|
+
* - FunctionExpression (possibly async/generator), e.g.:
|
|
469
|
+
* const obj = {
|
|
470
|
+
* fn: async function (input) {
|
|
471
|
+
* return input;
|
|
472
|
+
* }
|
|
473
|
+
* };
|
|
474
|
+
*
|
|
475
|
+
* Rejects:
|
|
476
|
+
* - ArrowFunctionExpression, e.g.:
|
|
477
|
+
* const obj = {
|
|
478
|
+
* fn: async (input) => input
|
|
479
|
+
* };
|
|
480
|
+
*
|
|
481
|
+
* - Any other non-function expression.
|
|
482
|
+
*
|
|
483
|
+
* Notes:
|
|
484
|
+
* - The rewriter will proactively convert arrow `fn` to a FunctionExpression before further processing.
|
|
485
|
+
*
|
|
486
|
+
* @param {import('@babel/types').Expression} v - Candidate node for `fn` value.
|
|
487
|
+
* @returns {boolean} True if `v` is a FunctionExpression and not an arrow function.
|
|
488
|
+
*/
|
|
489
|
+
export const isFunction = v => isFunctionExpression( v ) && !isArrowFunctionExpression( v );
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Determine whether a variable declarator represents a function-like value.
|
|
493
|
+
*
|
|
494
|
+
* Use case:
|
|
495
|
+
* - When `fn` calls a locally-declared function (directly or transitively), we need to:
|
|
496
|
+
* - propagate `this` to that function call (`callee.call(this, ...)`)
|
|
497
|
+
* - traverse into that function's body to rewrite imported step/workflow/evaluator calls.
|
|
498
|
+
*
|
|
499
|
+
* Matches patterns like:
|
|
500
|
+
* - Function expression:
|
|
501
|
+
* const foo = function (x) { return x + 1; };
|
|
502
|
+
*
|
|
503
|
+
* - Async/generator function expression:
|
|
504
|
+
* const foo = async function (x) { return await work(x); };
|
|
505
|
+
*
|
|
506
|
+
* - Arrow function (will be normalized to FunctionExpression by the rewriter):
|
|
507
|
+
* const foo = (x) => x + 1;
|
|
508
|
+
* const foo = async (x) => await work(x);
|
|
509
|
+
*
|
|
510
|
+
* Does not match:
|
|
511
|
+
* - Non-function initializers:
|
|
512
|
+
* const foo = 42;
|
|
513
|
+
* const foo = someIdentifier;
|
|
514
|
+
*
|
|
515
|
+
* @param {import('@babel/types').Node} v - AST node (typically a VariableDeclarator).
|
|
516
|
+
* @returns {boolean} True if the declarator's initializer is a function (arrow or function expression).
|
|
517
|
+
*/
|
|
518
|
+
export const isVarFunction = v =>
|
|
519
|
+
isVariableDeclarator( v ) && ( isFunctionExpression( v.init ) || isArrowFunctionExpression( v.init ) );
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Determine whether a binding node corresponds to a function-like declaration usable
|
|
523
|
+
* as a call-chain function target during workflow rewriting.
|
|
524
|
+
*
|
|
525
|
+
* Matches:
|
|
526
|
+
* - FunctionDeclaration:
|
|
527
|
+
* function foo(x) { return x + 1; }
|
|
528
|
+
*
|
|
529
|
+
* - VariableDeclarator initialized with a function or arrow (normalized later):
|
|
530
|
+
* const foo = function (x) { return x + 1; };
|
|
531
|
+
* const foo = (x) => x + 1;
|
|
532
|
+
*
|
|
533
|
+
* Non-matches:
|
|
534
|
+
* - Any binding that is not a function declaration nor a variable declarator with a function initializer.
|
|
535
|
+
*
|
|
536
|
+
* Why this matters:
|
|
537
|
+
* - The rewriter traverses call chains from the workflow `fn`. It must recognize which local
|
|
538
|
+
* callees are valid function bodies to rewrite and into which it can propagate `this`.
|
|
539
|
+
*
|
|
540
|
+
* @param {import('@babel/types').Node} node - Binding path node (FunctionDeclaration or VariableDeclarator).
|
|
541
|
+
* @returns {boolean} True if the node represents a function-like binding.
|
|
542
|
+
*/
|
|
543
|
+
export const isFunctionLikeBinding = node =>
|
|
544
|
+
isFunctionDeclaration( node ) ||
|
|
545
|
+
(
|
|
546
|
+
isVariableDeclarator( node ) &&
|
|
547
|
+
( isFunctionExpression( node.init ) || isArrowFunctionExpression( node.init ) )
|
|
548
|
+
);
|