@output.ai/core 0.0.14 → 0.0.16
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/bin/worker.sh +1 -1
- package/package.json +5 -8
- package/src/consts.js +8 -1
- package/src/index.d.ts +169 -7
- package/src/index.js +18 -1
- package/src/interface/evaluator.js +146 -0
- package/src/interface/step.js +4 -9
- package/src/interface/{schema_utils.js → validations/runtime.js} +0 -14
- package/src/interface/validations/runtime.spec.js +29 -0
- package/src/interface/validations/schema_utils.js +8 -0
- package/src/interface/validations/static.js +13 -1
- package/src/interface/validations/static.spec.js +29 -1
- package/src/interface/workflow.js +3 -8
- package/src/internal_activities/index.js +6 -0
- package/src/worker/catalog_workflow/catalog.js +19 -10
- package/src/worker/catalog_workflow/index.js +30 -1
- package/src/worker/catalog_workflow/index.spec.js +83 -22
- package/src/worker/index.js +1 -1
- package/src/worker/interceptors/activity.js +16 -3
- package/src/worker/loader.js +2 -2
- package/src/worker/tracer/index.js +1 -1
- package/src/worker/tracer/index.test.js +1 -2
- package/src/worker/tracer/tracer_tree.js +4 -3
- package/src/worker/tracer/tracer_tree.test.js +1 -1
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +35 -4
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +12 -4
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -4
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +13 -4
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +16 -2
- package/src/worker/webpack_loaders/workflow_rewriter/tools.js +46 -13
- package/src/worker/webpack_loaders/workflow_rewriter/tools.spec.js +20 -2
- /package/src/interface/{schema_utils.spec.js → validations/schema_utils.spec.js} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { FatalError } from '#errors';
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { setMetadata } from '../interface/metadata.js';
|
|
5
|
+
import { ComponentType } from '#consts';
|
|
4
6
|
|
|
5
7
|
const callerDir = process.argv[2];
|
|
6
8
|
|
|
@@ -38,6 +40,8 @@ export const sendWebhookPost = async ( { url, workflowId, payload } ) => {
|
|
|
38
40
|
}
|
|
39
41
|
};
|
|
40
42
|
|
|
43
|
+
setMetadata( sendWebhookPost, { type: ComponentType.INTERNAL_STEP } );
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
46
|
* Read the trace file of a given execution and returns the content
|
|
43
47
|
*
|
|
@@ -65,3 +69,5 @@ export const readTraceFile = async ( { workflowType, workflowId } ) => {
|
|
|
65
69
|
const file = join( dir, matchingFile );
|
|
66
70
|
return existsSync( file ) ? readFileSync( file, 'utf-8' ).split( '\n' ) : [];
|
|
67
71
|
};
|
|
72
|
+
|
|
73
|
+
setMetadata( readTraceFile, { type: ComponentType.INTERNAL_STEP, skipTrace: true } );
|
|
@@ -73,6 +73,18 @@ class CatalogEntry {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Describes a single activity within a workflow.
|
|
78
|
+
*
|
|
79
|
+
* @class
|
|
80
|
+
* @extends CatalogEntry
|
|
81
|
+
*/
|
|
82
|
+
export class CatalogActivity extends CatalogEntry {}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param { CatalogWorkflowOptions}
|
|
86
|
+
*/
|
|
87
|
+
|
|
76
88
|
/**
|
|
77
89
|
* Describes a single workflow within the catalog.
|
|
78
90
|
*
|
|
@@ -87,19 +99,16 @@ export class CatalogWorkflow extends CatalogEntry {
|
|
|
87
99
|
activities;
|
|
88
100
|
|
|
89
101
|
/**
|
|
90
|
-
* @
|
|
91
|
-
* @param {
|
|
102
|
+
* @param {Object} params - Entry parameters.
|
|
103
|
+
* @param {string} params.name - Name of the entry.
|
|
104
|
+
* @param {string} [params.description] - Optional description.
|
|
105
|
+
* @param {object} [params.inputSchema] - JSON schema describing the expected input.
|
|
106
|
+
* @param {object} [params.outputSchema] - JSON schema describing the produced output.
|
|
107
|
+
* @param {string} params.path - Absolute path of the entity in the file system.
|
|
108
|
+
* @param {Array<CatalogActivity>} params.activities - Each activity of this workflow
|
|
92
109
|
*/
|
|
93
110
|
constructor( { activities, ...args } ) {
|
|
94
111
|
super( args );
|
|
95
112
|
this.activities = activities;
|
|
96
113
|
};
|
|
97
114
|
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Describes a single activity within a workflow.
|
|
101
|
-
*
|
|
102
|
-
* @class
|
|
103
|
-
* @extends CatalogEntry
|
|
104
|
-
*/
|
|
105
|
-
export class CatalogActivity extends CatalogEntry {}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { METADATA_ACCESS_SYMBOL } from '#consts';
|
|
2
3
|
import { Catalog, CatalogActivity, CatalogWorkflow } from './catalog.js';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Zod schema to JSON Schema format.
|
|
7
|
+
*
|
|
8
|
+
* @param {any} schema - A zod schema
|
|
9
|
+
* @returns {object|null} JSON Schema object, or null if schema is invalid
|
|
10
|
+
*/
|
|
11
|
+
const convertToJsonSchema = schema => {
|
|
12
|
+
if ( !schema ) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return z.toJSONSchema( schema );
|
|
18
|
+
} catch ( error ) {
|
|
19
|
+
console.warn( 'Invalid schema provided (expected Zod schema):', schema, error );
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* Converts the list of workflows and the activities into the catalog information.
|
|
6
26
|
*
|
|
@@ -14,8 +34,17 @@ export const createCatalog = ( { workflows, activities } ) =>
|
|
|
14
34
|
workflows.reduce( ( catalog, workflow ) =>
|
|
15
35
|
catalog.addWorkflow( new CatalogWorkflow( {
|
|
16
36
|
...workflow,
|
|
37
|
+
inputSchema: convertToJsonSchema( workflow.inputSchema ),
|
|
38
|
+
outputSchema: convertToJsonSchema( workflow.outputSchema ),
|
|
17
39
|
activities: Object.entries( activities )
|
|
18
40
|
.filter( ( [ k ] ) => k.startsWith( `${workflow.path}#` ) )
|
|
19
|
-
.map( ( [ _, v ] ) =>
|
|
41
|
+
.map( ( [ _, v ] ) => {
|
|
42
|
+
const metadata = v[METADATA_ACCESS_SYMBOL];
|
|
43
|
+
return new CatalogActivity( {
|
|
44
|
+
...metadata,
|
|
45
|
+
inputSchema: convertToJsonSchema( metadata.inputSchema ),
|
|
46
|
+
outputSchema: convertToJsonSchema( metadata.outputSchema )
|
|
47
|
+
} );
|
|
48
|
+
} )
|
|
20
49
|
} ) )
|
|
21
50
|
, new Catalog() );
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
|
|
3
4
|
// Provide the same symbol to the module under test and to the test
|
|
4
5
|
const METADATA_ACCESS_SYMBOL = Symbol( '__metadata' );
|
|
@@ -19,16 +20,16 @@ describe( 'createCatalog', () => {
|
|
|
19
20
|
path: '/flows/flow1',
|
|
20
21
|
pathname: '/flows/flow1/workflow.js',
|
|
21
22
|
description: 'desc-flow1',
|
|
22
|
-
inputSchema: { in: 'f1' },
|
|
23
|
-
outputSchema: { out: 'f1' }
|
|
23
|
+
inputSchema: z.object( { in: z.literal( 'f1' ) } ),
|
|
24
|
+
outputSchema: z.object( { out: z.literal( 'f1' ) } )
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
27
|
name: 'flow2',
|
|
27
28
|
path: '/flows/flow2',
|
|
28
29
|
pathname: '/flows/flow2/workflow.js',
|
|
29
30
|
description: 'desc-flow2',
|
|
30
|
-
inputSchema: { in: 'f2' },
|
|
31
|
-
outputSchema: { out: 'f2' }
|
|
31
|
+
inputSchema: z.object( { in: z.literal( 'f2' ) } ),
|
|
32
|
+
outputSchema: z.object( { out: z.literal( 'f2' ) } )
|
|
32
33
|
}
|
|
33
34
|
];
|
|
34
35
|
|
|
@@ -37,8 +38,8 @@ describe( 'createCatalog', () => {
|
|
|
37
38
|
name: 'A1',
|
|
38
39
|
path: '/flows/flow1#A1',
|
|
39
40
|
description: 'desc-a1',
|
|
40
|
-
inputSchema: { in: 'a1' },
|
|
41
|
-
outputSchema: { out: 'a1' }
|
|
41
|
+
inputSchema: z.object( { in: z.literal( 'a1' ) } ),
|
|
42
|
+
outputSchema: z.object( { out: z.literal( 'a1' ) } )
|
|
42
43
|
} );
|
|
43
44
|
|
|
44
45
|
const activity2 = () => {};
|
|
@@ -46,8 +47,8 @@ describe( 'createCatalog', () => {
|
|
|
46
47
|
name: 'A2',
|
|
47
48
|
path: '/flows/flow1#A2',
|
|
48
49
|
description: 'desc-a2',
|
|
49
|
-
inputSchema: { in: 'a2' },
|
|
50
|
-
outputSchema: { out: 'a2' }
|
|
50
|
+
inputSchema: z.object( { in: z.literal( 'a2' ) } ),
|
|
51
|
+
outputSchema: z.object( { out: z.literal( 'a2' ) } )
|
|
51
52
|
} );
|
|
52
53
|
|
|
53
54
|
const activity3 = () => {};
|
|
@@ -55,8 +56,8 @@ describe( 'createCatalog', () => {
|
|
|
55
56
|
name: 'B1',
|
|
56
57
|
path: '/flows/flow2#B1',
|
|
57
58
|
description: 'desc-b1',
|
|
58
|
-
inputSchema: { in: 'b1' },
|
|
59
|
-
outputSchema: { out: 'b1' }
|
|
59
|
+
inputSchema: z.object( { in: z.literal( 'b1' ) } ),
|
|
60
|
+
outputSchema: z.object( { out: z.literal( 'b1' ) } )
|
|
60
61
|
} );
|
|
61
62
|
|
|
62
63
|
const activity4 = () => {};
|
|
@@ -64,8 +65,8 @@ describe( 'createCatalog', () => {
|
|
|
64
65
|
name: 'X',
|
|
65
66
|
path: '/other#X',
|
|
66
67
|
description: 'desc-x',
|
|
67
|
-
inputSchema: { in: 'x' },
|
|
68
|
-
outputSchema: { out: 'x' }
|
|
68
|
+
inputSchema: z.object( { in: z.literal( 'x' ) } ),
|
|
69
|
+
outputSchema: z.object( { out: z.literal( 'x' ) } )
|
|
69
70
|
} );
|
|
70
71
|
|
|
71
72
|
const activities = {
|
|
@@ -96,20 +97,56 @@ describe( 'createCatalog', () => {
|
|
|
96
97
|
name: 'flow1',
|
|
97
98
|
path: '/flows/flow1',
|
|
98
99
|
description: 'desc-flow1',
|
|
99
|
-
inputSchema: {
|
|
100
|
-
|
|
100
|
+
inputSchema: {
|
|
101
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: { in: { type: 'string', const: 'f1' } },
|
|
104
|
+
required: [ 'in' ],
|
|
105
|
+
additionalProperties: false
|
|
106
|
+
},
|
|
107
|
+
outputSchema: {
|
|
108
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: { out: { type: 'string', const: 'f1' } },
|
|
111
|
+
required: [ 'out' ],
|
|
112
|
+
additionalProperties: false
|
|
113
|
+
},
|
|
101
114
|
activities: [
|
|
102
115
|
{
|
|
103
116
|
name: 'A1',
|
|
104
117
|
description: 'desc-a1',
|
|
105
|
-
inputSchema: {
|
|
106
|
-
|
|
118
|
+
inputSchema: {
|
|
119
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: { in: { type: 'string', const: 'a1' } },
|
|
122
|
+
required: [ 'in' ],
|
|
123
|
+
additionalProperties: false
|
|
124
|
+
},
|
|
125
|
+
outputSchema: {
|
|
126
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: { out: { type: 'string', const: 'a1' } },
|
|
129
|
+
required: [ 'out' ],
|
|
130
|
+
additionalProperties: false
|
|
131
|
+
}
|
|
107
132
|
},
|
|
108
133
|
{
|
|
109
134
|
name: 'A2',
|
|
110
135
|
description: 'desc-a2',
|
|
111
|
-
inputSchema: {
|
|
112
|
-
|
|
136
|
+
inputSchema: {
|
|
137
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: { in: { type: 'string', const: 'a2' } },
|
|
140
|
+
required: [ 'in' ],
|
|
141
|
+
additionalProperties: false
|
|
142
|
+
},
|
|
143
|
+
outputSchema: {
|
|
144
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: { out: { type: 'string', const: 'a2' } },
|
|
147
|
+
required: [ 'out' ],
|
|
148
|
+
additionalProperties: false
|
|
149
|
+
}
|
|
113
150
|
}
|
|
114
151
|
]
|
|
115
152
|
},
|
|
@@ -117,14 +154,38 @@ describe( 'createCatalog', () => {
|
|
|
117
154
|
name: 'flow2',
|
|
118
155
|
path: '/flows/flow2',
|
|
119
156
|
description: 'desc-flow2',
|
|
120
|
-
inputSchema: {
|
|
121
|
-
|
|
157
|
+
inputSchema: {
|
|
158
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: { in: { type: 'string', const: 'f2' } },
|
|
161
|
+
required: [ 'in' ],
|
|
162
|
+
additionalProperties: false
|
|
163
|
+
},
|
|
164
|
+
outputSchema: {
|
|
165
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: { out: { type: 'string', const: 'f2' } },
|
|
168
|
+
required: [ 'out' ],
|
|
169
|
+
additionalProperties: false
|
|
170
|
+
},
|
|
122
171
|
activities: [
|
|
123
172
|
{
|
|
124
173
|
name: 'B1',
|
|
125
174
|
description: 'desc-b1',
|
|
126
|
-
inputSchema: {
|
|
127
|
-
|
|
175
|
+
inputSchema: {
|
|
176
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: { in: { type: 'string', const: 'b1' } },
|
|
179
|
+
required: [ 'in' ],
|
|
180
|
+
additionalProperties: false
|
|
181
|
+
},
|
|
182
|
+
outputSchema: {
|
|
183
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: { out: { type: 'string', const: 'b1' } },
|
|
186
|
+
required: [ 'out' ],
|
|
187
|
+
additionalProperties: false
|
|
188
|
+
}
|
|
128
189
|
}
|
|
129
190
|
]
|
|
130
191
|
}
|
package/src/worker/index.js
CHANGED
|
@@ -47,7 +47,7 @@ const callerDir = process.argv[2];
|
|
|
47
47
|
sinks,
|
|
48
48
|
interceptors: {
|
|
49
49
|
workflowModules: [ join( __dirname, './interceptors/workflow.js' ) ],
|
|
50
|
-
activityInbound: [ () => new ActivityExecutionInterceptor() ]
|
|
50
|
+
activityInbound: [ () => new ActivityExecutionInterceptor( activities ) ]
|
|
51
51
|
},
|
|
52
52
|
maxConcurrentWorkflowTaskExecutions: maxWorkflows,
|
|
53
53
|
maxConcurrentActivityTaskExecutions: maxActivities,
|
|
@@ -2,7 +2,8 @@ import { Context } from '@temporalio/activity';
|
|
|
2
2
|
import { Storage } from '../async_storage.js';
|
|
3
3
|
import { trace } from '../tracer/index.js';
|
|
4
4
|
import { headersToObject } from '../sandboxed_utils.js';
|
|
5
|
-
import { THIS_LIB_NAME
|
|
5
|
+
import { THIS_LIB_NAME } from '#consts';
|
|
6
|
+
import { METADATA_ACCESS_SYMBOL } from '#consts';
|
|
6
7
|
|
|
7
8
|
/*
|
|
8
9
|
This interceptor is called for every activity execution
|
|
@@ -14,14 +15,26 @@ import { THIS_LIB_NAME, TraceEvent } from '#consts';
|
|
|
14
15
|
Some information it needs for its context comes from Temporal's Activity Context others are injected in the headers
|
|
15
16
|
*/
|
|
16
17
|
export class ActivityExecutionInterceptor {
|
|
18
|
+
constructor( activities ) {
|
|
19
|
+
this.activities = activities;
|
|
20
|
+
};
|
|
21
|
+
|
|
17
22
|
async execute( input, next ) {
|
|
18
23
|
const { workflowExecution: { workflowId }, activityId, activityType, workflowType } = Context.current().info;
|
|
24
|
+
|
|
25
|
+
const activityFn = this.activities?.[activityType];
|
|
26
|
+
const { type: componentType, skipTrace } = activityFn?.[METADATA_ACCESS_SYMBOL];
|
|
27
|
+
|
|
19
28
|
const context = { workflowId, workflowType, activityId, activityType, ...headersToObject( input.headers ) };
|
|
20
29
|
|
|
21
30
|
return Storage.runWithContext( async _ => {
|
|
22
|
-
|
|
31
|
+
if ( !skipTrace ) {
|
|
32
|
+
trace( { lib: THIS_LIB_NAME, event: `${componentType}_start`, input: input.args } );
|
|
33
|
+
}
|
|
23
34
|
const output = await next( input );
|
|
24
|
-
|
|
35
|
+
if ( !skipTrace ) {
|
|
36
|
+
trace( { lib: THIS_LIB_NAME, event: `${componentType}_end`, output } );
|
|
37
|
+
}
|
|
25
38
|
return output;
|
|
26
39
|
}, context );
|
|
27
40
|
}
|
package/src/worker/loader.js
CHANGED
|
@@ -12,10 +12,10 @@ const __dirname = dirname( fileURLToPath( import.meta.url ) );
|
|
|
12
12
|
|
|
13
13
|
// returns a map of activities, where the key is they path + name and the value is the function with metadata
|
|
14
14
|
export async function loadActivities( path ) {
|
|
15
|
-
const activityPaths = recursiveNavigateWhileCollecting( path, [ 'steps.js' ] );
|
|
15
|
+
const activityPaths = recursiveNavigateWhileCollecting( path, [ 'steps.js', 'evaluators.js' ] );
|
|
16
16
|
const activities = [];
|
|
17
17
|
for await ( const { component, metadata, pathname, path } of iteratorOverImportedComponents( activityPaths ) ) {
|
|
18
|
-
console.log( '[Core.Scanner]', '
|
|
18
|
+
console.log( '[Core.Scanner]', 'Component loaded:', metadata.type, metadata.name, 'at', pathname );
|
|
19
19
|
activities[`${path}#${metadata.name}`] = component;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -48,7 +48,7 @@ export function trace( { lib, event, input = undefined, output = undefined } ) {
|
|
|
48
48
|
rootWorkflowType
|
|
49
49
|
} = Storage.load();
|
|
50
50
|
|
|
51
|
-
const entry = {
|
|
51
|
+
const entry = { event, input, lib, output, parentWorkflowId, stepId, stepName, timestamp: now, workflowId, workflowPath, workflowType };
|
|
52
52
|
|
|
53
53
|
// test for rootWorkflow to append to the same file as the parent/grandparent
|
|
54
54
|
const outputDir = join( callerDir, 'logs', 'runs', rootWorkflowType ?? workflowType );
|
|
@@ -4,7 +4,7 @@ import { tmpdir, EOL } from 'node:os';
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
import { THIS_LIB_NAME } from '#consts';
|
|
6
6
|
|
|
7
|
-
const createTempDir = () => mkdtempSync( join( tmpdir(), '
|
|
7
|
+
const createTempDir = () => mkdtempSync( join( tmpdir(), 'output-sdk-trace-' ) );
|
|
8
8
|
|
|
9
9
|
// Single mocks (configured per-test by mutating the backing objects)
|
|
10
10
|
const mockConfig = { tracing: { enabled: false } };
|
|
@@ -32,7 +32,6 @@ describe( 'tracer/index', () => {
|
|
|
32
32
|
vi.clearAllMocks();
|
|
33
33
|
vi.useFakeTimers();
|
|
34
34
|
vi.setSystemTime( new Date( '2020-01-01T00:00:00.000Z' ) );
|
|
35
|
-
|
|
36
35
|
} );
|
|
37
36
|
|
|
38
37
|
afterEach( () => {
|
|
@@ -46,11 +46,12 @@ export const buildLogTree = src => {
|
|
|
46
46
|
const { event, workflowId, workflowType, workflowPath, parentWorkflowId, stepId, stepName, input, output, timestamp } = entry;
|
|
47
47
|
|
|
48
48
|
const baseEntry = { children: [], startedAt: timestamp, workflowId };
|
|
49
|
-
if (
|
|
50
|
-
|
|
49
|
+
if ( [ TraceEvent.EVALUATOR_START, TraceEvent.STEP_START ].includes( event ) ) {
|
|
50
|
+
const eventName = event === TraceEvent.EVALUATOR_START ? 'evaluator' : 'step';
|
|
51
|
+
stepsMap.set( `${workflowId}:${stepId}`, { event: eventName, input, stepId, stepName, ...baseEntry } );
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
if (
|
|
54
|
+
if ( [ TraceEvent.EVALUATOR_END, TraceEvent.STEP_END ].includes( event ) ) {
|
|
54
55
|
Object.assign( stepsMap.get( `${workflowId}:${stepId}` ) ?? {}, { output, endedAt: timestamp } );
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -7,7 +7,7 @@ import { EOL } from 'os';
|
|
|
7
7
|
import { buildLogTree } from './tracer_tree.js';
|
|
8
8
|
import { THIS_LIB_NAME, TraceEvent } from '#consts';
|
|
9
9
|
|
|
10
|
-
const createTempDir = () => mkdtempSync( join( tmpdir(), '
|
|
10
|
+
const createTempDir = () => mkdtempSync( join( tmpdir(), 'output-sdk-trace-tree-' ) );
|
|
11
11
|
|
|
12
12
|
describe( 'tracer/tracer_tree', () => {
|
|
13
13
|
it( 'builds a tree JSON from a raw log file', () => {
|
|
@@ -2,9 +2,11 @@ import traverseModule from '@babel/traverse';
|
|
|
2
2
|
import {
|
|
3
3
|
buildWorkflowNameMap,
|
|
4
4
|
getLocalNameFromDestructuredProperty,
|
|
5
|
+
isEvaluatorsPath,
|
|
5
6
|
isStepsPath,
|
|
6
7
|
isWorkflowPath,
|
|
7
8
|
buildStepsNameMap,
|
|
9
|
+
buildEvaluatorsNameMap,
|
|
8
10
|
toAbsolutePath
|
|
9
11
|
} from './tools.js';
|
|
10
12
|
import {
|
|
@@ -34,15 +36,16 @@ const traverse = traverseModule.default ?? traverseModule;
|
|
|
34
36
|
* @returns {{ stepImports: Array<{localName:string,stepName:string}>,
|
|
35
37
|
* flowImports: Array<{localName:string,workflowName:string}> }} Collected info mappings.
|
|
36
38
|
*/
|
|
37
|
-
export default function collectTargetImports( ast, fileDir, { stepsNameCache, workflowNameCache } ) {
|
|
39
|
+
export default function collectTargetImports( ast, fileDir, { stepsNameCache, workflowNameCache, evaluatorsNameCache } ) {
|
|
38
40
|
const stepImports = [];
|
|
39
41
|
const flowImports = [];
|
|
42
|
+
const evaluatorImports = [];
|
|
40
43
|
|
|
41
44
|
traverse( ast, {
|
|
42
45
|
ImportDeclaration: path => {
|
|
43
46
|
const src = path.node.source.value;
|
|
44
47
|
// Ignore other imports
|
|
45
|
-
if ( !isStepsPath( src ) && !isWorkflowPath( src ) ) {
|
|
48
|
+
if ( !isStepsPath( src ) && !isWorkflowPath( src ) && !isEvaluatorsPath( src ) ) {
|
|
46
49
|
return;
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -58,6 +61,17 @@ export default function collectTargetImports( ast, fileDir, { stepsNameCache, wo
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
if ( isEvaluatorsPath( src ) ) {
|
|
65
|
+
const nameMap = buildEvaluatorsNameMap( absolutePath, evaluatorsNameCache );
|
|
66
|
+
for ( const s of path.node.specifiers.filter( s => isImportSpecifier( s ) ) ) {
|
|
67
|
+
const importedName = s.imported.name;
|
|
68
|
+
const localName = s.local.name;
|
|
69
|
+
const evaluatorName = nameMap.get( importedName );
|
|
70
|
+
if ( evaluatorName ) {
|
|
71
|
+
evaluatorImports.push( { localName, evaluatorName } );
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
61
75
|
if ( isWorkflowPath( src ) ) {
|
|
62
76
|
const { named, default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
|
|
63
77
|
for ( const s of path.node.specifiers ) {
|
|
@@ -94,7 +108,7 @@ export default function collectTargetImports( ast, fileDir, { stepsNameCache, wo
|
|
|
94
108
|
|
|
95
109
|
const req = firstArgument.value;
|
|
96
110
|
// Must be steps/workflows module
|
|
97
|
-
if ( !isStepsPath( req ) && !isWorkflowPath( req ) ) {
|
|
111
|
+
if ( !isStepsPath( req ) && !isWorkflowPath( req ) && !isEvaluatorsPath( req ) ) {
|
|
98
112
|
return;
|
|
99
113
|
}
|
|
100
114
|
|
|
@@ -116,6 +130,23 @@ export default function collectTargetImports( ast, fileDir, { stepsNameCache, wo
|
|
|
116
130
|
} else {
|
|
117
131
|
path.remove();
|
|
118
132
|
}
|
|
133
|
+
} else if ( isEvaluatorsPath( req ) && isObjectPattern( path.node.id ) ) {
|
|
134
|
+
const nameMap = buildEvaluatorsNameMap( absolutePath, evaluatorsNameCache );
|
|
135
|
+
for ( const prop of path.node.id.properties.filter( prop => isObjectProperty( prop ) && isIdentifier( prop.key ) ) ) {
|
|
136
|
+
const importedName = prop.key.name;
|
|
137
|
+
const localName = getLocalNameFromDestructuredProperty( prop );
|
|
138
|
+
if ( localName ) {
|
|
139
|
+
const evaluatorName = nameMap.get( importedName );
|
|
140
|
+
if ( evaluatorName ) {
|
|
141
|
+
evaluatorImports.push( { localName, evaluatorName } );
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if ( isVariableDeclaration( path.parent ) && path.parent.declarations.length === 1 ) {
|
|
146
|
+
path.parentPath.remove();
|
|
147
|
+
} else {
|
|
148
|
+
path.remove();
|
|
149
|
+
}
|
|
119
150
|
} else if ( isWorkflowPath( req ) && isIdentifier( path.node.id ) ) {
|
|
120
151
|
const { default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
|
|
121
152
|
const localName = path.node.id.name;
|
|
@@ -129,5 +160,5 @@ export default function collectTargetImports( ast, fileDir, { stepsNameCache, wo
|
|
|
129
160
|
}
|
|
130
161
|
} );
|
|
131
162
|
|
|
132
|
-
return { stepImports, flowImports };
|
|
163
|
+
return { stepImports, evaluatorImports, flowImports };
|
|
133
164
|
};
|
|
@@ -16,6 +16,9 @@ describe( 'collect_target_imports', () => {
|
|
|
16
16
|
'export const StepA = step({ name: "step.a" })',
|
|
17
17
|
'export const StepB = step({ name: "step.b" })'
|
|
18
18
|
].join( '\n' ) );
|
|
19
|
+
writeFileSync( join( dir, 'evaluators.js' ), [
|
|
20
|
+
'export const EvalA = evaluator({ name: "eval.a" })'
|
|
21
|
+
].join( '\n' ) );
|
|
19
22
|
writeFileSync( join( dir, 'workflow.js' ), [
|
|
20
23
|
'export const FlowA = workflow({ name: "flow.a" })',
|
|
21
24
|
'export default workflow({ name: "flow.def" })'
|
|
@@ -23,16 +26,18 @@ describe( 'collect_target_imports', () => {
|
|
|
23
26
|
|
|
24
27
|
const source = [
|
|
25
28
|
'import { StepA } from "./steps.js";',
|
|
29
|
+
'import { EvalA } from "./evaluators.js";',
|
|
26
30
|
'import WF, { FlowA } from "./workflow.js";',
|
|
27
31
|
'const x = 1;'
|
|
28
32
|
].join( '\n' );
|
|
29
33
|
|
|
30
34
|
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
31
|
-
const { stepImports, flowImports } = collectTargetImports(
|
|
35
|
+
const { stepImports, evaluatorImports, flowImports } = collectTargetImports(
|
|
32
36
|
ast,
|
|
33
37
|
dir,
|
|
34
|
-
{ stepsNameCache: new Map(), workflowNameCache: new Map() }
|
|
38
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
35
39
|
);
|
|
40
|
+
expect( evaluatorImports ).toEqual( [ { localName: 'EvalA', evaluatorName: 'eval.a' } ] );
|
|
36
41
|
|
|
37
42
|
expect( stepImports ).toEqual( [ { localName: 'StepA', stepName: 'step.a' } ] );
|
|
38
43
|
expect( flowImports ).toEqual( [
|
|
@@ -48,20 +53,23 @@ describe( 'collect_target_imports', () => {
|
|
|
48
53
|
it( 'collects CJS requires and removes declarators (steps + default workflow)', () => {
|
|
49
54
|
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-' ) );
|
|
50
55
|
writeFileSync( join( dir, 'steps.js' ), 'export const StepB = step({ name: "step.b" })\n' );
|
|
56
|
+
writeFileSync( join( dir, 'evaluators.js' ), 'export const EvalB = evaluator({ name: "eval.b" })\n' );
|
|
51
57
|
writeFileSync( join( dir, 'workflow.js' ), 'export default workflow({ name: "flow.c" })\n' );
|
|
52
58
|
|
|
53
59
|
const source = [
|
|
54
60
|
'const { StepB } = require("./steps.js");',
|
|
61
|
+
'const { EvalB } = require("./evaluators.js");',
|
|
55
62
|
'const WF = require("./workflow.js");',
|
|
56
63
|
'const obj = {};'
|
|
57
64
|
].join( '\n' );
|
|
58
65
|
|
|
59
66
|
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
60
|
-
const { stepImports, flowImports } = collectTargetImports(
|
|
67
|
+
const { stepImports, evaluatorImports, flowImports } = collectTargetImports(
|
|
61
68
|
ast,
|
|
62
69
|
dir,
|
|
63
|
-
{ stepsNameCache: new Map(), workflowNameCache: new Map() }
|
|
70
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
64
71
|
);
|
|
72
|
+
expect( evaluatorImports ).toEqual( [ { localName: 'EvalB', evaluatorName: 'eval.b' } ] );
|
|
65
73
|
|
|
66
74
|
expect( stepImports ).toEqual( [ { localName: 'StepB', stepName: 'step.b' } ] );
|
|
67
75
|
expect( flowImports ).toEqual( [ { localName: 'WF', workflowName: 'flow.c' } ] );
|
|
@@ -10,6 +10,7 @@ const generate = generatorModule.default ?? generatorModule;
|
|
|
10
10
|
|
|
11
11
|
// Caches to avoid re-reading files during a build
|
|
12
12
|
const stepsNameCache = new Map(); // path -> Map<exported, stepName>
|
|
13
|
+
const evaluatorsNameCache = new Map(); // path -> Map<exported, evaluatorName>
|
|
13
14
|
const workflowNameCache = new Map(); // path -> { default?: name, named: Map<exported, flowName> }
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -25,20 +26,20 @@ const workflowNameCache = new Map(); // path -> { default?: name, named: Map<exp
|
|
|
25
26
|
export default function stepImportRewriterAstLoader( source, inputMap ) {
|
|
26
27
|
this.cacheable?.( true );
|
|
27
28
|
const callback = this.async?.() ?? this.callback;
|
|
28
|
-
const cache = { stepsNameCache, workflowNameCache };
|
|
29
|
+
const cache = { stepsNameCache, evaluatorsNameCache, workflowNameCache };
|
|
29
30
|
|
|
30
31
|
try {
|
|
31
32
|
const filename = this.resourcePath;
|
|
32
33
|
const ast = parse( String( source ), filename );
|
|
33
34
|
const fileDir = dirname( filename );
|
|
34
|
-
const { stepImports, flowImports } = collectTargetImports( ast, fileDir, cache );
|
|
35
|
+
const { stepImports, evaluatorImports, flowImports } = collectTargetImports( ast, fileDir, cache );
|
|
35
36
|
|
|
36
37
|
// No imports
|
|
37
|
-
if (
|
|
38
|
+
if ( [].concat( stepImports, evaluatorImports, flowImports ).length === 0 ) {
|
|
38
39
|
return callback( null, source, inputMap );
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
const rewrote = rewriteFnBodies( ast, stepImports, flowImports );
|
|
42
|
+
const rewrote = rewriteFnBodies( { ast, stepImports, evaluatorImports, flowImports } );
|
|
42
43
|
// No edits performed
|
|
43
44
|
if ( !rewrote ) {
|
|
44
45
|
return callback( null, source, inputMap );
|
|
@@ -15,12 +15,14 @@ const traverse = traverseModule.default ?? traverseModule;
|
|
|
15
15
|
* `this.invokeStep('name', ...)` and `FlowY(...)` with
|
|
16
16
|
* `this.startWorkflow('name', ...)`.
|
|
17
17
|
*
|
|
18
|
-
* @param {
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {Array<{localName:string,
|
|
18
|
+
* @param {object} params
|
|
19
|
+
* @param {import('@babel/types').File} params.ast - Parsed file AST.
|
|
20
|
+
* @param {Array<{localName:string,stepName:string}>} params.stepImports - Step imports.
|
|
21
|
+
* @param {Array<{localName:string,evaluatorName:string}>} params.evaluatorImports - Evaluator imports.
|
|
22
|
+
* @param {Array<{localName:string,workflowName:string}>} params.flowImports - Workflow imports.
|
|
21
23
|
* @returns {boolean} True if the AST was modified; false otherwise.
|
|
22
24
|
*/
|
|
23
|
-
export default function rewriteFnBodies( ast, stepImports, flowImports ) {
|
|
25
|
+
export default function rewriteFnBodies( { ast, stepImports, evaluatorImports, flowImports } ) {
|
|
24
26
|
const state = { rewrote: false };
|
|
25
27
|
traverse( ast, {
|
|
26
28
|
ObjectProperty: path => {
|
|
@@ -56,6 +58,13 @@ export default function rewriteFnBodies( ast, stepImports, flowImports ) {
|
|
|
56
58
|
state.rewrote = true;
|
|
57
59
|
return; // Stop after rewriting as step call
|
|
58
60
|
}
|
|
61
|
+
const evaluator = evaluatorImports.find( x => x.localName === callee.name );
|
|
62
|
+
if ( evaluator ) {
|
|
63
|
+
const args = cPath.node.arguments;
|
|
64
|
+
cPath.replaceWith( createThisMethodCall( 'invokeEvaluator', evaluator.evaluatorName, args ) );
|
|
65
|
+
state.rewrote = true;
|
|
66
|
+
return; // Stop after rewriting as evaluator call
|
|
67
|
+
}
|
|
59
68
|
const flow = flowImports.find( x => x.localName === callee.name );
|
|
60
69
|
if ( flow ) {
|
|
61
70
|
const args = cPath.node.arguments;
|
|
@@ -16,17 +16,31 @@ describe( 'rewrite_fn_bodies', () => {
|
|
|
16
16
|
const stepImports = [ { localName: 'StepA', stepName: 'step.a' } ];
|
|
17
17
|
const flowImports = [ { localName: 'FlowB', workflowName: 'flow.b' } ];
|
|
18
18
|
|
|
19
|
-
const rewrote = rewriteFnBodies( ast, stepImports, flowImports );
|
|
19
|
+
const rewrote = rewriteFnBodies( { ast, stepImports, evaluatorImports: [], flowImports } );
|
|
20
20
|
expect( rewrote ).toBe( true );
|
|
21
21
|
|
|
22
22
|
const code = ast.program.body.map( n => n.type ).length; // smoke: ast mutated
|
|
23
23
|
expect( code ).toBeGreaterThan( 0 );
|
|
24
24
|
} );
|
|
25
25
|
|
|
26
|
+
it( 'rewrites evaluator calls to this.invokeEvaluator', () => {
|
|
27
|
+
const src = [
|
|
28
|
+
'const obj = {',
|
|
29
|
+
' fn: async (x) => {',
|
|
30
|
+
' EvalA(3);',
|
|
31
|
+
' }',
|
|
32
|
+
'}'
|
|
33
|
+
].join( '\n' );
|
|
34
|
+
const ast = parse( src, 'file.js' );
|
|
35
|
+
const evaluatorImports = [ { localName: 'EvalA', evaluatorName: 'eval.a' } ];
|
|
36
|
+
const rewrote = rewriteFnBodies( { ast, stepImports: [], evaluatorImports, flowImports: [] } );
|
|
37
|
+
expect( rewrote ).toBe( true );
|
|
38
|
+
} );
|
|
39
|
+
|
|
26
40
|
it( 'does nothing when no matching calls are present', () => {
|
|
27
41
|
const src = [ 'const obj = { fn: function() { other(); } }' ].join( '\n' );
|
|
28
42
|
const ast = parse( src, 'file.js' );
|
|
29
|
-
const rewrote = rewriteFnBodies( ast, [], [] );
|
|
43
|
+
const rewrote = rewriteFnBodies( { ast, stepImports: [], evaluatorImports: [], flowImports: [] } );
|
|
30
44
|
expect( rewrote ).toBe( false );
|
|
31
45
|
} );
|
|
32
46
|
} );
|