@outputai/core 0.10.1-dev.b7b2fbe.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/helpers/object.js +5 -1
- package/src/helpers/object.spec.js +5 -0
- 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/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/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/hooks/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BusEventType
|
|
1
|
+
import { mainEventBus, stepEventBus } from '#bus';
|
|
2
|
+
import { BusEventType } from '#consts';
|
|
3
3
|
import { createChildLogger } from '#logger';
|
|
4
4
|
|
|
5
5
|
const log = createChildLogger( 'Hooks' );
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Invokes
|
|
8
|
+
* Invokes a function within a try catch and log the error
|
|
9
9
|
*
|
|
10
10
|
* @param {Function} fn
|
|
11
11
|
* @param {any} args - Args to invoke the function with
|
|
12
12
|
* @param {string} hookName - hookName to identify this hook function in the logs
|
|
13
13
|
*/
|
|
14
|
-
const
|
|
14
|
+
const callHookCb = async ( fn, args, hookName ) => {
|
|
15
15
|
try {
|
|
16
16
|
await fn( args );
|
|
17
17
|
} catch ( error ) {
|
|
@@ -19,50 +19,55 @@ const safeInvoke = async ( fn, args, hookName ) => {
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
// General Life-cycle
|
|
23
|
+
// --------------------------------------
|
|
24
|
+
const onError = cb => {
|
|
25
|
+
mainEventBus.on( BusEventType.ACTIVITY_ERROR, async payload =>
|
|
26
|
+
callHookCb( cb, { source: 'activity', ...payload }, 'onError' ) );
|
|
27
|
+
mainEventBus.on( BusEventType.WORKFLOW_ERROR, async payload =>
|
|
28
|
+
callHookCb( cb, { source: 'workflow', ...payload }, 'onError' ) );
|
|
29
|
+
mainEventBus.on( BusEventType.RUNTIME_ERROR, async payload =>
|
|
30
|
+
callHookCb( cb, { source: 'runtime', ...payload }, 'onError' ) );
|
|
30
31
|
};
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
export const onBeforeWorkerStart = handler => messageBus.on( BusEventType.WORKER_BEFORE_START, () =>
|
|
34
|
-
safeInvoke( handler, undefined, 'onBeforeWorkerStart' ) );
|
|
33
|
+
const onBeforeWorkerStart = cb => mainEventBus.on( BusEventType.WORKER_BEFORE_START, () => callHookCb( cb, undefined, 'onBeforeWorkerStart' ) );
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// Workflow Life-cycle
|
|
36
|
+
// --------------------------------------
|
|
37
|
+
const onWorkflowStart = cb => mainEventBus.on( BusEventType.WORKFLOW_START, payload => callHookCb( cb, payload, 'onWorkflowStart' ) );
|
|
38
|
+
const onWorkflowEnd = cb => mainEventBus.on( BusEventType.WORKFLOW_END, payload => callHookCb( cb, payload, 'onWorkflowEnd' ) );
|
|
39
|
+
const onWorkflowError = cb => mainEventBus.on( BusEventType.WORKFLOW_ERROR, payload => callHookCb( cb, payload, 'onWorkflowError' ) );
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
// Activity Life-cycle
|
|
42
|
+
// --------------------------------------
|
|
43
|
+
const onActivityStart = cb => mainEventBus.on( BusEventType.ACTIVITY_START, fields => callHookCb( cb, fields, 'onActivityStart' ) );
|
|
44
|
+
const onActivityEnd = cb => mainEventBus.on( BusEventType.ACTIVITY_END, fields => callHookCb( cb, fields, 'onActivityEnd' ) );
|
|
45
|
+
const onActivityError = cb => mainEventBus.on( BusEventType.ACTIVITY_ERROR, fields => callHookCb( cb, fields, 'onActivityError' ) );
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/** Internal activities do not trigger hooks */
|
|
52
|
-
const shouldEmitActivityEvent = outputActivityKind => outputActivityKind !== ComponentType.INTERNAL_STEP;
|
|
53
|
-
|
|
54
|
-
/** Listen to workflow start events, excludes catalog workflow */
|
|
55
|
-
export const onActivityStart = handler => messageBus.on( BusEventType.ACTIVITY_START, ( { outputActivityKind, ...eventFields } ) =>
|
|
56
|
-
shouldEmitActivityEvent( outputActivityKind ) ? safeInvoke( handler, { outputActivityKind, ...eventFields }, 'onActivityStart' ) : null );
|
|
57
|
-
|
|
58
|
-
/** Listen to workflow end events, excludes catalog workflow */
|
|
59
|
-
export const onActivityEnd = handler => messageBus.on( BusEventType.ACTIVITY_END, ( { outputActivityKind, ...eventFields } ) =>
|
|
60
|
-
shouldEmitActivityEvent( outputActivityKind ) ? safeInvoke( handler, { outputActivityKind, ...eventFields }, 'onActivityEnd' ) : null );
|
|
47
|
+
// Generic Events
|
|
48
|
+
// --------------------------------------
|
|
49
|
+
/** Listen to both sdk and custom events */
|
|
50
|
+
const on = ( eventName, cb ) => {
|
|
51
|
+
stepEventBus.on( `sdk:${eventName}`, payload => callHookCb( cb, payload, eventName ) );
|
|
52
|
+
stepEventBus.on( `usr:${eventName}`, payload => callHookCb( cb, payload, eventName ) );
|
|
53
|
+
};
|
|
61
54
|
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Emits a custom event
|
|
57
|
+
* @param {string} eventName
|
|
58
|
+
* @param {any} payload
|
|
59
|
+
*/
|
|
60
|
+
const emit = ( eventName, payload ) => stepEventBus.emit( `usr:${eventName}`, payload );
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
export {
|
|
63
|
+
emit,
|
|
64
|
+
on,
|
|
65
|
+
onActivityEnd,
|
|
66
|
+
onActivityError,
|
|
67
|
+
onActivityStart,
|
|
68
|
+
onBeforeWorkerStart,
|
|
69
|
+
onError,
|
|
70
|
+
onWorkflowEnd,
|
|
71
|
+
onWorkflowError,
|
|
72
|
+
onWorkflowStart
|
|
73
|
+
};
|
package/src/hooks/index.spec.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { BusEventType
|
|
2
|
+
import { BusEventType } from '#consts';
|
|
3
3
|
|
|
4
4
|
const logErrorMock = vi.hoisted( () => vi.fn() );
|
|
5
5
|
const createChildLoggerMock = vi.hoisted( () =>
|
|
6
6
|
vi.fn( () => ( { error: logErrorMock } ) )
|
|
7
7
|
);
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const mainOnHandlers = vi.hoisted( () => ( {} ) );
|
|
10
|
+
const stepOnHandlers = vi.hoisted( () => ( {} ) );
|
|
11
|
+
const mainEventBusMock = vi.hoisted( () => ( {
|
|
11
12
|
on: vi.fn( ( eventType, handler ) => {
|
|
12
|
-
|
|
13
|
+
mainOnHandlers[eventType] = handler;
|
|
14
|
+
} )
|
|
15
|
+
} ) );
|
|
16
|
+
const stepEventBusMock = vi.hoisted( () => ( {
|
|
17
|
+
emit: vi.fn( () => true ),
|
|
18
|
+
on: vi.fn( ( eventType, handler ) => {
|
|
19
|
+
stepOnHandlers[eventType] = handler;
|
|
13
20
|
} )
|
|
14
21
|
} ) );
|
|
15
22
|
|
|
16
23
|
vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
|
|
17
|
-
vi.mock( '#bus', () => ( {
|
|
24
|
+
vi.mock( '#bus', () => ( {
|
|
25
|
+
mainEventBus: mainEventBusMock,
|
|
26
|
+
stepEventBus: stepEventBusMock
|
|
27
|
+
} ) );
|
|
18
28
|
|
|
19
29
|
import {
|
|
30
|
+
emit,
|
|
20
31
|
on,
|
|
21
32
|
onActivityEnd,
|
|
22
33
|
onActivityError,
|
|
@@ -38,11 +49,6 @@ const workflowDetails = {
|
|
|
38
49
|
attempt: 1
|
|
39
50
|
};
|
|
40
51
|
|
|
41
|
-
const catalogWorkflowDetails = {
|
|
42
|
-
...workflowDetails,
|
|
43
|
-
workflowType: WORKFLOW_CATALOG
|
|
44
|
-
};
|
|
45
|
-
|
|
46
52
|
const activityInfo = {
|
|
47
53
|
activityId: 'act-1',
|
|
48
54
|
activityType: 'wf#step',
|
|
@@ -61,8 +67,11 @@ const aggregations = {
|
|
|
61
67
|
describe( 'hooks/index', () => {
|
|
62
68
|
beforeEach( () => {
|
|
63
69
|
vi.clearAllMocks();
|
|
64
|
-
Object.keys(
|
|
65
|
-
delete
|
|
70
|
+
Object.keys( mainOnHandlers ).forEach( k => {
|
|
71
|
+
delete mainOnHandlers[k];
|
|
72
|
+
} );
|
|
73
|
+
Object.keys( stepOnHandlers ).forEach( k => {
|
|
74
|
+
delete stepOnHandlers[k];
|
|
66
75
|
} );
|
|
67
76
|
} );
|
|
68
77
|
|
|
@@ -71,9 +80,9 @@ describe( 'hooks/index', () => {
|
|
|
71
80
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
72
81
|
onError( handler );
|
|
73
82
|
|
|
74
|
-
expect(
|
|
75
|
-
expect(
|
|
76
|
-
expect(
|
|
83
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, expect.any( Function ) );
|
|
84
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, expect.any( Function ) );
|
|
85
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.RUNTIME_ERROR, expect.any( Function ) );
|
|
77
86
|
} );
|
|
78
87
|
|
|
79
88
|
it( 'invokes handler with activity-shaped payload, forwarding bus fields', async () => {
|
|
@@ -81,7 +90,7 @@ describe( 'hooks/index', () => {
|
|
|
81
90
|
onError( handler );
|
|
82
91
|
|
|
83
92
|
const err = new Error( 'act-fail' );
|
|
84
|
-
await
|
|
93
|
+
await mainOnHandlers[BusEventType.ACTIVITY_ERROR]( {
|
|
85
94
|
eventId: 'evt-act-1',
|
|
86
95
|
eventDate,
|
|
87
96
|
activityInfo,
|
|
@@ -108,7 +117,7 @@ describe( 'hooks/index', () => {
|
|
|
108
117
|
onError( handler );
|
|
109
118
|
|
|
110
119
|
const err = new Error( 'wf-fail' );
|
|
111
|
-
await
|
|
120
|
+
await mainOnHandlers[BusEventType.WORKFLOW_ERROR]( {
|
|
112
121
|
eventId: 'evt-wf-1',
|
|
113
122
|
eventDate,
|
|
114
123
|
workflowDetails,
|
|
@@ -131,7 +140,7 @@ describe( 'hooks/index', () => {
|
|
|
131
140
|
onError( handler );
|
|
132
141
|
|
|
133
142
|
const error = new Error( 'rt' );
|
|
134
|
-
await
|
|
143
|
+
await mainOnHandlers[BusEventType.RUNTIME_ERROR]( { eventId: 'evt-rt-1', eventDate, error } );
|
|
135
144
|
|
|
136
145
|
expect( handler ).toHaveBeenCalledWith( { eventId: 'evt-rt-1', eventDate, source: 'runtime', error } );
|
|
137
146
|
} );
|
|
@@ -142,68 +151,35 @@ describe( 'hooks/index', () => {
|
|
|
142
151
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
143
152
|
onBeforeWorkerStart( handler );
|
|
144
153
|
|
|
145
|
-
expect(
|
|
146
|
-
await
|
|
154
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.WORKER_BEFORE_START, expect.any( Function ) );
|
|
155
|
+
await mainOnHandlers[BusEventType.WORKER_BEFORE_START]();
|
|
147
156
|
|
|
148
157
|
expect( handler ).toHaveBeenCalledWith( undefined );
|
|
149
158
|
} );
|
|
150
159
|
} );
|
|
151
160
|
|
|
152
|
-
describe( '
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
eventId: 'evt-ignored', eventDate, workflowDetails: catalogWorkflowDetails
|
|
159
|
-
} ) );
|
|
160
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
161
|
-
|
|
162
|
-
await Promise.resolve( onHandlers[BusEventType.WORKFLOW_START]( {
|
|
163
|
-
eventId: 'evt-start-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
164
|
-
} ) );
|
|
165
|
-
expect( handler ).toHaveBeenCalledWith( {
|
|
166
|
-
eventId: 'evt-start-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
167
|
-
} );
|
|
168
|
-
} );
|
|
169
|
-
} );
|
|
170
|
-
|
|
171
|
-
describe( 'onWorkflowEnd', () => {
|
|
172
|
-
it( 'skips catalog workflow and forwards bus fields for real workflows', async () => {
|
|
173
|
-
const handler = vi.fn().mockResolvedValue( undefined );
|
|
174
|
-
onWorkflowEnd( handler );
|
|
175
|
-
|
|
176
|
-
await Promise.resolve( onHandlers[BusEventType.WORKFLOW_END]( {
|
|
177
|
-
eventId: 'evt-ignored', eventDate, workflowDetails: catalogWorkflowDetails
|
|
178
|
-
} ) );
|
|
179
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
180
|
-
|
|
181
|
-
await Promise.resolve( onHandlers[BusEventType.WORKFLOW_END]( {
|
|
182
|
-
eventId: 'evt-end-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
183
|
-
} ) );
|
|
184
|
-
expect( handler ).toHaveBeenCalledWith( {
|
|
185
|
-
eventId: 'evt-end-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
186
|
-
} );
|
|
187
|
-
} );
|
|
188
|
-
} );
|
|
161
|
+
describe( 'workflow lifecycle hooks', () => {
|
|
162
|
+
const cases = [
|
|
163
|
+
[ 'onWorkflowStart', onWorkflowStart, BusEventType.WORKFLOW_START, {} ],
|
|
164
|
+
[ 'onWorkflowEnd', onWorkflowEnd, BusEventType.WORKFLOW_END, {} ],
|
|
165
|
+
[ 'onWorkflowError', onWorkflowError, BusEventType.WORKFLOW_ERROR, { error: new Error( 'workflow failed' ) } ]
|
|
166
|
+
];
|
|
189
167
|
|
|
190
|
-
|
|
191
|
-
it( 'skips catalog workflow and forwards bus fields for real workflows', async () => {
|
|
168
|
+
it.each( cases )( '%s forwards bus fields', async ( _name, registerHook, eventType, extraFields ) => {
|
|
192
169
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
193
|
-
const
|
|
194
|
-
|
|
170
|
+
const payload = {
|
|
171
|
+
eventId: 'evt-workflow-1',
|
|
172
|
+
eventDate,
|
|
173
|
+
workflowDetails,
|
|
174
|
+
extra: 'passthrough',
|
|
175
|
+
...extraFields
|
|
176
|
+
};
|
|
177
|
+
registerHook( handler );
|
|
195
178
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
} ) );
|
|
199
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
179
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
|
|
180
|
+
await mainOnHandlers[eventType]( payload );
|
|
200
181
|
|
|
201
|
-
|
|
202
|
-
eventId: 'evt-err-1', eventDate, workflowDetails, error: err, extra: 'passthrough'
|
|
203
|
-
} ) );
|
|
204
|
-
expect( handler ).toHaveBeenCalledWith( {
|
|
205
|
-
eventId: 'evt-err-1', eventDate, workflowDetails, error: err, extra: 'passthrough'
|
|
206
|
-
} );
|
|
182
|
+
expect( handler ).toHaveBeenCalledWith( payload );
|
|
207
183
|
} );
|
|
208
184
|
} );
|
|
209
185
|
|
|
@@ -214,53 +190,49 @@ describe( 'hooks/index', () => {
|
|
|
214
190
|
[ 'onActivityError', onActivityError, BusEventType.ACTIVITY_ERROR, { error: new Error( 'activity failed' ) } ]
|
|
215
191
|
];
|
|
216
192
|
|
|
217
|
-
it.each( cases )( '%s
|
|
193
|
+
it.each( cases )( '%s forwards internal activity bus fields', async ( _name, registerHook, eventType, extraFields = {} ) => {
|
|
218
194
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
expect( messageBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
|
|
222
|
-
|
|
223
|
-
await Promise.resolve( onHandlers[eventType]( {
|
|
224
|
-
eventId: 'evt-ignored',
|
|
225
|
-
eventDate,
|
|
226
|
-
activityInfo,
|
|
227
|
-
workflowDetails,
|
|
228
|
-
outputActivityKind: ComponentType.INTERNAL_STEP,
|
|
229
|
-
...extraFields
|
|
230
|
-
} ) );
|
|
231
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
232
|
-
|
|
233
|
-
await Promise.resolve( onHandlers[eventType]( {
|
|
195
|
+
const payload = {
|
|
234
196
|
eventId: 'evt-activity-1',
|
|
235
197
|
eventDate,
|
|
236
198
|
activityInfo,
|
|
237
199
|
workflowDetails,
|
|
238
|
-
outputActivityKind:
|
|
200
|
+
outputActivityKind: 'internal_step',
|
|
239
201
|
extra: 'passthrough',
|
|
240
202
|
...extraFields
|
|
241
|
-
}
|
|
203
|
+
};
|
|
204
|
+
registerHook( handler );
|
|
242
205
|
|
|
243
|
-
expect(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
workflowDetails,
|
|
248
|
-
outputActivityKind: ComponentType.STEP,
|
|
249
|
-
extra: 'passthrough',
|
|
250
|
-
...extraFields
|
|
251
|
-
} );
|
|
206
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
|
|
207
|
+
await mainOnHandlers[eventType]( payload );
|
|
208
|
+
|
|
209
|
+
expect( handler ).toHaveBeenCalledWith( payload );
|
|
252
210
|
} );
|
|
253
211
|
} );
|
|
254
212
|
|
|
255
213
|
describe( 'on', () => {
|
|
256
|
-
it( 'subscribes to
|
|
214
|
+
it( 'subscribes to SDK and user event channels and forwards payloads', async () => {
|
|
257
215
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
258
216
|
on( 'myEvent', handler );
|
|
259
217
|
|
|
260
|
-
expect(
|
|
261
|
-
|
|
218
|
+
expect( stepEventBusMock.on ).toHaveBeenCalledWith( 'sdk:myEvent', expect.any( Function ) );
|
|
219
|
+
expect( stepEventBusMock.on ).toHaveBeenCalledWith( 'usr:myEvent', expect.any( Function ) );
|
|
220
|
+
await stepOnHandlers['sdk:myEvent']( { payload: { source: 'sdk' } } );
|
|
221
|
+
await stepOnHandlers['usr:myEvent']( { payload: { source: 'user' } } );
|
|
222
|
+
|
|
223
|
+
expect( handler ).toHaveBeenNthCalledWith( 1, { payload: { source: 'sdk' } } );
|
|
224
|
+
expect( handler ).toHaveBeenNthCalledWith( 2, { payload: { source: 'user' } } );
|
|
225
|
+
} );
|
|
226
|
+
} );
|
|
227
|
+
|
|
228
|
+
describe( 'emit', () => {
|
|
229
|
+
it( 'emits payloads on the user event channel', () => {
|
|
230
|
+
const payload = { foo: 1 };
|
|
231
|
+
|
|
232
|
+
const emitted = emit( 'myEvent', payload );
|
|
262
233
|
|
|
263
|
-
expect(
|
|
234
|
+
expect( stepEventBusMock.emit ).toHaveBeenCalledWith( 'usr:myEvent', payload );
|
|
235
|
+
expect( emitted ).toBe( true );
|
|
264
236
|
} );
|
|
265
237
|
} );
|
|
266
238
|
} );
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
* Tools to interact with Events
|
|
3
3
|
*/
|
|
4
4
|
export declare const Event: {
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
|
-
* Emits
|
|
7
|
-
*
|
|
8
|
-
* When called inside an Output activity context, the framework automatically
|
|
9
|
-
* attaches `activityInfo`, `workflowDetails`, and `outputActivityKind` onto the emitted payload.
|
|
7
|
+
* Emits an event on step message bus.
|
|
10
8
|
*
|
|
11
9
|
* @param eventName - The name of the event to emit
|
|
12
|
-
* @param payload - An optional payload to send to the event
|
|
10
|
+
* @param payload - An optional payload to send to the event.
|
|
13
11
|
*/
|
|
14
|
-
emit( eventName: string, payload?: object ):
|
|
12
|
+
emit( eventName: string, payload?: object ): boolean;
|
|
15
13
|
};
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Storage } from '#async_storage';
|
|
1
|
+
import { stepEventBus } from '#bus';
|
|
3
2
|
|
|
4
3
|
export const Event = {
|
|
5
|
-
emit: ( eventName, payload ) => {
|
|
6
|
-
const ctx = Storage.load();
|
|
7
|
-
|
|
8
|
-
messageBus.emit( `external:${eventName}`, {
|
|
9
|
-
...payload ?? {},
|
|
10
|
-
...( ctx && {
|
|
11
|
-
activityInfo: ctx.activityInfo,
|
|
12
|
-
workflowDetails: ctx.workflowDetails,
|
|
13
|
-
outputActivityKind: ctx.outputActivityKind
|
|
14
|
-
} )
|
|
15
|
-
} );
|
|
16
|
-
}
|
|
4
|
+
emit: ( eventName, payload ) => stepEventBus.emit( `sdk:${eventName}`, payload )
|
|
17
5
|
};
|
|
18
|
-
|
|
@@ -1,118 +1,40 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
|
|
3
|
-
const loadMock = vi.hoisted( () => vi.fn() );
|
|
4
3
|
const emitMock = vi.hoisted( () => vi.fn() );
|
|
5
4
|
|
|
6
|
-
vi.mock( '#async_storage', () => ( {
|
|
7
|
-
Storage: { load: loadMock }
|
|
8
|
-
} ) );
|
|
9
|
-
|
|
10
5
|
vi.mock( '#bus', () => ( {
|
|
11
|
-
|
|
6
|
+
stepEventBus: { emit: emitMock }
|
|
12
7
|
} ) );
|
|
13
8
|
|
|
14
9
|
import { Event } from './events.js';
|
|
15
10
|
|
|
16
|
-
// `eventId` stamping is the bus layer's responsibility (see bus.spec.js).
|
|
17
|
-
// Assertions here use `objectContaining` so they don't have to know about that enrichment.
|
|
18
11
|
describe( 'Event.emit', () => {
|
|
19
12
|
beforeEach( () => {
|
|
20
13
|
vi.clearAllMocks();
|
|
21
14
|
} );
|
|
22
15
|
|
|
23
|
-
it( 'forwards
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
workflowType: 'workflow'
|
|
29
|
-
};
|
|
30
|
-
const workflowDetails = {
|
|
31
|
-
workflowId: 'wf-1',
|
|
32
|
-
runId: 'run-1',
|
|
33
|
-
workflowType: 'workflow'
|
|
34
|
-
};
|
|
35
|
-
loadMock.mockReturnValue( {
|
|
36
|
-
activityInfo,
|
|
37
|
-
workflowDetails,
|
|
38
|
-
outputActivityKind: 'step'
|
|
39
|
-
} );
|
|
16
|
+
it( 'forwards class instance payloads unchanged', () => {
|
|
17
|
+
class Payload {
|
|
18
|
+
value = 'test';
|
|
19
|
+
}
|
|
20
|
+
const payload = new Payload();
|
|
40
21
|
|
|
41
|
-
Event.emit( '
|
|
22
|
+
Event.emit( 'test:event', payload );
|
|
42
23
|
|
|
43
|
-
expect( emitMock ).toHaveBeenCalledWith( '
|
|
44
|
-
activityInfo,
|
|
45
|
-
workflowDetails,
|
|
46
|
-
outputActivityKind: 'step',
|
|
47
|
-
modelId: 'gpt-4o'
|
|
48
|
-
} ) );
|
|
24
|
+
expect( emitMock ).toHaveBeenCalledWith( 'sdk:test:event', payload );
|
|
49
25
|
} );
|
|
50
26
|
|
|
51
|
-
it( 'emits
|
|
52
|
-
|
|
27
|
+
it( 'emits plain object payloads on the SDK event channel', () => {
|
|
28
|
+
const payload = { modelId: 'gpt-4o' };
|
|
53
29
|
|
|
54
|
-
Event.emit( '
|
|
30
|
+
Event.emit( 'cost:llm:request', payload );
|
|
55
31
|
|
|
56
|
-
expect( emitMock ).toHaveBeenCalledWith( '
|
|
32
|
+
expect( emitMock ).toHaveBeenCalledWith( 'sdk:cost:llm:request', payload );
|
|
57
33
|
} );
|
|
58
34
|
|
|
59
|
-
it( '
|
|
60
|
-
const activityInfo = {
|
|
61
|
-
activityId: 'act-2',
|
|
62
|
-
activityType: 'step',
|
|
63
|
-
workflowExecution: { workflowId: 'wf-2', runId: 'run-2' },
|
|
64
|
-
workflowType: 'workflow'
|
|
65
|
-
};
|
|
66
|
-
const workflowDetails = {
|
|
67
|
-
workflowId: 'wf-2',
|
|
68
|
-
runId: 'run-2',
|
|
69
|
-
workflowType: 'workflow'
|
|
70
|
-
};
|
|
71
|
-
loadMock.mockReturnValue( {
|
|
72
|
-
activityInfo,
|
|
73
|
-
workflowDetails,
|
|
74
|
-
outputActivityKind: 'step'
|
|
75
|
-
} );
|
|
76
|
-
|
|
35
|
+
it( 'forwards a missing payload as undefined', () => {
|
|
77
36
|
Event.emit( 'lifecycle:start' );
|
|
78
37
|
|
|
79
|
-
expect( emitMock ).toHaveBeenCalledWith( '
|
|
80
|
-
activityInfo,
|
|
81
|
-
workflowDetails,
|
|
82
|
-
outputActivityKind: 'step'
|
|
83
|
-
} ) );
|
|
84
|
-
} );
|
|
85
|
-
|
|
86
|
-
it( 'does not let payload override activityInfo, workflowDetails, or outputActivityKind', () => {
|
|
87
|
-
const activityInfo = {
|
|
88
|
-
activityId: 'act-3',
|
|
89
|
-
activityType: 'step',
|
|
90
|
-
workflowExecution: { workflowId: 'wf-3', runId: 'run-3' },
|
|
91
|
-
workflowType: 'workflow'
|
|
92
|
-
};
|
|
93
|
-
const workflowDetails = {
|
|
94
|
-
workflowId: 'wf-3',
|
|
95
|
-
runId: 'run-3',
|
|
96
|
-
workflowType: 'workflow'
|
|
97
|
-
};
|
|
98
|
-
loadMock.mockReturnValue( {
|
|
99
|
-
activityInfo,
|
|
100
|
-
workflowDetails,
|
|
101
|
-
outputActivityKind: 'step'
|
|
102
|
-
} );
|
|
103
|
-
|
|
104
|
-
Event.emit( 'cost:http:request', {
|
|
105
|
-
activityInfo: { activityId: 'should-be-overridden' },
|
|
106
|
-
workflowDetails: { workflowId: 'should-be-overridden' },
|
|
107
|
-
outputActivityKind: 'should-be-overridden',
|
|
108
|
-
url: 'https://example.com'
|
|
109
|
-
} );
|
|
110
|
-
|
|
111
|
-
expect( emitMock ).toHaveBeenCalledWith( 'external:cost:http:request', expect.objectContaining( {
|
|
112
|
-
activityInfo,
|
|
113
|
-
workflowDetails,
|
|
114
|
-
outputActivityKind: 'step',
|
|
115
|
-
url: 'https://example.com'
|
|
116
|
-
} ) );
|
|
38
|
+
expect( emitMock ).toHaveBeenCalledWith( 'sdk:lifecycle:start', undefined );
|
|
117
39
|
} );
|
|
118
40
|
} );
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mainEventBus } from '#bus';
|
|
2
2
|
import { ACTIVITY_LOGGER_SYMBOL, BusEventType } from '#consts';
|
|
3
3
|
import { activityInfo as activityInfoFn } from '@temporalio/activity';
|
|
4
4
|
import { assignImmutableProperty } from '#helpers/object';
|
|
@@ -9,6 +9,6 @@ import { assignImmutableProperty } from '#helpers/object';
|
|
|
9
9
|
export const bindGlobalFunctions = () => {
|
|
10
10
|
/** Defines the activity logger function, accessible in activity context via logger interface */
|
|
11
11
|
assignImmutableProperty( globalThis, ACTIVITY_LOGGER_SYMBOL, ( { level, message, metadata } ) =>
|
|
12
|
-
|
|
12
|
+
mainEventBus.emit( BusEventType.ACTIVITY_LOG, { level, message, metadata, activityInfo: activityInfoFn() } )
|
|
13
13
|
);
|
|
14
14
|
};
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
}
|
|
11
11
|
} ) );
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const mainEventBusMock = vi.hoisted( () => ( {
|
|
14
14
|
emit: vi.fn()
|
|
15
15
|
} ) );
|
|
16
16
|
const activityInfoMock = vi.hoisted( () => vi.fn( () => ( {
|
|
@@ -19,7 +19,7 @@ const activityInfoMock = vi.hoisted( () => vi.fn( () => ( {
|
|
|
19
19
|
} ) ) );
|
|
20
20
|
|
|
21
21
|
vi.mock( '#consts', () => ( { ACTIVITY_LOGGER_SYMBOL, BusEventType } ) );
|
|
22
|
-
vi.mock( '#bus', () => ( {
|
|
22
|
+
vi.mock( '#bus', () => ( { mainEventBus: mainEventBusMock } ) );
|
|
23
23
|
vi.mock( '@temporalio/activity', () => ( {
|
|
24
24
|
activityInfo: activityInfoMock
|
|
25
25
|
} ) );
|
|
@@ -42,7 +42,7 @@ describe( 'worker/global_functions', () => {
|
|
|
42
42
|
} );
|
|
43
43
|
|
|
44
44
|
expect( activityInfoMock ).toHaveBeenCalledTimes( 1 );
|
|
45
|
-
expect(
|
|
45
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.ACTIVITY_LOG, {
|
|
46
46
|
level: 'debug',
|
|
47
47
|
message: 'activity detail',
|
|
48
48
|
metadata,
|
package/src/worker/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createChildLogger } from '#logger';
|
|
|
13
13
|
import { setupInterruptionHandler } from './interruption.js';
|
|
14
14
|
import { CatalogJob } from './catalog_workflow/catalog_job.js';
|
|
15
15
|
import { bootstrapFetchProxy } from './proxy.js';
|
|
16
|
-
import {
|
|
16
|
+
import { mainEventBus } from '#bus';
|
|
17
17
|
import { BusEventType } from '#consts';
|
|
18
18
|
import { setupTelemetry } from './telemetry.js';
|
|
19
19
|
import { TemporalConnectionMonitor } from './connection_monitor.js';
|
|
@@ -60,7 +60,7 @@ const execute = async () => {
|
|
|
60
60
|
log.info( 'Loading activities...', { callerDir } );
|
|
61
61
|
const { activities } = await loadActivities( callerDir, workflows );
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
mainEventBus.emit( BusEventType.WORKER_BEFORE_START );
|
|
64
64
|
bootstrapFetchProxy();
|
|
65
65
|
|
|
66
66
|
log.info( 'Initializing tracing...' );
|
|
@@ -218,7 +218,7 @@ execute()
|
|
|
218
218
|
.catch( error => {
|
|
219
219
|
log.error( 'Fatal error', { error: error.message, stack: error.stack } );
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
mainEventBus.emit( BusEventType.RUNTIME_ERROR, { error } );
|
|
222
222
|
|
|
223
223
|
const timeToFlushEvent = configs.processFailureShutdownDelay;
|
|
224
224
|
log.info( `Exiting in ${timeToFlushEvent}ms` );
|