@outputai/core 0.10.1-dev.b7b2fbe.0 → 0.10.1-next.14a191e.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 +8 -7
- package/src/bus.js +58 -20
- package/src/bus.spec.js +135 -77
- package/src/helpers/fetch.js +1 -1
- 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 +56 -49
- package/src/hooks/index.spec.js +88 -97
- package/src/hooks/pending_hooks.js +24 -0
- package/src/hooks/pending_hooks.spec.js +62 -0
- package/src/interface/index.js +2 -1
- package/src/interface/workflow_utils.d.ts +15 -0
- package/src/interface/workflow_utils.js +19 -0
- package/src/interface/workflow_utils.spec.js +82 -1
- 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/configs.js +0 -3
- package/src/worker/global_functions.js +2 -2
- package/src/worker/global_functions.spec.js +3 -3
- package/src/worker/index.js +14 -10
- package/src/worker/index.spec.js +13 -8
- 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/worker/proxy.js +0 -20
- package/src/worker/proxy.spec.js +0 -51
|
@@ -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
|
} );
|
package/src/worker/configs.js
CHANGED
|
@@ -36,8 +36,6 @@ const envVarSchema = z.object( {
|
|
|
36
36
|
OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 2 * 60 * 1000 ) ), // 2min
|
|
37
37
|
// Whether to send activity heartbeats (enabled by default)
|
|
38
38
|
OUTPUT_ACTIVITY_HEARTBEAT_ENABLED: z.transform( v => v === undefined ? true : isStringboolTrue( v ) ),
|
|
39
|
-
// Time to allow for hooks to flush before shutdown
|
|
40
|
-
OUTPUT_PROCESS_FAILURE_SHUTDOWN_DELAY: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 3000 ) ),
|
|
41
39
|
// Set temporal worker shutdown force time
|
|
42
40
|
TEMPORAL_SHUTDOWN_FORCE_TIME: durationSchema,
|
|
43
41
|
// Set temporal worker shutdown grace time
|
|
@@ -70,7 +68,6 @@ export const catalogId = envVars.OUTPUT_CATALOG_ID;
|
|
|
70
68
|
export const workerTelemetryIntervalMs = envVars.OUTPUT_WORKER_TELEMETRY_INTERVAL_MS;
|
|
71
69
|
export const activityHeartbeatIntervalMs = envVars.OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS;
|
|
72
70
|
export const activityHeartbeatEnabled = envVars.OUTPUT_ACTIVITY_HEARTBEAT_ENABLED;
|
|
73
|
-
export const processFailureShutdownDelay = envVars.OUTPUT_PROCESS_FAILURE_SHUTDOWN_DELAY;
|
|
74
71
|
export const shutdownForceTime = envVars.TEMPORAL_SHUTDOWN_FORCE_TIME;
|
|
75
72
|
export const shutdownGraceTime = envVars.TEMPORAL_SHUTDOWN_GRACE_TIME;
|
|
76
73
|
export const grpcProxy = envVars.TEMPORAL_GRPC_PROXY;
|
|
@@ -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
|
@@ -12,8 +12,8 @@ import { initInterceptors } from './interceptors/index.js';
|
|
|
12
12
|
import { createChildLogger } from '#logger';
|
|
13
13
|
import { setupInterruptionHandler } from './interruption.js';
|
|
14
14
|
import { CatalogJob } from './catalog_workflow/catalog_job.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { mainEventBus } from '#bus';
|
|
16
|
+
import { flushPendingHooks } from '#hooks/pending_hooks';
|
|
17
17
|
import { BusEventType } from '#consts';
|
|
18
18
|
import { setupTelemetry } from './telemetry.js';
|
|
19
19
|
import { TemporalConnectionMonitor } from './connection_monitor.js';
|
|
@@ -60,8 +60,7 @@ const execute = async () => {
|
|
|
60
60
|
log.info( 'Loading activities...', { callerDir } );
|
|
61
61
|
const { activities } = await loadActivities( callerDir, workflows );
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
bootstrapFetchProxy();
|
|
63
|
+
mainEventBus.emit( BusEventType.WORKER_BEFORE_START );
|
|
65
64
|
|
|
66
65
|
log.info( 'Initializing tracing...' );
|
|
67
66
|
await initTracing();
|
|
@@ -214,13 +213,18 @@ execute()
|
|
|
214
213
|
.catch( e => log.warn( 'Connection close error', { error: e.message } ) );
|
|
215
214
|
}
|
|
216
215
|
} )
|
|
217
|
-
.then( () =>
|
|
218
|
-
|
|
216
|
+
.then( async () => {
|
|
217
|
+
log.info( 'Flushing hook callbacks...' );
|
|
218
|
+
await flushPendingHooks();
|
|
219
|
+
log.info( 'Bye' );
|
|
220
|
+
} )
|
|
221
|
+
.catch( async error => {
|
|
219
222
|
log.error( 'Fatal error', { error: error.message, stack: error.stack } );
|
|
220
223
|
|
|
221
|
-
|
|
224
|
+
mainEventBus.emit( BusEventType.RUNTIME_ERROR, { error } );
|
|
222
225
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
log.info( 'Flushing hook callbacks...' );
|
|
227
|
+
await flushPendingHooks();
|
|
228
|
+
log.info( 'Exiting...' );
|
|
229
|
+
setTimeout( () => process.exit( 1 ) );
|
|
226
230
|
} );
|
package/src/worker/index.spec.js
CHANGED
|
@@ -6,8 +6,9 @@ const {
|
|
|
6
6
|
configValues,
|
|
7
7
|
connectionMonitorInstance,
|
|
8
8
|
createCatalogMock,
|
|
9
|
+
flushPendingHooksMock,
|
|
9
10
|
initInterceptorsMock,
|
|
10
|
-
|
|
11
|
+
mainEventBusMock,
|
|
11
12
|
mockConnection,
|
|
12
13
|
mockLog,
|
|
13
14
|
mockWorker,
|
|
@@ -46,7 +47,6 @@ const {
|
|
|
46
47
|
maxConcurrentActivityTaskPolls: 5,
|
|
47
48
|
maxConcurrentWorkflowTaskPolls: 5,
|
|
48
49
|
workerTuner: undefined,
|
|
49
|
-
processFailureShutdownDelay: 0,
|
|
50
50
|
shutdownForceTime: undefined,
|
|
51
51
|
shutdownGraceTime: undefined
|
|
52
52
|
};
|
|
@@ -105,12 +105,13 @@ const {
|
|
|
105
105
|
connectionMonitorInstance,
|
|
106
106
|
createCatalogMock: vi.fn().mockReturnValue( { workflows: [], activities: {} } ),
|
|
107
107
|
createWorkflowsEntryPointMock: vi.fn().mockReturnValue( '/fake/workflows/path.js' ),
|
|
108
|
+
flushPendingHooksMock: vi.fn().mockResolvedValue( undefined ),
|
|
108
109
|
hashSourceCodeMock: vi.fn().mockResolvedValue( 'catalog-hash' ),
|
|
109
110
|
initInterceptorsMock: vi.fn().mockReturnValue( [] ),
|
|
110
111
|
loadActivitiesMock: vi.fn().mockResolvedValue( {} ),
|
|
111
112
|
loadHooksMock: vi.fn().mockResolvedValue( undefined ),
|
|
112
113
|
loadWorkflowsMock: vi.fn().mockResolvedValue( [] ),
|
|
113
|
-
|
|
114
|
+
mainEventBusMock: { emit: vi.fn(), on: vi.fn() },
|
|
114
115
|
mockConnection: { close: vi.fn().mockResolvedValue( undefined ) },
|
|
115
116
|
mockLog: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
|
|
116
117
|
mockWorker,
|
|
@@ -128,7 +129,8 @@ vi.mock( '#consts', async importOriginal => {
|
|
|
128
129
|
} );
|
|
129
130
|
const initTracing = vi.fn().mockResolvedValue( undefined );
|
|
130
131
|
vi.mock( '#tracing', () => ( { init: initTracing } ) );
|
|
131
|
-
vi.mock( '#bus', () => ( {
|
|
132
|
+
vi.mock( '#bus', () => ( { mainEventBus: mainEventBusMock } ) );
|
|
133
|
+
vi.mock( '#hooks/pending_hooks', () => ( { flushPendingHooks: flushPendingHooksMock } ) );
|
|
132
134
|
|
|
133
135
|
const loadWorkflowsMock = vi.fn().mockResolvedValue( { workflows: [], entrypoint: '/fake/workflows/path.js' } );
|
|
134
136
|
const loadActivitiesMock = vi.fn().mockResolvedValue( { activities: {} } );
|
|
@@ -145,7 +147,6 @@ vi.mock( './sinks.js', () => ( { sinks: {} } ) );
|
|
|
145
147
|
vi.mock( './catalog_workflow/index.js', () => ( { createCatalog: createCatalogMock } ) );
|
|
146
148
|
vi.mock( './bundler_options.js', () => ( { webpackConfigHook: vi.fn() } ) );
|
|
147
149
|
vi.mock( './interceptors/index.js', () => ( { initInterceptors: initInterceptorsMock } ) );
|
|
148
|
-
vi.mock( './proxy.js', () => ( { bootstrapFetchProxy: vi.fn() } ) );
|
|
149
150
|
vi.mock( './telemetry.js', () => ( { setupTelemetry: setupTelemetryMock } ) );
|
|
150
151
|
vi.mock( './interruption.js', () => ( { setupInterruptionHandler: setupInterruptionHandlerMock } ) );
|
|
151
152
|
vi.mock( './global_functions.js', () => ( { bindGlobalFunctions: bindGlobalFunctionsMock } ) );
|
|
@@ -262,6 +263,7 @@ describe( 'worker/index', () => {
|
|
|
262
263
|
expect( catalogJobInstance.run ).toHaveBeenCalled();
|
|
263
264
|
|
|
264
265
|
await settleWorker();
|
|
266
|
+
await vi.waitFor( () => expect( flushPendingHooksMock ).toHaveBeenCalledOnce() );
|
|
265
267
|
expect( mockLog.info ).toHaveBeenCalledWith( 'Bye' );
|
|
266
268
|
} );
|
|
267
269
|
|
|
@@ -357,8 +359,11 @@ describe( 'worker/index', () => {
|
|
|
357
359
|
error: 'Big Failure'
|
|
358
360
|
} ) );
|
|
359
361
|
} );
|
|
360
|
-
expect(
|
|
362
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( expect.any( String ), { error } );
|
|
361
363
|
await vi.waitFor( () => expect( exitMock ).toHaveBeenCalledWith( 1 ) );
|
|
364
|
+
expect( flushPendingHooksMock ).toHaveBeenCalledOnce();
|
|
365
|
+
expect( mainEventBusMock.emit.mock.invocationCallOrder[0] ).toBeLessThan( flushPendingHooksMock.mock.invocationCallOrder[0] );
|
|
366
|
+
expect( flushPendingHooksMock.mock.invocationCallOrder[0] ).toBeLessThan( exitMock.mock.invocationCallOrder[0] );
|
|
362
367
|
} );
|
|
363
368
|
|
|
364
369
|
it( 'throws connection monitor errors after graceful shutdown', async () => {
|
|
@@ -378,7 +383,7 @@ describe( 'worker/index', () => {
|
|
|
378
383
|
} );
|
|
379
384
|
expect( mockWorker.shutdown ).toHaveBeenCalledOnce();
|
|
380
385
|
expect( catalogJobInstance.interrupt ).toHaveBeenCalledOnce();
|
|
381
|
-
expect(
|
|
386
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( expect.any( String ), { error } );
|
|
382
387
|
} );
|
|
383
388
|
|
|
384
389
|
it( 'throws catalog job errors after graceful shutdown', async () => {
|
|
@@ -398,7 +403,7 @@ describe( 'worker/index', () => {
|
|
|
398
403
|
} );
|
|
399
404
|
expect( mockWorker.shutdown ).toHaveBeenCalledOnce();
|
|
400
405
|
expect( connectionMonitorInstance.stop ).toHaveBeenCalledOnce();
|
|
401
|
-
expect(
|
|
406
|
+
expect( mainEventBusMock.emit ).toHaveBeenCalledWith( expect.any( String ), { error } );
|
|
402
407
|
} );
|
|
403
408
|
|
|
404
409
|
it( 'cleans up partial startup failures after connecting', async () => {
|
|
@@ -4,7 +4,7 @@ import * as Tracing from '#tracing';
|
|
|
4
4
|
import { headersToObject } from './headers.js';
|
|
5
5
|
import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL } from '#consts';
|
|
6
6
|
import { activityHeartbeatEnabled, activityHeartbeatIntervalMs } from '../configs.js';
|
|
7
|
-
import {
|
|
7
|
+
import { mainEventBus } from '#bus';
|
|
8
8
|
import { aggregateAttributes } from '#helpers/aggregations';
|
|
9
9
|
import { buildApplicationFailureWithDetails } from '#helpers/errors';
|
|
10
10
|
|
|
@@ -68,7 +68,7 @@ export class ActivityExecutionInterceptor {
|
|
|
68
68
|
addAttribute
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
mainEventBus.emit( BusEventType.ACTIVITY_START, { activityInfo, workflowDetails, outputActivityKind } );
|
|
72
72
|
Tracing.addEventStart( { id: activityId, name: activityType, kind: outputActivityKind, parentId: runId, details: input.args[0], traceInfo } );
|
|
73
73
|
|
|
74
74
|
try {
|
|
@@ -79,14 +79,14 @@ export class ActivityExecutionInterceptor {
|
|
|
79
79
|
|
|
80
80
|
const aggregations = state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null;
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
mainEventBus.emit( BusEventType.ACTIVITY_END, { activityInfo, aggregations, workflowDetails, outputActivityKind } );
|
|
83
83
|
Tracing.addEventEnd( { id: activityId, details: output, traceInfo } );
|
|
84
84
|
|
|
85
85
|
return { [ACTIVITY_WRAPPER_VERSION_FIELD]: 1, output, aggregations };
|
|
86
86
|
|
|
87
87
|
} catch ( error ) {
|
|
88
88
|
const aggregations = state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null;
|
|
89
|
-
|
|
89
|
+
mainEventBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, aggregations, workflowDetails, outputActivityKind, error } );
|
|
90
90
|
Tracing.addEventError( { id: activityId, details: error, traceInfo } );
|
|
91
91
|
|
|
92
92
|
throw aggregations ? buildApplicationFailureWithDetails( error, { aggregations } ) : error;
|
|
@@ -61,8 +61,8 @@ vi.mock( './headers.js', () => ( {
|
|
|
61
61
|
headersToObject: () => ( { traceInfo: traceInfoMock, workflowDetails: workflowDetailsMock } )
|
|
62
62
|
} ) );
|
|
63
63
|
|
|
64
|
-
const
|
|
65
|
-
vi.mock( '#bus', () => ( {
|
|
64
|
+
const mainEventBusEmitMock = vi.fn();
|
|
65
|
+
vi.mock( '#bus', () => ( { mainEventBus: { emit: mainEventBusEmitMock } } ) );
|
|
66
66
|
|
|
67
67
|
vi.mock( '#consts', async importOriginal => {
|
|
68
68
|
const actual = await importOriginal();
|
|
@@ -135,11 +135,11 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
135
135
|
aggregations: null,
|
|
136
136
|
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1
|
|
137
137
|
} );
|
|
138
|
-
expect(
|
|
138
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith(
|
|
139
139
|
BusEventType.ACTIVITY_START,
|
|
140
140
|
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
141
141
|
);
|
|
142
|
-
expect(
|
|
142
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith(
|
|
143
143
|
BusEventType.ACTIVITY_END,
|
|
144
144
|
{ activityInfo: activityInfoMock, aggregations: null, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
145
145
|
);
|
|
@@ -178,7 +178,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
178
178
|
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1
|
|
179
179
|
} );
|
|
180
180
|
|
|
181
|
-
expect(
|
|
181
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_END, expect.any( Object ) );
|
|
182
182
|
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'act-1', details: { result: 'sync' }, traceInfo: traceInfoMock } );
|
|
183
183
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
184
184
|
} );
|
|
@@ -198,7 +198,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
198
198
|
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1
|
|
199
199
|
} );
|
|
200
200
|
|
|
201
|
-
expect(
|
|
201
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_END, {
|
|
202
202
|
activityInfo: activityInfoMock,
|
|
203
203
|
aggregations: httpRequestAggregations,
|
|
204
204
|
workflowDetails: workflowDetailsMock,
|
|
@@ -226,7 +226,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
226
226
|
} ],
|
|
227
227
|
cause: error
|
|
228
228
|
} );
|
|
229
|
-
expect(
|
|
229
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
230
230
|
activityInfo: activityInfoMock,
|
|
231
231
|
aggregations: httpRequestAggregations,
|
|
232
232
|
workflowDetails: workflowDetailsMock,
|
|
@@ -318,8 +318,8 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
318
318
|
vi.advanceTimersByTime( 0 );
|
|
319
319
|
|
|
320
320
|
await expect( promise ).rejects.toThrow( 'step failed' );
|
|
321
|
-
expect(
|
|
322
|
-
expect(
|
|
321
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_START, expect.any( Object ) );
|
|
322
|
+
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
323
323
|
activityInfo: activityInfoMock,
|
|
324
324
|
aggregations: null,
|
|
325
325
|
workflowDetails: workflowDetailsMock,
|
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
|
},
|