@outputai/core 0.7.0 → 0.7.1-next.0e958f3.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 (66) hide show
  1. package/bin/worker.sh +6 -0
  2. package/package.json +1 -1
  3. package/src/consts.js +0 -4
  4. package/src/errors.js +6 -2
  5. package/src/hooks/index.d.ts +10 -0
  6. package/src/interface/evaluator.js +7 -20
  7. package/src/interface/evaluator.spec.js +117 -1
  8. package/src/interface/step.js +8 -9
  9. package/src/interface/step.spec.js +124 -0
  10. package/src/interface/validations/index.js +108 -0
  11. package/src/interface/validations/index.spec.js +182 -0
  12. package/src/interface/validations/schemas.js +113 -0
  13. package/src/interface/validations/schemas.spec.js +209 -0
  14. package/src/interface/webhook.js +1 -1
  15. package/src/interface/webhook.spec.js +1 -1
  16. package/src/interface/workflow.d.ts +10 -9
  17. package/src/interface/workflow.js +76 -164
  18. package/src/interface/workflow.spec.js +637 -521
  19. package/src/interface/workflow_activity_options.js +16 -0
  20. package/src/interface/workflow_utils.js +1 -1
  21. package/src/interface/zod_integration.spec.js +2 -2
  22. package/src/internal_utils/aggregations.js +0 -10
  23. package/src/internal_utils/aggregations.spec.js +1 -48
  24. package/src/internal_utils/errors.js +14 -8
  25. package/src/internal_utils/errors.spec.js +73 -27
  26. package/src/utils/index.d.ts +19 -0
  27. package/src/utils/utils.js +46 -0
  28. package/src/utils/utils.spec.js +82 -1
  29. package/src/worker/bundle.js +26 -0
  30. package/src/worker/bundle.spec.js +52 -0
  31. package/src/worker/catalog_workflow/catalog_job.js +148 -0
  32. package/src/worker/catalog_workflow/catalog_job.spec.js +232 -0
  33. package/src/worker/check.js +24 -0
  34. package/src/worker/connection_monitor.js +112 -0
  35. package/src/worker/connection_monitor.spec.js +199 -0
  36. package/src/worker/index.js +140 -34
  37. package/src/worker/index.spec.js +280 -108
  38. package/src/worker/interceptors/activity.js +7 -24
  39. package/src/worker/interceptors/activity.spec.js +97 -66
  40. package/src/worker/interceptors/index.js +4 -7
  41. package/src/worker/interceptors/modules.js +15 -0
  42. package/src/worker/interceptors/workflow.js +4 -7
  43. package/src/worker/interceptors/workflow.spec.js +49 -42
  44. package/src/worker/interruption.js +33 -0
  45. package/src/worker/interruption.spec.js +86 -0
  46. package/src/worker/loader_tools.js +1 -1
  47. package/src/worker/loader_tools.spec.js +36 -0
  48. package/src/worker/{setup_telemetry.js → telemetry.js} +9 -4
  49. package/src/worker/{setup_telemetry.spec.js → telemetry.spec.js} +3 -3
  50. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
  51. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
  52. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
  53. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
  54. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
  55. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
  56. package/src/interface/validations/runtime.js +0 -20
  57. package/src/interface/validations/runtime.spec.js +0 -29
  58. package/src/interface/validations/schema_utils.js +0 -8
  59. package/src/interface/validations/schema_utils.spec.js +0 -67
  60. package/src/interface/validations/static.js +0 -137
  61. package/src/interface/validations/static.spec.js +0 -397
  62. package/src/interface/workflow.replay_compatibility.spec.js +0 -254
  63. package/src/worker/shutdown.js +0 -26
  64. package/src/worker/shutdown.spec.js +0 -82
  65. package/src/worker/start_catalog.js +0 -96
  66. package/src/worker/start_catalog.spec.js +0 -179
@@ -11,6 +11,7 @@ import {
11
11
  findSharedActivitiesFromWorkflows,
12
12
  importComponents,
13
13
  findPackageRoot,
14
+ hashSourceCode,
14
15
  isPackageRoot,
15
16
  isPathDescendentFromNodeModules,
16
17
  resolveNodeModulesPath,
@@ -568,6 +569,41 @@ describe( 'findSharedActivitiesFromWorkflows', () => {
568
569
  } );
569
570
  } );
570
571
 
572
+ describe( 'hashSourceCode', () => {
573
+ const writeSource = root => {
574
+ mkdirSync( join( root, 'src' ), { recursive: true } );
575
+ writeFileSync( join( root, 'package.json' ), JSON.stringify( { name: 'proj' } ) );
576
+ writeFileSync( join( root, 'src', 'workflow.js' ), 'export default {};\n' );
577
+ };
578
+
579
+ it( 'ignores excluded folders so accumulated run logs do not change the hash', async () => {
580
+ const baseline = join( TEMP_BASE, `hash-baseline-${Date.now()}` );
581
+ const withCruft = join( TEMP_BASE, `hash-cruft-${Date.now()}` );
582
+ writeSource( baseline );
583
+ writeSource( withCruft );
584
+
585
+ // The cruft tree is identical source plus large excluded artifacts that
586
+ // boot must not walk: local trace dumps under logs/ and build output under dist/.
587
+ for ( const excluded of [ 'logs', 'logs/runs', 'dist', 'node_modules' ] ) {
588
+ const dir = join( withCruft, excluded );
589
+ mkdirSync( dir, { recursive: true } );
590
+ writeFileSync( join( dir, 'dump.json' ), JSON.stringify( { blob: 'x'.repeat( 50_000 ) } ) );
591
+ }
592
+
593
+ expect( await hashSourceCode( withCruft ) ).toBe( await hashSourceCode( baseline ) );
594
+ } );
595
+
596
+ it( 'changes the hash when actual source changes', async () => {
597
+ const before = join( TEMP_BASE, `hash-src-before-${Date.now()}` );
598
+ const after = join( TEMP_BASE, `hash-src-after-${Date.now()}` );
599
+ writeSource( before );
600
+ writeSource( after );
601
+ writeFileSync( join( after, 'src', 'workflow.js' ), 'export default { changed: true };\n' );
602
+
603
+ expect( await hashSourceCode( after ) ).not.toBe( await hashSourceCode( before ) );
604
+ } );
605
+ } );
606
+
571
607
  describe( 'staticMatchers', () => {
572
608
  describe( 'workflowFile', () => {
573
609
  it( 'matches paths ending with path separator and workflow.js', () => {
@@ -4,8 +4,11 @@ import { workerTelemetryIntervalMs } from './configs.js';
4
4
  const log = createChildLogger( 'Telemetry' );
5
5
 
6
6
  export const setupTelemetry = ( { worker } ) => {
7
- if ( workerTelemetryIntervalMs > 0 ) {
8
- setInterval( () => {
7
+ if ( workerTelemetryIntervalMs <= 0 ) {
8
+ return;
9
+ }
10
+ setInterval( () => {
11
+ try {
9
12
  log.info( 'Worker', {
10
13
  status: worker.getStatus(),
11
14
  memory: {
@@ -14,6 +17,8 @@ export const setupTelemetry = ( { worker } ) => {
14
17
  memoryUsage: process.memoryUsage()
15
18
  }
16
19
  } );
17
- }, workerTelemetryIntervalMs ).unref();
18
- }
20
+ } catch ( error ) {
21
+ log.warn( 'Failure', { error: error.message } );
22
+ }
23
+ }, workerTelemetryIntervalMs ).unref();
19
24
  };
@@ -1,7 +1,7 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  const configMock = vi.hoisted( () => ( { workerTelemetryIntervalMs: 0 } ) );
4
- const logMock = vi.hoisted( () => ( { info: vi.fn() } ) );
4
+ const logMock = vi.hoisted( () => ( { info: vi.fn(), warn: vi.fn() } ) );
5
5
  const createChildLoggerMock = vi.hoisted( () => vi.fn( () => logMock ) );
6
6
 
7
7
  vi.mock( './configs.js', () => ( {
@@ -14,13 +14,13 @@ vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
14
14
 
15
15
  const loadSetupTelemetry = async () => {
16
16
  vi.resetModules();
17
- return import( './setup_telemetry.js' );
17
+ return import( './telemetry.js' );
18
18
  };
19
19
 
20
20
  const mockSetInterval = unrefMock =>
21
21
  vi.spyOn( globalThis, 'setInterval' ).mockReturnValue( { unref: unrefMock } );
22
22
 
23
- describe( 'worker/setup_telemetry', () => {
23
+ describe( 'worker/telemetry', () => {
24
24
  const availableMemoryMock = vi.fn();
25
25
  const constrainedMemoryMock = vi.fn();
26
26
  const memoryUsageMock = vi.fn();
@@ -1,31 +1,20 @@
1
- import { join } from 'node:path';
2
1
  import traverseModule from '@babel/traverse';
3
2
  import {
4
- buildWorkflowNameMap,
5
3
  buildEvaluatorsNameMap,
6
4
  buildSharedEvaluatorsNameMap,
7
5
  buildSharedStepsNameMap,
8
6
  buildStepsNameMap,
9
7
  getLocalNameFromDestructuredProperty,
10
- isAbsoluteWorkflowJsResource,
11
8
  isEvaluatorsPath,
12
9
  isSharedEvaluatorsPath,
13
10
  isSharedStepsPath,
14
11
  isStepsPath,
15
- isWorkflowPath,
16
12
  toAbsolutePath
17
13
  } from '../tools.js';
18
- import {
19
- isBareNpmSpecifier,
20
- resolveBareImportSpecifiersAsWorkflows,
21
- resolveBareDestructuredRequireAsWorkflows,
22
- resolveBareDefaultRequireAsWorkflow
23
- } from '../npm_workflow_export_resolve.js';
24
14
 
25
15
  import {
26
16
  isCallExpression,
27
17
  isIdentifier,
28
- isImportDefaultSpecifier,
29
18
  isImportSpecifier,
30
19
  isObjectPattern,
31
20
  isObjectProperty,
@@ -44,12 +33,6 @@ const unresolvedImportError = ( name, fileLabel, filePath ) =>
44
33
  '(e.g. step() in steps files, evaluator() in evaluators files, workflow() in workflow files).'
45
34
  );
46
35
 
47
- const mixedBareWorkflowImportError = ( specifier, resourcePath ) => new Error(
48
- `Workflow file '${resourcePath}': import from '${specifier}' mixes workflow exports with ` +
49
- 'non-workflow exports, or could not resolve every binding to a workflow.js module. ' +
50
- 'Split npm imports so each declaration only imports workflows, or only non-workflows.'
51
- );
52
-
53
36
  const removeRequireDeclarator = path => {
54
37
  if ( isVariableDeclaration( path.parent ) && path.parent.declarations.length === 1 ) {
55
38
  path.parentPath.remove();
@@ -84,28 +67,23 @@ const collectDestructuredRequires = ( path, absolutePath, req, descriptors ) =>
84
67
 
85
68
  /**
86
69
  * Collect and strip target imports and requires from an AST, producing
87
- * step/workflow import mappings for later rewrites.
70
+ * step/evaluator import mappings for later rewrites.
88
71
  *
89
72
  * Mutates the AST by removing matching import declarations and require declarators.
90
73
  *
91
74
  * @param {import('@babel/types').File} ast - Parsed file AST.
92
75
  * @param {string} fileDir - Absolute directory of the file represented by `ast`.
93
- * @param {{ stepsNameCache: Map<string,Map<string,string>>, workflowNameCache: Map<string,{default:(string|null),named:Map<string,string>}> }} caches
76
+ * @param {{ stepsNameCache: Map<string,Map<string,string>> }} caches
94
77
  * Resolved-name caches to avoid re-reading same modules.
95
- * @param {string} [resourcePath] - Absolute path of the file being transformed; used to resolve
96
- * npm package imports from `workflow.js` via Node resolution and export following.
97
78
  * @returns {{ stepImports: Array<{localName:string,stepName:string}>,
98
- * flowImports: Array<{localName:string,workflowName:string}> }} Collected info mappings.
79
+ * evaluatorImports: Array<{localName:string,evaluatorName:string}> }} Collected info mappings.
99
80
  */
100
81
  export default function collectTargetImports(
101
82
  ast, fileDir,
102
- { stepsNameCache, workflowNameCache, evaluatorsNameCache, sharedStepsNameCache, sharedEvaluatorsNameCache },
103
- resourcePath
83
+ { stepsNameCache, evaluatorsNameCache, sharedStepsNameCache, sharedEvaluatorsNameCache }
104
84
  ) {
105
- const resolutionPath = resourcePath ?? join( fileDir, 'file.js' );
106
85
  const stepImports = [];
107
86
  const sharedStepImports = [];
108
- const flowImports = [];
109
87
  const evaluatorImports = [];
110
88
  const sharedEvaluatorImports = [];
111
89
 
@@ -113,27 +91,7 @@ export default function collectTargetImports(
113
91
  ImportDeclaration: path => {
114
92
  const src = path.node.source.value;
115
93
 
116
- if ( isBareNpmSpecifier( src ) && isAbsoluteWorkflowJsResource( resolutionPath ) ) {
117
- const outcome = resolveBareImportSpecifiersAsWorkflows( {
118
- fromAbsoluteFile: resolutionPath,
119
- specifier: src,
120
- specifiers: path.node.specifiers,
121
- workflowNameCache
122
- } );
123
- if ( outcome.type === 'partial' ) {
124
- throw mixedBareWorkflowImportError( src, resolutionPath );
125
- }
126
- if ( outcome.type === 'all' ) {
127
- for ( const { localName, workflowName } of outcome.bindings ) {
128
- flowImports.push( { localName, workflowName } );
129
- }
130
- path.remove();
131
- return;
132
- }
133
- }
134
-
135
94
  const isTargetImport = isStepsPath( src ) || isSharedStepsPath( src ) ||
136
- isWorkflowPath( src ) ||
137
95
  isEvaluatorsPath( src ) || isSharedEvaluatorsPath( src );
138
96
  if ( !isTargetImport ) {
139
97
  return;
@@ -166,24 +124,6 @@ export default function collectTargetImports(
166
124
  isSharedEvaluatorsPath( src ), buildSharedEvaluatorsNameMap,
167
125
  sharedEvaluatorsNameCache, sharedEvaluatorImports, 'evaluatorName', 'shared evaluators'
168
126
  );
169
- if ( isWorkflowPath( src ) ) {
170
- const { named, default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
171
- for ( const s of path.node.specifiers ) {
172
- if ( isImportDefaultSpecifier( s ) ) {
173
- const localName = s.local.name;
174
- flowImports.push( { localName, workflowName: defName ?? localName } );
175
- } else if ( isImportSpecifier( s ) ) {
176
- const importedName = s.imported.name;
177
- const localName = s.local.name;
178
- const workflowName = named.get( importedName );
179
- if ( workflowName ) {
180
- flowImports.push( { localName, workflowName } );
181
- } else {
182
- throw unresolvedImportError( importedName, 'workflow', absolutePath );
183
- }
184
- }
185
- }
186
- }
187
127
  path.remove();
188
128
  },
189
129
  VariableDeclarator: path => {
@@ -201,38 +141,7 @@ export default function collectTargetImports(
201
141
 
202
142
  const req = firstArgument.value;
203
143
 
204
- if ( isBareNpmSpecifier( req ) && isAbsoluteWorkflowJsResource( resolutionPath ) ) {
205
- if ( isObjectPattern( path.node.id ) ) {
206
- const outcome = resolveBareDestructuredRequireAsWorkflows( {
207
- fromAbsoluteFile: resolutionPath,
208
- specifier: req,
209
- properties: path.node.id.properties,
210
- workflowNameCache
211
- } );
212
- if ( outcome.type === 'partial' ) {
213
- throw mixedBareWorkflowImportError( req, resolutionPath );
214
- }
215
- if ( outcome.type === 'all' ) {
216
- for ( const { localName, workflowName } of outcome.bindings ) {
217
- flowImports.push( { localName, workflowName } );
218
- }
219
- removeRequireDeclarator( path );
220
- return;
221
- }
222
- } else if ( isIdentifier( path.node.id ) ) {
223
- const outcome = resolveBareDefaultRequireAsWorkflow(
224
- resolutionPath, req, path.node.id.name, workflowNameCache
225
- );
226
- if ( outcome.type === 'binding' ) {
227
- flowImports.push( { localName: outcome.localName, workflowName: outcome.workflowName } );
228
- removeRequireDeclarator( path );
229
- return;
230
- }
231
- }
232
- }
233
-
234
144
  const isTargetRequire = isStepsPath( req ) || isSharedStepsPath( req ) ||
235
- isWorkflowPath( req ) ||
236
145
  isEvaluatorsPath( req ) || isSharedEvaluatorsPath( req );
237
146
  if ( !isTargetRequire ) {
238
147
  return;
@@ -263,12 +172,6 @@ export default function collectTargetImports(
263
172
  cache: sharedEvaluatorsNameCache ?? evaluatorsNameCache,
264
173
  target: sharedEvaluatorImports,
265
174
  valueKey: 'evaluatorName', label: 'shared evaluators'
266
- },
267
- {
268
- match: isWorkflowPath,
269
- buildMap: ( p, c ) => buildWorkflowNameMap( p, c ).named,
270
- cache: workflowNameCache, target: flowImports,
271
- valueKey: 'workflowName', label: 'workflow'
272
175
  }
273
176
  ];
274
177
  collectDestructuredRequires(
@@ -276,15 +179,8 @@ export default function collectTargetImports(
276
179
  );
277
180
  return;
278
181
  }
279
-
280
- if ( isWorkflowPath( req ) && isIdentifier( path.node.id ) ) {
281
- const { default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
282
- const localName = path.node.id.name;
283
- flowImports.push( { localName, workflowName: defName ?? localName } );
284
- removeRequireDeclarator( path );
285
- }
286
182
  }
287
183
  } );
288
184
 
289
- return { stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports };
185
+ return { stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports };
290
186
  }
@@ -10,7 +10,7 @@ function makeAst( source, filename ) {
10
10
  }
11
11
 
12
12
  describe( 'collect_target_imports', () => {
13
- it( 'collects ESM imports for steps and workflows and flags changes', () => {
13
+ it( 'collects ESM imports for steps and evaluators and leaves workflow imports intact', () => {
14
14
  const dir = mkdtempSync( join( tmpdir(), 'collect-esm-' ) );
15
15
  writeFileSync( join( dir, 'steps.js' ), `
16
16
  export const StepA = step({ name: 'step.a' });
@@ -27,25 +27,20 @@ import WF, { FlowA } from './workflow.js';
27
27
  const x = 1;`;
28
28
 
29
29
  const ast = makeAst( source, join( dir, 'file.js' ) );
30
- const { stepImports, evaluatorImports, flowImports } = collectTargetImports(
30
+ const { stepImports, evaluatorImports } = collectTargetImports(
31
31
  ast,
32
32
  dir,
33
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
33
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
34
34
  );
35
35
  expect( evaluatorImports ).toEqual( [ { localName: 'EvalA', evaluatorName: 'eval.a' } ] );
36
36
 
37
37
  expect( stepImports ).toEqual( [ { localName: 'StepA', stepName: 'step.a' } ] );
38
- expect( flowImports ).toEqual( [
39
- { localName: 'WF', workflowName: 'flow.def' },
40
- { localName: 'FlowA', workflowName: 'flow.a' }
41
- ] );
42
- // Import declarations should have been removed
43
- expect( ast.program.body.find( n => n.type === 'ImportDeclaration' ) ).toBeUndefined();
38
+ expect( ast.program.body.find( n => n.type === 'ImportDeclaration' )?.source.value ).toBe( './workflow.js' );
44
39
 
45
40
  rmSync( dir, { recursive: true, force: true } );
46
41
  } );
47
42
 
48
- it( 'collects CJS requires and removes declarators (steps + default workflow)', () => {
43
+ it( 'collects CJS requires for steps and evaluators and leaves workflow requires intact', () => {
49
44
  const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-' ) );
50
45
  writeFileSync( join( dir, 'steps.js' ), 'export const StepB = step({ name: \'step.b\' })' );
51
46
  writeFileSync( join( dir, 'evaluators.js' ), 'export const EvalB = evaluator({ name: \'eval.b\' })' );
@@ -58,20 +53,19 @@ const WF = require( './workflow.js' );
58
53
  const obj = {};`;
59
54
 
60
55
  const ast = makeAst( source, join( dir, 'file.js' ) );
61
- const { stepImports, evaluatorImports, flowImports } = collectTargetImports(
56
+ const { stepImports, evaluatorImports } = collectTargetImports(
62
57
  ast,
63
58
  dir,
64
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
59
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
65
60
  );
66
61
  expect( evaluatorImports ).toEqual( [ { localName: 'EvalB', evaluatorName: 'eval.b' } ] );
67
62
 
68
63
  expect( stepImports ).toEqual( [ { localName: 'StepB', stepName: 'step.b' } ] );
69
- expect( flowImports ).toEqual( [ { localName: 'WF', workflowName: 'flow.c' } ] );
70
- // All require-based declarators should have been removed (only non-require decls may remain)
64
+ // Only non-target require declarators should remain.
71
65
  const hasRequireDecl = ast.program.body.some( n =>
72
66
  n.type === 'VariableDeclaration' && n.declarations.some( d => d.init && d.init.type === 'CallExpression' )
73
67
  );
74
- expect( hasRequireDecl ).toBe( false );
68
+ expect( hasRequireDecl ).toBe( true );
75
69
 
76
70
  rmSync( dir, { recursive: true, force: true } );
77
71
  } );
@@ -86,7 +80,7 @@ const obj = {};`;
86
80
  const { evaluatorImports } = collectTargetImports(
87
81
  ast,
88
82
  dir,
89
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
83
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
90
84
  );
91
85
  expect( evaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
92
86
 
@@ -103,7 +97,7 @@ const obj = {};`;
103
97
  const { stepImports } = collectTargetImports(
104
98
  ast,
105
99
  dir,
106
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
100
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
107
101
  );
108
102
  expect( stepImports ).toEqual( [ { localName: 'MyExport', stepName: 'bad' } ] );
109
103
 
@@ -120,7 +114,7 @@ const obj = {};`;
120
114
  const { evaluatorImports } = collectTargetImports(
121
115
  ast,
122
116
  dir,
123
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
117
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
124
118
  );
125
119
  expect( evaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
126
120
 
@@ -137,14 +131,14 @@ const obj = {};`;
137
131
  const { stepImports } = collectTargetImports(
138
132
  ast,
139
133
  dir,
140
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
134
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
141
135
  );
142
136
  expect( stepImports ).toEqual( [ { localName: 'MyExport', stepName: 'bad' } ] );
143
137
 
144
138
  rmSync( dir, { recursive: true, force: true } );
145
139
  } );
146
140
 
147
- it( 'throws when ESM import from workflow.js has non-workflow export', () => {
141
+ it( 'leaves ESM imports from workflow.js alone', () => {
148
142
  const dir = mkdtempSync( join( tmpdir(), 'collect-esm-mismatch-wf-' ) );
149
143
  writeFileSync( join( dir, 'workflow.js' ), 'export const helper = () => 42;' );
150
144
 
@@ -154,13 +148,14 @@ const obj = {};`;
154
148
  expect( () => collectTargetImports(
155
149
  ast,
156
150
  dir,
157
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
158
- ) ).toThrow( /Unresolved import 'helper' from workflow file/ );
151
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
152
+ ) ).not.toThrow();
153
+ expect( ast.program.body.find( n => n.type === 'ImportDeclaration' )?.source.value ).toBe( './workflow.js' );
159
154
 
160
155
  rmSync( dir, { recursive: true, force: true } );
161
156
  } );
162
157
 
163
- it( 'throws when CJS destructured require from workflow.js has non-workflow export', () => {
158
+ it( 'leaves CJS requires from workflow.js alone', () => {
164
159
  const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-mismatch-wf-' ) );
165
160
  writeFileSync( join( dir, 'workflow.js' ), 'export const helper = () => 42;' );
166
161
 
@@ -170,25 +165,26 @@ const obj = {};`;
170
165
  expect( () => collectTargetImports(
171
166
  ast,
172
167
  dir,
173
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
174
- ) ).toThrow( /Unresolved import 'helper' from workflow file/ );
168
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
169
+ ) ).not.toThrow();
170
+ expect( ast.program.body.some( n => n.type === 'VariableDeclaration' ) ).toBe( true );
175
171
 
176
172
  rmSync( dir, { recursive: true, force: true } );
177
173
  } );
178
174
 
179
- it( 'collects CJS destructured require from workflow.js', () => {
175
+ it( 'does not collect CJS destructured require from workflow.js', () => {
180
176
  const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-wf-destruct-' ) );
181
177
  writeFileSync( join( dir, 'workflow.js' ), 'export const FlowX = workflow({ name: \'flow.x\' });' );
182
178
 
183
179
  const source = 'const { FlowX } = require( \'./workflow.js\' );\nconst obj = {};';
184
180
  const ast = makeAst( source, join( dir, 'file.js' ) );
185
181
 
186
- const { flowImports } = collectTargetImports(
182
+ collectTargetImports(
187
183
  ast,
188
184
  dir,
189
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
185
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map() }
190
186
  );
191
- expect( flowImports ).toEqual( [ { localName: 'FlowX', workflowName: 'flow.x' } ] );
187
+ expect( ast.program.body.some( n => n.type === 'VariableDeclaration' ) ).toBe( true );
192
188
 
193
189
  rmSync( dir, { recursive: true, force: true } );
194
190
  } );
@@ -208,7 +204,7 @@ const obj = {};`;
208
204
  const { sharedEvaluatorImports } = collectTargetImports(
209
205
  ast,
210
206
  join( dir, 'workflows', 'my_workflow' ),
211
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
207
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map() }
212
208
  );
213
209
  expect( sharedEvaluatorImports ).toEqual( [ { localName: 'SharedEval', evaluatorName: 'shared.eval' } ] );
214
210
 
@@ -230,7 +226,7 @@ const obj = {};`;
230
226
  const { sharedEvaluatorImports } = collectTargetImports(
231
227
  ast,
232
228
  join( dir, 'workflows', 'my_workflow' ),
233
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
229
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map() }
234
230
  );
235
231
  expect( sharedEvaluatorImports ).toEqual( [ { localName: 'SharedEval', evaluatorName: 'shared.eval' } ] );
236
232
 
@@ -254,8 +250,7 @@ const obj = {};`;
254
250
  join( dir, 'workflows', 'my_workflow' ),
255
251
  {
256
252
  stepsNameCache: new Map(), sharedStepsNameCache: new Map(),
257
- evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(),
258
- workflowNameCache: new Map()
253
+ evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map()
259
254
  }
260
255
  );
261
256
  expect( sharedStepImports ).toEqual( [ { localName: 'SharedA', stepName: 'shared.a' } ] );
@@ -280,8 +275,7 @@ const obj = {};`;
280
275
  join( dir, 'workflows', 'my_workflow' ),
281
276
  {
282
277
  stepsNameCache: new Map(), sharedStepsNameCache: new Map(),
283
- evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(),
284
- workflowNameCache: new Map()
278
+ evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map()
285
279
  }
286
280
  );
287
281
  expect( sharedStepImports ).toEqual( [ { localName: 'MyExport', stepName: 'bad' } ] );
@@ -304,7 +298,7 @@ const obj = {};`;
304
298
  const { sharedEvaluatorImports } = collectTargetImports(
305
299
  ast,
306
300
  join( dir, 'workflows', 'my_workflow' ),
307
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
301
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map() }
308
302
  );
309
303
  expect( sharedEvaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
310
304
 
@@ -326,78 +320,12 @@ const obj = {};`;
326
320
  const { sharedEvaluatorImports } = collectTargetImports(
327
321
  ast,
328
322
  join( dir, 'workflows', 'my_workflow' ),
329
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
323
+ { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map() }
330
324
  );
331
325
  expect( sharedEvaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
332
326
 
333
327
  rmSync( dir, { recursive: true, force: true } );
334
328
  } );
335
329
 
336
- it( 'collects ESM imports from @growthxlabs/workflows_catalog', () => {
337
- const dir = mkdtempSync( join( tmpdir(), 'collect-cat-esm-' ) );
338
- const pkgRoot = join( dir, 'node_modules', '@growthxlabs', 'workflows_catalog' );
339
- const srcDir = join( pkgRoot, 'src' );
340
- mkdirSync( join( srcDir, 'workflows', 'wf' ), { recursive: true } );
341
- writeFileSync( join( pkgRoot, 'package.json' ), JSON.stringify( {
342
- name: '@growthxlabs/workflows_catalog',
343
- type: 'module',
344
- main: './src/index.js',
345
- dependencies: { '@outputai/core': '1.0.0' }
346
- } ) );
347
- writeFileSync( join( srcDir, 'index.js' ), 'export { default as sumNumbers } from \'./workflows/wf/workflow.js\';\n' );
348
- writeFileSync( join( srcDir, 'workflows', 'wf', 'workflow.js' ), 'export default workflow({ name: \'cat.flow\' });\n' );
349
-
350
- const fileDir = join( dir, 'consumer' );
351
- mkdirSync( fileDir, { recursive: true } );
352
- const resourcePath = join( fileDir, 'workflow.js' );
353
-
354
- const source = `
355
- import { sumNumbers as SN } from '@growthxlabs/workflows_catalog';
356
- const x = 1;`;
357
- const ast = makeAst( source, resourcePath );
358
-
359
- const { flowImports } = collectTargetImports(
360
- ast,
361
- fileDir,
362
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() },
363
- resourcePath
364
- );
365
- expect( flowImports ).toEqual( [ { localName: 'SN', workflowName: 'cat.flow' } ] );
366
- expect( ast.program.body.find( n => n.type === 'ImportDeclaration' ) ).toBeUndefined();
367
-
368
- rmSync( dir, { recursive: true, force: true } );
369
- } );
370
-
371
- it( 'collects CJS destructured require from @growthxlabs/workflows_catalog', () => {
372
- const dir = mkdtempSync( join( tmpdir(), 'collect-cat-cjs-' ) );
373
- const pkgRoot = join( dir, 'node_modules', '@growthxlabs', 'workflows_catalog' );
374
- const srcDir = join( pkgRoot, 'src' );
375
- mkdirSync( join( srcDir, 'workflows', 'wf' ), { recursive: true } );
376
- writeFileSync( join( pkgRoot, 'package.json' ), JSON.stringify( {
377
- name: '@growthxlabs/workflows_catalog',
378
- type: 'module',
379
- main: './src/index.js',
380
- dependencies: { '@outputai/core': '1.0.0' }
381
- } ) );
382
- writeFileSync( join( srcDir, 'index.js' ), 'export { default as sumNumbers } from \'./workflows/wf/workflow.js\';\n' );
383
- writeFileSync( join( srcDir, 'workflows', 'wf', 'workflow.js' ), 'export default workflow({ name: \'cat.flow2\' });\n' );
384
-
385
- const fileDir = join( dir, 'consumer' );
386
- mkdirSync( fileDir, { recursive: true } );
387
- const resourcePath = join( fileDir, 'workflow.js' );
388
-
389
- const source = 'const { sumNumbers } = require( \'@growthxlabs/workflows_catalog\' );\nconst x = 1;';
390
- const ast = makeAst( source, resourcePath );
391
-
392
- const { flowImports } = collectTargetImports(
393
- ast,
394
- fileDir,
395
- { stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() },
396
- resourcePath
397
- );
398
- expect( flowImports ).toEqual( [ { localName: 'sumNumbers', workflowName: 'cat.flow2' } ] );
399
-
400
- rmSync( dir, { recursive: true, force: true } );
401
- } );
402
330
  } );
403
331
 
@@ -13,10 +13,9 @@ const stepsNameCache = new Map(); // path -> Map<exported, stepName>
13
13
  const sharedStepsNameCache = new Map(); // path -> Map<exported, stepName> (shared)
14
14
  const evaluatorsNameCache = new Map(); // path -> Map<exported, evaluatorName>
15
15
  const sharedEvaluatorsNameCache = new Map(); // path -> Map<exported, evaluatorName> (shared)
16
- const workflowNameCache = new Map(); // path -> { default?: name, named: Map<exported, flowName> }
17
16
 
18
17
  /**
19
- * Webpack loader that rewrites step/workflow calls by reading names from
18
+ * Webpack loader that rewrites step/evaluator calls by reading names from
20
19
  * the respective modules and transforming `fn` bodies accordingly.
21
20
  * Preserves sourcemaps.
22
21
  *
@@ -28,21 +27,21 @@ const workflowNameCache = new Map(); // path -> { default?: name, named: Map<exp
28
27
  export default function stepImportRewriterAstLoader( source, inputMap ) {
29
28
  this.cacheable?.( true );
30
29
  const callback = this.async?.() ?? this.callback;
31
- const cache = { stepsNameCache, sharedStepsNameCache, evaluatorsNameCache, sharedEvaluatorsNameCache, workflowNameCache };
30
+ const cache = { stepsNameCache, sharedStepsNameCache, evaluatorsNameCache, sharedEvaluatorsNameCache };
32
31
 
33
32
  try {
34
33
  const filename = this.resourcePath;
35
34
  const ast = parse( String( source ), filename );
36
35
  const fileDir = dirname( filename );
37
- const { stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports } =
36
+ const { stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports } =
38
37
  collectTargetImports( ast, fileDir, cache, filename );
39
38
 
40
39
  // No imports
41
- if ( [].concat( stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports ).length === 0 ) {
40
+ if ( [].concat( stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports ).length === 0 ) {
42
41
  return callback( null, source, inputMap );
43
42
  }
44
43
 
45
- const rewrote = rewriteFnBodies( { ast, stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports } );
44
+ const rewrote = rewriteFnBodies( { ast, stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports } );
46
45
  // No edits performed
47
46
  if ( !rewrote ) {
48
47
  return callback( null, source, inputMap );