@outputai/core 0.10.0 → 0.10.1-next.09ed166.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/bus.js +58 -20
- package/src/bus.spec.js +135 -77
- package/src/consts.js +2 -1
- package/src/helpers/object.js +48 -35
- package/src/helpers/object.spec.js +123 -75
- package/src/hooks/index.d.ts +48 -45
- package/src/hooks/index.js +49 -44
- package/src/hooks/index.spec.js +74 -102
- package/src/interface/workflow.js +92 -85
- package/src/interface/workflow.spec.js +135 -142
- package/src/sdk/helpers/objects.d.ts +24 -19
- package/src/sdk/runtime/events.d.ts +4 -6
- package/src/sdk/runtime/events.js +2 -15
- package/src/sdk/runtime/events.spec.js +14 -92
- package/src/worker/global_functions.js +2 -2
- package/src/worker/global_functions.spec.js +3 -3
- package/src/worker/index.js +3 -3
- package/src/worker/index.spec.js +6 -6
- package/src/worker/interceptors/activity.js +4 -4
- package/src/worker/interceptors/activity.spec.js +9 -9
- package/src/worker/interceptors/workflow.js +5 -5
- package/src/worker/interceptors/workflow.spec.js +7 -7
- package/src/worker/loader/activities.js +56 -40
- package/src/worker/loader/activities.spec.js +21 -22
- package/src/worker/loader/workflows.spec.js +1 -2
- package/src/worker/log_hooks.js +9 -9
- package/src/worker/log_hooks.spec.js +2 -2
- package/src/worker/sinks.js +8 -8
- package/src/worker/sinks.spec.js +9 -9
- package/src/worker/webpack_loaders/consts.js +6 -0
- package/src/worker/webpack_loaders/tools.js +1 -146
- package/src/worker/webpack_loaders/tools.spec.js +0 -23
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +24 -32
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +82 -279
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +6 -7
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +57 -119
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.js +88 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +165 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +96 -29
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +110 -583
- package/src/worker/webpack_loaders/{npm_workflow_export_resolve.js → workflow_validator/npm_workflow_export_resolve.js} +1 -1
- package/src/worker/webpack_loaders/{npm_workflow_export_resolve.spec.js → workflow_validator/npm_workflow_export_resolve.spec.js} +1 -1
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +0 -193
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +0 -123
|
@@ -5,8 +5,7 @@ vi.mock( '#consts', () => ( {
|
|
|
5
5
|
ACTIVITY_GET_TRACE_DESTINATIONS: '__internal#getTraceDestinations',
|
|
6
6
|
WORKFLOWS_INDEX_FILENAME: '__workflows_entrypoint.js',
|
|
7
7
|
WORKFLOW_CATALOG: 'catalog',
|
|
8
|
-
ACTIVITY_OPTIONS_FILENAME: '__activity_options.js'
|
|
9
|
-
SHARED_STEP_PREFIX: '$shared'
|
|
8
|
+
ACTIVITY_OPTIONS_FILENAME: '__activity_options.js'
|
|
10
9
|
} ) );
|
|
11
10
|
|
|
12
11
|
const sendHttpRequestMock = vi.fn();
|
|
@@ -63,6 +62,7 @@ describe( 'loadActivities', () => {
|
|
|
63
62
|
it( 'loadActivities returns map including system activity and writes options file', async () => {
|
|
64
63
|
const { loadActivities } = await import( './activities.js' );
|
|
65
64
|
|
|
65
|
+
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
66
66
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
67
67
|
yield {
|
|
68
68
|
fn: () => {},
|
|
@@ -70,7 +70,6 @@ describe( 'loadActivities', () => {
|
|
|
70
70
|
path: '/a/steps.js'
|
|
71
71
|
};
|
|
72
72
|
} );
|
|
73
|
-
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
74
73
|
|
|
75
74
|
const workflows = [ { name: 'A', path: '/a/workflow.js' } ];
|
|
76
75
|
const { activities, optionsFile } = await loadActivities( '/root', workflows );
|
|
@@ -89,11 +88,11 @@ describe( 'loadActivities', () => {
|
|
|
89
88
|
|
|
90
89
|
it( 'loadActivities omits activity options when component has no options or no activityOptions', async () => {
|
|
91
90
|
const { loadActivities } = await import( './activities.js' );
|
|
91
|
+
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
92
92
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
93
93
|
yield { fn: () => {}, metadata: { name: 'NoOptions' }, path: '/a/steps.js' };
|
|
94
94
|
yield { fn: () => {}, metadata: { name: 'EmptyOptions', options: {} }, path: '/a/steps2.js' };
|
|
95
95
|
} );
|
|
96
|
-
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
97
96
|
|
|
98
97
|
await loadActivities( '/root', [ { name: 'A', path: '/a/workflow.js' } ] );
|
|
99
98
|
const written = JSON.parse(
|
|
@@ -105,6 +104,7 @@ describe( 'loadActivities', () => {
|
|
|
105
104
|
|
|
106
105
|
it( 'loadActivities throws when two activities in the same workflow share a name', async () => {
|
|
107
106
|
const { loadActivities } = await import( './activities.js' );
|
|
107
|
+
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
108
108
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
109
109
|
yield { fn: () => {}, metadata: { name: 'DuplicateActivity' }, path: '/a/steps.js' };
|
|
110
110
|
yield { fn: () => {}, metadata: { name: 'DuplicateActivity' }, path: '/a/evaluators.js' };
|
|
@@ -118,7 +118,6 @@ Activity names must be unique within a workflow.'
|
|
|
118
118
|
|
|
119
119
|
it( 'loadActivities throws when two shared activities share a name', async () => {
|
|
120
120
|
const { loadActivities } = await import( './activities.js' );
|
|
121
|
-
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
122
121
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
123
122
|
yield { fn: () => {}, metadata: { name: 'DuplicateShared' }, path: '/root/shared/steps/a.js' };
|
|
124
123
|
yield { fn: () => {}, metadata: { name: 'DuplicateShared' }, path: '/root/shared/evaluators/a.js' };
|
|
@@ -133,8 +132,8 @@ Activity names must be unique within a workflow.'
|
|
|
133
132
|
const { loadActivities } = await import( './activities.js' );
|
|
134
133
|
const workflowFiles = [ { path: '/a/steps/foo.js' } ];
|
|
135
134
|
const sharedFiles = [ { path: '/root/shared/steps/baz.js' } ];
|
|
136
|
-
matchFilesMock.mockReturnValueOnce( workflowFiles );
|
|
137
135
|
matchFilesMock.mockReturnValueOnce( sharedFiles );
|
|
136
|
+
matchFilesMock.mockReturnValueOnce( workflowFiles );
|
|
138
137
|
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
139
138
|
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
140
139
|
|
|
@@ -143,12 +142,12 @@ Activity names must be unique within a workflow.'
|
|
|
143
142
|
|
|
144
143
|
expect( matchFilesMock ).toHaveBeenCalledTimes( 2 );
|
|
145
144
|
expect( buildActivityMatcherMock ).toHaveBeenCalledWith( '/a' );
|
|
146
|
-
expect( matchFilesMock ).toHaveBeenNthCalledWith( 1, '/
|
|
147
|
-
expect( matchFilesMock ).toHaveBeenNthCalledWith( 2, '/
|
|
145
|
+
expect( matchFilesMock ).toHaveBeenNthCalledWith( 1, '/root', [ sharedStepsDirMock, sharedEvaluatorsDirMock ] );
|
|
146
|
+
expect( matchFilesMock ).toHaveBeenNthCalledWith( 2, '/a', [ activityMatcherMock ] );
|
|
148
147
|
|
|
149
148
|
expect( importComponentsMock ).toHaveBeenCalledTimes( 2 );
|
|
150
|
-
expect( importComponentsMock ).toHaveBeenNthCalledWith( 1,
|
|
151
|
-
expect( importComponentsMock ).toHaveBeenNthCalledWith( 2,
|
|
149
|
+
expect( importComponentsMock ).toHaveBeenNthCalledWith( 1, sharedFiles );
|
|
150
|
+
expect( importComponentsMock ).toHaveBeenNthCalledWith( 2, workflowFiles );
|
|
152
151
|
} );
|
|
153
152
|
|
|
154
153
|
it( 'loads shared activities from external workflow packages', async () => {
|
|
@@ -157,8 +156,6 @@ Activity names must be unique within a workflow.'
|
|
|
157
156
|
const localWorkflow = { name: 'Local', path: '/root/workflows/local/workflow.js' };
|
|
158
157
|
const externalWorkflow = { name: 'External', path: '/root/node_modules/pkg/workflows/a/workflow.js', external: true };
|
|
159
158
|
findSharedActivitiesFromWorkflowsMock.mockReturnValue( externalSharedFiles );
|
|
160
|
-
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
161
|
-
importComponentsMock.mockImplementationOnce( async function *() {} );
|
|
162
159
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
163
160
|
yield {
|
|
164
161
|
fn: () => {},
|
|
@@ -170,27 +167,29 @@ Activity names must be unique within a workflow.'
|
|
|
170
167
|
const { activities } = await loadActivities( '/root', [ localWorkflow, externalWorkflow ] );
|
|
171
168
|
|
|
172
169
|
expect( findSharedActivitiesFromWorkflowsMock ).toHaveBeenCalledWith( [ externalWorkflow ] );
|
|
173
|
-
expect( importComponentsMock ).toHaveBeenNthCalledWith(
|
|
174
|
-
expect( activities['
|
|
170
|
+
expect( importComponentsMock ).toHaveBeenNthCalledWith( 1, externalSharedFiles );
|
|
171
|
+
expect( activities['Local#ExternalShared'] ).toBeTypeOf( 'function' );
|
|
172
|
+
expect( activities['External#ExternalShared'] ).toBeTypeOf( 'function' );
|
|
175
173
|
const written = JSON.parse(
|
|
176
174
|
writeFileInTempDirMock.mock.calls[0][0].replace( /^export default\s*/, '' ).replace( /;\s*$/, '' )
|
|
177
175
|
);
|
|
178
|
-
expect( written['
|
|
176
|
+
expect( written['Local#ExternalShared'] ).toEqual( { retry: { maximumAttempts: 2 } } );
|
|
177
|
+
expect( written['External#ExternalShared'] ).toEqual( { retry: { maximumAttempts: 2 } } );
|
|
179
178
|
} );
|
|
180
179
|
|
|
181
180
|
it( 'loadActivities includes nested workflow steps and shared evaluators', async () => {
|
|
182
181
|
const { loadActivities } = await import( './activities.js' );
|
|
183
182
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
184
|
-
yield { fn: () => {}, metadata: { name: '
|
|
183
|
+
yield { fn: () => {}, metadata: { name: 'SharedEval' }, path: '/root/shared/evaluators/bar.js' };
|
|
185
184
|
} );
|
|
186
185
|
importComponentsMock.mockImplementationOnce( async function *() {
|
|
187
|
-
yield { fn: () => {}, metadata: { name: '
|
|
186
|
+
yield { fn: () => {}, metadata: { name: 'ActNested' }, path: '/a/steps/foo.js' };
|
|
188
187
|
} );
|
|
189
188
|
|
|
190
189
|
const workflows = [ { name: 'A', path: '/a/workflow.js' } ];
|
|
191
190
|
const { activities } = await loadActivities( '/root', workflows );
|
|
192
191
|
expect( activities['A#ActNested'] ).toBeTypeOf( 'function' );
|
|
193
|
-
expect( activities['
|
|
192
|
+
expect( activities['A#SharedEval'] ).toBeTypeOf( 'function' );
|
|
194
193
|
} );
|
|
195
194
|
|
|
196
195
|
it( 'collects shared nested steps and evaluators across multiple subfolders', async () => {
|
|
@@ -205,9 +204,9 @@ Activity names must be unique within a workflow.'
|
|
|
205
204
|
|
|
206
205
|
const workflows = [ { name: 'A', path: '/a/workflow.js' } ];
|
|
207
206
|
const { activities } = await loadActivities( '/root', workflows );
|
|
208
|
-
expect( activities['
|
|
209
|
-
expect( activities['
|
|
210
|
-
expect( activities['
|
|
211
|
-
expect( activities['
|
|
207
|
+
expect( activities['A#SharedStepPrimary'] ).toBeTypeOf( 'function' );
|
|
208
|
+
expect( activities['A#SharedStepSecondary'] ).toBeTypeOf( 'function' );
|
|
209
|
+
expect( activities['A#SharedEvalPrimary'] ).toBeTypeOf( 'function' );
|
|
210
|
+
expect( activities['A#SharedEvalSecondary'] ).toBeTypeOf( 'function' );
|
|
212
211
|
} );
|
|
213
212
|
} );
|
|
@@ -5,8 +5,7 @@ vi.mock( '#consts', () => ( {
|
|
|
5
5
|
ACTIVITY_GET_TRACE_DESTINATIONS: '__internal#getTraceDestinations',
|
|
6
6
|
WORKFLOWS_INDEX_FILENAME: '__workflows_entrypoint.js',
|
|
7
7
|
WORKFLOW_CATALOG: 'catalog',
|
|
8
|
-
ACTIVITY_OPTIONS_FILENAME: '__activity_options.js'
|
|
9
|
-
SHARED_STEP_PREFIX: '$shared'
|
|
8
|
+
ACTIVITY_OPTIONS_FILENAME: '__activity_options.js'
|
|
10
9
|
} ) );
|
|
11
10
|
|
|
12
11
|
const { importComponentsMock, findWorkflowsInNodeModulesMock, matchFilesMock, writeFileInTempDirMock } = vi.hoisted( () => ( {
|
package/src/worker/log_hooks.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mainEventBus } from '#bus';
|
|
2
2
|
import { createChildLogger } from '#logger';
|
|
3
3
|
import { ACTIVITY_GET_TRACE_DESTINATIONS, BusEventType, LifecycleEvent, WORKFLOW_CATALOG } from '#consts';
|
|
4
4
|
|
|
@@ -25,21 +25,21 @@ const serializedActivityFields = activityInfo => ( {
|
|
|
25
25
|
|
|
26
26
|
const shouldLogActivity = activityInfo => activityInfo.activityType !== ACTIVITY_GET_TRACE_DESTINATIONS;
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
mainEventBus.on( BusEventType.ACTIVITY_START, ( { activityInfo, outputActivityKind } ) =>
|
|
29
29
|
shouldLogActivity( activityInfo ) && activityLog.info( `Started ${activityInfo.activityType} ${outputActivityKind}`, {
|
|
30
30
|
event: LifecycleEvent.START,
|
|
31
31
|
...serializedActivityFields( activityInfo )
|
|
32
32
|
} )
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
mainEventBus.on( BusEventType.ACTIVITY_END, ( { activityInfo, outputActivityKind } ) =>
|
|
36
36
|
shouldLogActivity( activityInfo ) && activityLog.info( `Ended ${activityInfo.activityType} ${outputActivityKind}`, {
|
|
37
37
|
event: LifecycleEvent.END,
|
|
38
38
|
...serializedActivityFields( activityInfo )
|
|
39
39
|
} )
|
|
40
40
|
);
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
mainEventBus.on( BusEventType.ACTIVITY_ERROR, ( { activityInfo, outputActivityKind, error } ) =>
|
|
43
43
|
shouldLogActivity( activityInfo ) && activityLog.error( `Error ${activityInfo.activityType} ${outputActivityKind}: ${error.constructor.name}`, {
|
|
44
44
|
event: LifecycleEvent.ERROR,
|
|
45
45
|
...serializedActivityFields( activityInfo ),
|
|
@@ -47,7 +47,7 @@ messageBus.on( BusEventType.ACTIVITY_ERROR, ( { activityInfo, outputActivityKind
|
|
|
47
47
|
} )
|
|
48
48
|
);
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
mainEventBus.on( BusEventType.ACTIVITY_LOG, ( { level, message, metadata, activityInfo } ) =>
|
|
51
51
|
activityLog[level]( message, {
|
|
52
52
|
...metadata ?? {},
|
|
53
53
|
...serializedActivityFields( activityInfo )
|
|
@@ -68,21 +68,21 @@ const serializeWorkflowFields = workflowDetails => ( {
|
|
|
68
68
|
|
|
69
69
|
const shouldLogWorkflow = workflowDetails => workflowDetails.workflowType !== WORKFLOW_CATALOG;
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
mainEventBus.on( BusEventType.WORKFLOW_START, ( { workflowDetails } ) =>
|
|
72
72
|
shouldLogWorkflow( workflowDetails ) && workflowLog.info( `Started ${workflowDetails.workflowType} workflow`, {
|
|
73
73
|
event: LifecycleEvent.START,
|
|
74
74
|
...serializeWorkflowFields( workflowDetails )
|
|
75
75
|
} )
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
mainEventBus.on( BusEventType.WORKFLOW_END, ( { workflowDetails } ) =>
|
|
79
79
|
shouldLogWorkflow( workflowDetails ) && workflowLog.info( `Ended ${workflowDetails.workflowType} workflow`, {
|
|
80
80
|
event: LifecycleEvent.END,
|
|
81
81
|
...serializeWorkflowFields( workflowDetails )
|
|
82
82
|
} )
|
|
83
83
|
);
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
mainEventBus.on( BusEventType.WORKFLOW_ERROR, ( { workflowDetails, error } ) =>
|
|
86
86
|
shouldLogWorkflow( workflowDetails ) && workflowLog.error( `Error ${workflowDetails.workflowType} workflow: ${error.constructor.name}`, {
|
|
87
87
|
event: LifecycleEvent.ERROR,
|
|
88
88
|
...serializeWorkflowFields( workflowDetails ),
|
|
@@ -90,7 +90,7 @@ messageBus.on( BusEventType.WORKFLOW_ERROR, ( { workflowDetails, error } ) =>
|
|
|
90
90
|
} )
|
|
91
91
|
);
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
mainEventBus.on( BusEventType.WORKFLOW_LOG, ( { level, message, metadata, workflowDetails } ) =>
|
|
94
94
|
workflowLog[level]( message, {
|
|
95
95
|
...metadata ?? {},
|
|
96
96
|
...serializeWorkflowFields( workflowDetails )
|
|
@@ -22,14 +22,14 @@ const createChildLoggerMock = vi.hoisted( () =>
|
|
|
22
22
|
);
|
|
23
23
|
|
|
24
24
|
const onHandlers = vi.hoisted( () => ( {} ) );
|
|
25
|
-
const
|
|
25
|
+
const mainEventBusMock = vi.hoisted( () => ( {
|
|
26
26
|
on: vi.fn( ( eventType, handler ) => {
|
|
27
27
|
onHandlers[eventType] = handler;
|
|
28
28
|
} )
|
|
29
29
|
} ) );
|
|
30
30
|
|
|
31
31
|
vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
|
|
32
|
-
vi.mock( '#bus', () => ( {
|
|
32
|
+
vi.mock( '#bus', () => ( { mainEventBus: mainEventBusMock } ) );
|
|
33
33
|
|
|
34
34
|
import './log_hooks.js';
|
|
35
35
|
|
package/src/worker/sinks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BusEventType, ComponentType } from '#consts';
|
|
2
2
|
import * as Tracing from '#tracing';
|
|
3
|
-
import {
|
|
3
|
+
import { mainEventBus } from '#bus';
|
|
4
4
|
import { createWorkflowDetails } from '#helpers/temporal_context';
|
|
5
5
|
|
|
6
6
|
// This sink allow for sandbox Temporal environment to send trace logs back to the main thread.
|
|
@@ -12,15 +12,15 @@ export const sinks = {
|
|
|
12
12
|
workflow: {
|
|
13
13
|
log: {
|
|
14
14
|
fn: ( workflowInfo, { level, message, metadata } ) => {
|
|
15
|
-
|
|
15
|
+
mainEventBus.emit( BusEventType.WORKFLOW_LOG, { level, message, metadata, workflowDetails: createWorkflowDetails( workflowInfo ) } );
|
|
16
16
|
},
|
|
17
17
|
callDuringReplay: false
|
|
18
18
|
},
|
|
19
19
|
start: {
|
|
20
20
|
fn: ( workflowInfo, input ) => {
|
|
21
21
|
const { runId, workflowType, memo: { traceInfo }, parent } = workflowInfo;
|
|
22
|
-
|
|
23
|
-
if ( traceInfo ) {
|
|
22
|
+
mainEventBus.emit( BusEventType.WORKFLOW_START, { workflowDetails: createWorkflowDetails( workflowInfo ) } );
|
|
23
|
+
if ( traceInfo ) {
|
|
24
24
|
Tracing.addEventStart( {
|
|
25
25
|
id: runId,
|
|
26
26
|
kind: ComponentType.WORKFLOW,
|
|
@@ -37,8 +37,8 @@ export const sinks = {
|
|
|
37
37
|
end: {
|
|
38
38
|
fn: ( workflowInfo, output ) => {
|
|
39
39
|
const { runId, memo: { traceInfo } } = workflowInfo;
|
|
40
|
-
|
|
41
|
-
if ( traceInfo ) {
|
|
40
|
+
mainEventBus.emit( BusEventType.WORKFLOW_END, { workflowDetails: createWorkflowDetails( workflowInfo ) } );
|
|
41
|
+
if ( traceInfo ) {
|
|
42
42
|
Tracing.addEventEnd( { id: runId, details: output, traceInfo } );
|
|
43
43
|
}
|
|
44
44
|
},
|
|
@@ -48,8 +48,8 @@ export const sinks = {
|
|
|
48
48
|
error: {
|
|
49
49
|
fn: ( workflowInfo, error ) => {
|
|
50
50
|
const { runId, memo: { traceInfo } } = workflowInfo;
|
|
51
|
-
|
|
52
|
-
if ( traceInfo ) {
|
|
51
|
+
mainEventBus.emit( BusEventType.WORKFLOW_ERROR, { workflowDetails: createWorkflowDetails( workflowInfo ), error } );
|
|
52
|
+
if ( traceInfo ) {
|
|
53
53
|
Tracing.addEventError( { id: runId, details: error, traceInfo } );
|
|
54
54
|
}
|
|
55
55
|
},
|
package/src/worker/sinks.spec.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { BusEventType, ComponentType } from '#consts';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const mainEventBusMock = vi.hoisted( () => ( {
|
|
5
5
|
emit: vi.fn()
|
|
6
6
|
} ) );
|
|
7
7
|
|
|
@@ -15,7 +15,7 @@ const createWorkflowDetailsMock = vi.hoisted( () => vi.fn( workflowInfo => ( {
|
|
|
15
15
|
runId: workflowInfo.runId
|
|
16
16
|
} ) ) );
|
|
17
17
|
|
|
18
|
-
vi.mock( '#bus', () => ( {
|
|
18
|
+
vi.mock( '#bus', () => ( { mainEventBus: mainEventBusMock } ) );
|
|
19
19
|
vi.mock( '#tracing', () => ( {
|
|
20
20
|
addEventStart: addEventStartMock,
|
|
21
21
|
addEventEnd: addEventEndMock,
|
|
@@ -69,7 +69,7 @@ describe( 'worker/sinks', () => {
|
|
|
69
69
|
} );
|
|
70
70
|
|
|
71
71
|
expect( createWorkflowDetailsMock ).toHaveBeenCalledWith( workflowInfo );
|
|
72
|
-
expect(
|
|
72
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_LOG, {
|
|
73
73
|
level: 'info',
|
|
74
74
|
message: 'workflow detail',
|
|
75
75
|
metadata,
|
|
@@ -83,7 +83,7 @@ describe( 'worker/sinks', () => {
|
|
|
83
83
|
|
|
84
84
|
sinks.workflow.start.fn( workflowInfo, input );
|
|
85
85
|
|
|
86
|
-
expect(
|
|
86
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_START, { workflowDetails } );
|
|
87
87
|
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
88
88
|
id: 'run-1',
|
|
89
89
|
kind: ComponentType.WORKFLOW,
|
|
@@ -99,7 +99,7 @@ describe( 'worker/sinks', () => {
|
|
|
99
99
|
|
|
100
100
|
sinks.workflow.start.fn( { ...workflowInfo, memo: {} }, { value: 'input' } );
|
|
101
101
|
|
|
102
|
-
expect(
|
|
102
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_START, { workflowDetails } );
|
|
103
103
|
expect( addEventStartMock ).not.toHaveBeenCalled();
|
|
104
104
|
} );
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ describe( 'worker/sinks', () => {
|
|
|
109
109
|
|
|
110
110
|
sinks.workflow.end.fn( workflowInfo, output );
|
|
111
111
|
|
|
112
|
-
expect(
|
|
112
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_END, { workflowDetails } );
|
|
113
113
|
expect( addEventEndMock ).toHaveBeenCalledWith( {
|
|
114
114
|
id: 'run-1',
|
|
115
115
|
details: output,
|
|
@@ -122,7 +122,7 @@ describe( 'worker/sinks', () => {
|
|
|
122
122
|
|
|
123
123
|
sinks.workflow.end.fn( { ...workflowInfo, memo: {} }, { value: 'output' } );
|
|
124
124
|
|
|
125
|
-
expect(
|
|
125
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_END, { workflowDetails } );
|
|
126
126
|
expect( addEventEndMock ).not.toHaveBeenCalled();
|
|
127
127
|
} );
|
|
128
128
|
|
|
@@ -132,7 +132,7 @@ describe( 'worker/sinks', () => {
|
|
|
132
132
|
|
|
133
133
|
sinks.workflow.error.fn( workflowInfo, error );
|
|
134
134
|
|
|
135
|
-
expect(
|
|
135
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, { workflowDetails, error } );
|
|
136
136
|
expect( addEventErrorMock ).toHaveBeenCalledWith( {
|
|
137
137
|
id: 'run-1',
|
|
138
138
|
details: error,
|
|
@@ -146,7 +146,7 @@ describe( 'worker/sinks', () => {
|
|
|
146
146
|
|
|
147
147
|
sinks.workflow.error.fn( { ...workflowInfo, memo: {} }, error );
|
|
148
148
|
|
|
149
|
-
expect(
|
|
149
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, { workflowDetails, error } );
|
|
150
150
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
151
151
|
} );
|
|
152
152
|
|
|
@@ -2,27 +2,14 @@ import parser from '@babel/parser';
|
|
|
2
2
|
import { resolve as resolvePath } from 'node:path';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
import {
|
|
5
|
-
blockStatement,
|
|
6
|
-
callExpression,
|
|
7
|
-
functionExpression,
|
|
8
|
-
identifier,
|
|
9
|
-
isArrowFunctionExpression,
|
|
10
5
|
isAssignmentPattern,
|
|
11
|
-
isBlockStatement,
|
|
12
6
|
isCallExpression,
|
|
13
7
|
isExportNamedDeclaration,
|
|
14
|
-
isFunctionExpression,
|
|
15
8
|
isIdentifier,
|
|
16
|
-
isVariableDeclarator,
|
|
17
9
|
isStringLiteral,
|
|
18
10
|
isVariableDeclaration,
|
|
19
11
|
isObjectExpression,
|
|
20
|
-
|
|
21
|
-
returnStatement,
|
|
22
|
-
stringLiteral,
|
|
23
|
-
thisExpression,
|
|
24
|
-
isExportDefaultDeclaration,
|
|
25
|
-
isFunctionDeclaration
|
|
12
|
+
isExportDefaultDeclaration
|
|
26
13
|
} from '@babel/types';
|
|
27
14
|
import { ComponentFile, NodeType } from './consts.js';
|
|
28
15
|
|
|
@@ -100,17 +87,6 @@ export const getLocalNameFromDestructuredProperty = prop => {
|
|
|
100
87
|
return null;
|
|
101
88
|
};
|
|
102
89
|
|
|
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
90
|
/**
|
|
115
91
|
* Check if a module specifier or request string points to steps.js or is in a steps folder.
|
|
116
92
|
* Matches: steps.js, /steps.js, /steps/*.js
|
|
@@ -231,38 +207,6 @@ export const getFileKind = path => {
|
|
|
231
207
|
return null;
|
|
232
208
|
};
|
|
233
209
|
|
|
234
|
-
/**
|
|
235
|
-
* Create a `this.method(literalName, ...args)` CallExpression.
|
|
236
|
-
* @param {string} method - Method name on `this`.
|
|
237
|
-
* @param {string} literalName - First string literal argument.
|
|
238
|
-
* @param {import('@babel/types').Expression[]} args - Remaining call arguments.
|
|
239
|
-
* @returns {import('@babel/types').CallExpression} Call expression node.
|
|
240
|
-
*/
|
|
241
|
-
export const createThisMethodCall = ( method, literalName, args ) =>
|
|
242
|
-
callExpression( memberExpression( thisExpression(), identifier( method ) ), [ stringLiteral( literalName ), ...args ] );
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Build a CallExpression that binds `this` at the call site:
|
|
246
|
-
* fn(arg1, arg2) -> fn.call(this, arg1, arg2)
|
|
247
|
-
*
|
|
248
|
-
* When to use:
|
|
249
|
-
* - Inside workflow `fn` rewriting, local call-chain functions must receive the dynamic `this`
|
|
250
|
-
* so that emitted `this.invokeStep(...)` and similar calls inside them operate correctly.
|
|
251
|
-
*
|
|
252
|
-
* Example:
|
|
253
|
-
* // Input AST intent:
|
|
254
|
-
* foo(a, b);
|
|
255
|
-
*
|
|
256
|
-
* // Rewritten AST:
|
|
257
|
-
* foo.call(this, a, b);
|
|
258
|
-
*
|
|
259
|
-
* @param {string} calleeName - Identifier name of the function being called (e.g., 'foo').
|
|
260
|
-
* @param {import('@babel/types').Expression[]} args - Original call arguments.
|
|
261
|
-
* @returns {import('@babel/types').CallExpression} CallExpression node representing `callee.call(this, ...args)`.
|
|
262
|
-
*/
|
|
263
|
-
export const bindThisAtCallSite = ( calleeName, args ) =>
|
|
264
|
-
callExpression( memberExpression( identifier( calleeName ), identifier( 'call' ) ), [ thisExpression(), ...args ] );
|
|
265
|
-
|
|
266
210
|
/**
|
|
267
211
|
* Resolve an options object's name property to a string.
|
|
268
212
|
* Accepts literal strings or top-level const string identifiers.
|
|
@@ -466,92 +410,3 @@ export const buildWorkflowNameMap = ( path, cache ) => {
|
|
|
466
410
|
return result;
|
|
467
411
|
};
|
|
468
412
|
|
|
469
|
-
/**
|
|
470
|
-
* Determine whether a node represents a function body usable as a workflow `fn`.
|
|
471
|
-
*
|
|
472
|
-
* Why this matters:
|
|
473
|
-
* - Workflow `fn` needs a dynamic `this` so the rewriter can emit calls like `this.invokeStep(...)`.
|
|
474
|
-
* - Arrow functions do not have their own `this`; they capture `this` lexically, which breaks the runtime contract.
|
|
475
|
-
*
|
|
476
|
-
* Accepts:
|
|
477
|
-
* - FunctionExpression (possibly async/generator), e.g.:
|
|
478
|
-
* const obj = {
|
|
479
|
-
* fn: async function (input) {
|
|
480
|
-
* return input;
|
|
481
|
-
* }
|
|
482
|
-
* };
|
|
483
|
-
*
|
|
484
|
-
* Rejects:
|
|
485
|
-
* - ArrowFunctionExpression, e.g.:
|
|
486
|
-
* const obj = {
|
|
487
|
-
* fn: async (input) => input
|
|
488
|
-
* };
|
|
489
|
-
*
|
|
490
|
-
* - Any other non-function expression.
|
|
491
|
-
*
|
|
492
|
-
* Notes:
|
|
493
|
-
* - The rewriter will proactively convert arrow `fn` to a FunctionExpression before further processing.
|
|
494
|
-
*
|
|
495
|
-
* @param {import('@babel/types').Expression} v - Candidate node for `fn` value.
|
|
496
|
-
* @returns {boolean} True if `v` is a FunctionExpression and not an arrow function.
|
|
497
|
-
*/
|
|
498
|
-
export const isFunction = v => isFunctionExpression( v ) && !isArrowFunctionExpression( v );
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Determine whether a variable declarator represents a function-like value.
|
|
502
|
-
*
|
|
503
|
-
* Use case:
|
|
504
|
-
* - When `fn` calls a locally-declared function (directly or transitively), we need to:
|
|
505
|
-
* - propagate `this` to that function call (`callee.call(this, ...)`)
|
|
506
|
-
* - traverse into that function's body to rewrite imported step/workflow/evaluator calls.
|
|
507
|
-
*
|
|
508
|
-
* Matches patterns like:
|
|
509
|
-
* - Function expression:
|
|
510
|
-
* const foo = function (x) { return x + 1; };
|
|
511
|
-
*
|
|
512
|
-
* - Async/generator function expression:
|
|
513
|
-
* const foo = async function (x) { return await work(x); };
|
|
514
|
-
*
|
|
515
|
-
* - Arrow function (will be normalized to FunctionExpression by the rewriter):
|
|
516
|
-
* const foo = (x) => x + 1;
|
|
517
|
-
* const foo = async (x) => await work(x);
|
|
518
|
-
*
|
|
519
|
-
* Does not match:
|
|
520
|
-
* - Non-function initializers:
|
|
521
|
-
* const foo = 42;
|
|
522
|
-
* const foo = someIdentifier;
|
|
523
|
-
*
|
|
524
|
-
* @param {import('@babel/types').Node} v - AST node (typically a VariableDeclarator).
|
|
525
|
-
* @returns {boolean} True if the declarator's initializer is a function (arrow or function expression).
|
|
526
|
-
*/
|
|
527
|
-
export const isVarFunction = v =>
|
|
528
|
-
isVariableDeclarator( v ) && ( isFunctionExpression( v.init ) || isArrowFunctionExpression( v.init ) );
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Determine whether a binding node corresponds to a function-like declaration usable
|
|
532
|
-
* as a call-chain function target during workflow rewriting.
|
|
533
|
-
*
|
|
534
|
-
* Matches:
|
|
535
|
-
* - FunctionDeclaration:
|
|
536
|
-
* function foo(x) { return x + 1; }
|
|
537
|
-
*
|
|
538
|
-
* - VariableDeclarator initialized with a function or arrow (normalized later):
|
|
539
|
-
* const foo = function (x) { return x + 1; };
|
|
540
|
-
* const foo = (x) => x + 1;
|
|
541
|
-
*
|
|
542
|
-
* Non-matches:
|
|
543
|
-
* - Any binding that is not a function declaration nor a variable declarator with a function initializer.
|
|
544
|
-
*
|
|
545
|
-
* Why this matters:
|
|
546
|
-
* - The rewriter traverses call chains from the workflow `fn`. It must recognize which local
|
|
547
|
-
* callees are valid function bodies to rewrite and into which it can propagate `this`.
|
|
548
|
-
*
|
|
549
|
-
* @param {import('@babel/types').Node} node - Binding path node (FunctionDeclaration or VariableDeclarator).
|
|
550
|
-
* @returns {boolean} True if the node represents a function-like binding.
|
|
551
|
-
*/
|
|
552
|
-
export const isFunctionLikeBinding = node =>
|
|
553
|
-
isFunctionDeclaration( node ) ||
|
|
554
|
-
(
|
|
555
|
-
isVariableDeclarator( node ) &&
|
|
556
|
-
( isFunctionExpression( node.init ) || isArrowFunctionExpression( node.init ) )
|
|
557
|
-
);
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
extractTopLevelStringConsts,
|
|
10
10
|
getObjectKeyName,
|
|
11
11
|
getLocalNameFromDestructuredProperty,
|
|
12
|
-
toFunctionExpression,
|
|
13
12
|
isStepsPath,
|
|
14
13
|
isSharedStepsPath,
|
|
15
14
|
isAnyStepsPath,
|
|
@@ -17,7 +16,6 @@ import {
|
|
|
17
16
|
isSharedEvaluatorsPath,
|
|
18
17
|
isWorkflowPath,
|
|
19
18
|
isAbsoluteWorkflowJsResource,
|
|
20
|
-
createThisMethodCall,
|
|
21
19
|
resolveNameFromArg,
|
|
22
20
|
resolveNameFromOptions,
|
|
23
21
|
buildStepsNameMap,
|
|
@@ -130,17 +128,6 @@ describe( 'workflow_rewriter tools', () => {
|
|
|
130
128
|
rmSync( dir, { recursive: true, force: true } );
|
|
131
129
|
} );
|
|
132
130
|
|
|
133
|
-
it( 'toFunctionExpression: converts arrow, wraps expression bodies', () => {
|
|
134
|
-
const arrowExprBody = t.arrowFunctionExpression( [ t.identifier( 'x' ) ], t.identifier( 'x' ) );
|
|
135
|
-
const arrowBlockBody = t.arrowFunctionExpression( [], t.blockStatement( [ t.returnStatement( t.numericLiteral( 1 ) ) ] ) );
|
|
136
|
-
const fn1 = toFunctionExpression( arrowExprBody );
|
|
137
|
-
const fn2 = toFunctionExpression( arrowBlockBody );
|
|
138
|
-
expect( t.isFunctionExpression( fn1 ) ).toBe( true );
|
|
139
|
-
expect( t.isBlockStatement( fn1.body ) ).toBe( true );
|
|
140
|
-
expect( t.isReturnStatement( fn1.body.body[0] ) ).toBe( true );
|
|
141
|
-
expect( t.isFunctionExpression( fn2 ) ).toBe( true );
|
|
142
|
-
} );
|
|
143
|
-
|
|
144
131
|
it( 'isStepsPath: matches LOCAL steps.js (no path traversal)', () => {
|
|
145
132
|
// Local steps (without ../ or /shared/)
|
|
146
133
|
expect( isStepsPath( 'steps.js' ) ).toBe( true );
|
|
@@ -226,16 +213,6 @@ describe( 'workflow_rewriter tools', () => {
|
|
|
226
213
|
expect( isSharedEvaluatorsPath( 'steps.js' ) ).toBe( false );
|
|
227
214
|
} );
|
|
228
215
|
|
|
229
|
-
it( 'createThisMethodCall: builds this.method(\'name\', ...args) call', () => {
|
|
230
|
-
const call = createThisMethodCall( 'invoke', 'n', [ t.numericLiteral( 1 ), t.identifier( 'x' ) ] );
|
|
231
|
-
expect( t.isCallExpression( call ) ).toBe( true );
|
|
232
|
-
expect( t.isMemberExpression( call.callee ) ).toBe( true );
|
|
233
|
-
expect( t.isThisExpression( call.callee.object ) ).toBe( true );
|
|
234
|
-
expect( t.isIdentifier( call.callee.property, { name: 'invoke' } ) ).toBe( true );
|
|
235
|
-
expect( t.isStringLiteral( call.arguments[0], { value: 'n' } ) ).toBe( true );
|
|
236
|
-
expect( call.arguments.length ).toBe( 3 );
|
|
237
|
-
} );
|
|
238
|
-
|
|
239
216
|
it( 'buildSharedStepsNameMap: reads names from shared steps module and caches result', () => {
|
|
240
217
|
const dir = mkdtempSync( join( tmpdir(), 'tools-shared-steps-' ) );
|
|
241
218
|
mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
|