@outputai/core 0.8.1-next.e92f632.0 → 0.8.2-dev.e78f6b4.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 +9 -10
- package/src/bus.js +1 -1
- package/src/consts.js +5 -2
- package/src/helpers/component.js +12 -0
- package/src/helpers/component.spec.js +54 -0
- package/src/helpers/fetch.js +105 -0
- package/src/helpers/fetch.spec.js +203 -0
- package/src/helpers/function.js +15 -0
- package/src/helpers/function.spec.js +48 -0
- package/src/helpers/object.js +98 -0
- package/src/helpers/object.spec.js +377 -0
- package/src/helpers/promise.js +29 -0
- package/src/helpers/promise.spec.js +35 -0
- package/src/helpers/string.js +30 -0
- package/src/helpers/string.spec.js +64 -0
- package/src/interface/evaluator.js +14 -12
- package/src/interface/evaluator.spec.js +10 -6
- package/src/interface/index.d.ts +7 -6
- package/src/interface/index.js +2 -0
- package/src/interface/logger.d.ts +53 -0
- package/src/interface/logger.js +68 -0
- package/src/interface/logger.spec.js +138 -0
- package/src/interface/step.js +15 -12
- package/src/interface/step.spec.js +10 -6
- package/src/interface/webhook.d.ts +21 -2
- package/src/interface/workflow.js +85 -78
- package/src/interface/workflow.spec.js +11 -4
- package/src/internal_activities/index.js +38 -35
- package/src/internal_activities/index.spec.js +27 -4
- package/src/logger/development.js +2 -2
- package/src/logger/development.spec.js +19 -2
- package/src/logger/production.js +1 -1
- package/src/logger/production.spec.js +24 -5
- package/src/sdk/README.md +47 -0
- package/src/sdk/helpers/component_metadata.d.ts +17 -0
- package/src/sdk/helpers/component_metadata.js +6 -0
- package/src/sdk/helpers/component_metadata.spec.js +30 -0
- package/src/sdk/helpers/index.d.ts +12 -0
- package/src/sdk/helpers/index.js +3 -0
- package/src/sdk/helpers/objects.d.ts +51 -0
- package/src/sdk/helpers/objects.js +8 -0
- package/src/sdk/helpers/objects.spec.js +16 -0
- package/src/sdk/helpers/path.d.ts +11 -0
- package/src/sdk/helpers/path.js +32 -0
- package/src/{utils/resolve_invocation_dir.spec.js → sdk/helpers/path.spec.js} +9 -9
- package/src/sdk/runtime/context.d.ts +30 -0
- package/src/sdk/runtime/context.js +15 -0
- package/src/{activity_integration → sdk/runtime}/context.spec.js +5 -5
- package/src/sdk/runtime/events.d.ts +15 -0
- package/src/sdk/runtime/events.js +18 -0
- package/src/{activity_integration → sdk/runtime}/events.spec.js +8 -9
- package/src/sdk/runtime/index.d.ts +12 -0
- package/src/sdk/runtime/index.js +3 -0
- package/src/sdk/runtime/tracing.d.ts +46 -0
- package/src/sdk/runtime/tracing.js +11 -0
- package/src/tracing/processors/s3/redis_client.spec.js +0 -6
- package/src/tracing/processors/s3/s3_client.spec.js +0 -6
- package/src/tracing/trace_engine.js +1 -1
- package/src/worker/catalog_workflow/catalog_job.js +1 -1
- package/src/worker/catalog_workflow/index.spec.js +8 -11
- package/src/worker/configs.js +14 -1
- package/src/worker/configs.spec.js +34 -1
- package/src/worker/connection_monitor.js +3 -14
- package/src/worker/connection_monitor.spec.js +4 -21
- package/src/worker/global_functions.js +14 -0
- package/src/worker/global_functions.spec.js +55 -0
- package/src/worker/index.js +11 -3
- package/src/worker/index.spec.js +29 -1
- package/src/worker/interceptors/activity.js +2 -2
- package/src/worker/interceptors/workflow.js +3 -3
- package/src/worker/interceptors/workflow.spec.js +1 -1
- package/src/worker/loader/matchers.js +1 -1
- package/src/worker/loader/tools.js +1 -1
- package/src/worker/loader/tools.spec.js +1 -1
- package/src/worker/log_hooks.js +14 -0
- package/src/worker/log_hooks.spec.js +83 -2
- package/src/worker/sinks.js +7 -1
- package/src/worker/sinks.spec.js +203 -0
- package/src/activity_integration/context.d.ts +0 -23
- package/src/activity_integration/context.js +0 -18
- package/src/activity_integration/event_id_integration.spec.js +0 -52
- package/src/activity_integration/events.d.ts +0 -10
- package/src/activity_integration/events.js +0 -15
- package/src/activity_integration/index.d.ts +0 -9
- package/src/activity_integration/index.js +0 -3
- package/src/activity_integration/tracing.d.ts +0 -40
- package/src/activity_integration/tracing.js +0 -48
- package/src/utils/index.d.ts +0 -180
- package/src/utils/index.js +0 -2
- package/src/utils/resolve_invocation_dir.js +0 -34
- package/src/utils/utils.js +0 -334
- package/src/utils/utils.spec.js +0 -723
- /package/src/{internal_utils → helpers}/aggregations.js +0 -0
- /package/src/{internal_utils → helpers}/aggregations.spec.js +0 -0
- /package/src/{internal_utils → helpers}/errors.js +0 -0
- /package/src/{internal_utils → helpers}/errors.spec.js +0 -0
- /package/src/{internal_utils → helpers}/temporal_context.js +0 -0
- /package/src/{internal_utils → helpers}/temporal_context.spec.ts +0 -0
- /package/src/{internal_utils → helpers}/trace_info.js +0 -0
- /package/src/{internal_utils → helpers}/trace_info.spec.js +0 -0
- /package/src/{internal_utils → helpers}/workflow_context.js +0 -0
- /package/src/{internal_utils → helpers}/workflow_context.spec.js +0 -0
package/src/worker/index.spec.js
CHANGED
|
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
catalogJobInstance,
|
|
5
|
+
bindGlobalFunctionsMock,
|
|
5
6
|
configValues,
|
|
6
7
|
connectionMonitorInstance,
|
|
7
8
|
createCatalogMock,
|
|
@@ -44,7 +45,9 @@ const {
|
|
|
44
45
|
maxCachedWorkflows: 1000,
|
|
45
46
|
maxConcurrentActivityTaskPolls: 5,
|
|
46
47
|
maxConcurrentWorkflowTaskPolls: 5,
|
|
47
|
-
processFailureShutdownDelay: 0
|
|
48
|
+
processFailureShutdownDelay: 0,
|
|
49
|
+
shutdownForceTime: undefined,
|
|
50
|
+
shutdownGraceTime: undefined
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
const connectionMonitorInstance = {
|
|
@@ -96,6 +99,7 @@ const {
|
|
|
96
99
|
|
|
97
100
|
return {
|
|
98
101
|
catalogJobInstance,
|
|
102
|
+
bindGlobalFunctionsMock: vi.fn(),
|
|
99
103
|
configValues,
|
|
100
104
|
connectionMonitorInstance,
|
|
101
105
|
createCatalogMock: vi.fn().mockReturnValue( { workflows: [], activities: {} } ),
|
|
@@ -143,6 +147,7 @@ vi.mock( './interceptors/index.js', () => ( { initInterceptors: initInterceptors
|
|
|
143
147
|
vi.mock( './proxy.js', () => ( { bootstrapFetchProxy: vi.fn() } ) );
|
|
144
148
|
vi.mock( './telemetry.js', () => ( { setupTelemetry: setupTelemetryMock } ) );
|
|
145
149
|
vi.mock( './interruption.js', () => ( { setupInterruptionHandler: setupInterruptionHandlerMock } ) );
|
|
150
|
+
vi.mock( './global_functions.js', () => ( { bindGlobalFunctions: bindGlobalFunctionsMock } ) );
|
|
146
151
|
vi.mock( './connection_monitor.js', () => ( {
|
|
147
152
|
TemporalConnectionMonitor: vi.fn( function () {
|
|
148
153
|
return connectionMonitorInstance;
|
|
@@ -180,6 +185,8 @@ describe( 'worker/index', () => {
|
|
|
180
185
|
resetPromises();
|
|
181
186
|
configValues.apiKey = undefined;
|
|
182
187
|
configValues.grpcProxy = undefined;
|
|
188
|
+
configValues.shutdownForceTime = undefined;
|
|
189
|
+
configValues.shutdownGraceTime = undefined;
|
|
183
190
|
catalogJobInstance.error = null;
|
|
184
191
|
catalogJobInstance.errorCb = null;
|
|
185
192
|
catalogJobInstance.running = false;
|
|
@@ -218,6 +225,10 @@ describe( 'worker/index', () => {
|
|
|
218
225
|
apiKey: undefined,
|
|
219
226
|
proxy: undefined
|
|
220
227
|
} );
|
|
228
|
+
expect( bindGlobalFunctionsMock ).toHaveBeenCalledTimes( 1 );
|
|
229
|
+
expect( bindGlobalFunctionsMock.mock.invocationCallOrder[0] ).toBeLessThan(
|
|
230
|
+
NativeConnection.connect.mock.invocationCallOrder[0]
|
|
231
|
+
);
|
|
221
232
|
expect( TemporalConnectionMonitor ).toHaveBeenCalledWith( mockConnection );
|
|
222
233
|
expect( CatalogJob ).toHaveBeenCalledWith( {
|
|
223
234
|
connection: mockConnection,
|
|
@@ -237,6 +248,8 @@ describe( 'worker/index', () => {
|
|
|
237
248
|
maxConcurrentActivityTaskPolls: configValues.maxConcurrentActivityTaskPolls,
|
|
238
249
|
maxConcurrentWorkflowTaskPolls: configValues.maxConcurrentWorkflowTaskPolls
|
|
239
250
|
} ) );
|
|
251
|
+
expect( Worker.create.mock.calls[0][0] ).not.toHaveProperty( 'shutdownForceTime' );
|
|
252
|
+
expect( Worker.create.mock.calls[0][0] ).not.toHaveProperty( 'shutdownGraceTime' );
|
|
240
253
|
expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [] } );
|
|
241
254
|
expect( setupTelemetryMock ).toHaveBeenCalledWith( { worker: mockWorker } );
|
|
242
255
|
expect( setupInterruptionHandlerMock ).toHaveBeenCalledWith( expect.any( Function ) );
|
|
@@ -264,6 +277,21 @@ describe( 'worker/index', () => {
|
|
|
264
277
|
await settleWorker();
|
|
265
278
|
} );
|
|
266
279
|
|
|
280
|
+
it( 'passes configured shutdown durations to the worker', async () => {
|
|
281
|
+
configValues.shutdownForceTime = '30s';
|
|
282
|
+
configValues.shutdownGraceTime = '10s';
|
|
283
|
+
const { Worker } = await import( '@temporalio/worker' );
|
|
284
|
+
|
|
285
|
+
await importWorker();
|
|
286
|
+
|
|
287
|
+
await vi.waitFor( () => expect( Worker.create ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
288
|
+
shutdownForceTime: '30s',
|
|
289
|
+
shutdownGraceTime: '10s'
|
|
290
|
+
} ) ) );
|
|
291
|
+
|
|
292
|
+
await settleWorker();
|
|
293
|
+
} );
|
|
294
|
+
|
|
267
295
|
it( 'runs graceful shutdown when interrupted', async () => {
|
|
268
296
|
await importWorker();
|
|
269
297
|
|
|
@@ -5,8 +5,8 @@ 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
7
|
import { messageBus } from '#bus';
|
|
8
|
-
import { aggregateAttributes } from '#
|
|
9
|
-
import { buildApplicationFailureWithDetails } from '#
|
|
8
|
+
import { aggregateAttributes } from '#helpers/aggregations';
|
|
9
|
+
import { buildApplicationFailureWithDetails } from '#helpers/errors';
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
This interceptor wraps every activity execution with cross-cutting concerns:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
|
|
2
2
|
import { workflowInfo, proxySinks, ContinueAsNew, isCancellation } from '@temporalio/workflow';
|
|
3
3
|
import { memoToHeaders } from './headers.js';
|
|
4
|
-
import { deepMerge } from '#
|
|
5
|
-
import { buildApplicationFailureWithDetails } from '#
|
|
4
|
+
import { deepMerge } from '#helpers/object';
|
|
5
|
+
import { buildApplicationFailureWithDetails } from '#helpers/errors';
|
|
6
6
|
import { METADATA_ACCESS_SYMBOL, WorkflowSpecialOutput } from '#consts';
|
|
7
|
-
import { createWorkflowDetails } from '#
|
|
7
|
+
import { createWorkflowDetails } from '#helpers/temporal_context';
|
|
8
8
|
|
|
9
9
|
// this is a dynamic generated file with activity configs overwrites
|
|
10
10
|
import stepOptions from '../temp/__activity_options.js';
|
|
@@ -54,7 +54,7 @@ const memoToHeadersMock = vi.fn( memo => ( memo ? { ...memo, __asHeaders: true }
|
|
|
54
54
|
vi.mock( './headers.js', () => ( { memoToHeaders: ( ...args ) => memoToHeadersMock( ...args ) } ) );
|
|
55
55
|
|
|
56
56
|
const deepMergeMock = vi.fn( ( a, b ) => ( { ...( a || {} ), ...( b || {} ) } ) );
|
|
57
|
-
vi.mock( '#
|
|
57
|
+
vi.mock( '#helpers/object', () => ( { deepMerge: ( ...args ) => deepMergeMock( ...args ) } ) );
|
|
58
58
|
|
|
59
59
|
const stepOptionsDefault = {};
|
|
60
60
|
vi.mock( '../temp/__activity_options.js', () => ( { default: stepOptionsDefault } ) );
|
|
@@ -252,7 +252,7 @@ export const hashSourceCode = async rootDir => {
|
|
|
252
252
|
try {
|
|
253
253
|
const { hash } = await hashElement( rootDir, {
|
|
254
254
|
folders: {
|
|
255
|
-
exclude: [ '.*', 'node_modules', 'test_coverage', 'vendor', 'test', 'logs', '
|
|
255
|
+
exclude: [ '.*', 'node_modules', 'test_coverage', 'vendor', 'test', 'logs', 'temp' ],
|
|
256
256
|
ignoreRootName: true
|
|
257
257
|
},
|
|
258
258
|
files: {
|
|
@@ -574,7 +574,7 @@ describe( 'hashSourceCode', () => {
|
|
|
574
574
|
|
|
575
575
|
// The cruft tree is identical source plus large excluded artifacts that
|
|
576
576
|
// boot must not walk: local trace dumps under logs/ and build output under dist/.
|
|
577
|
-
for ( const excluded of [ 'logs', 'logs/runs', '
|
|
577
|
+
for ( const excluded of [ 'logs', 'logs/runs', 'node_modules' ] ) {
|
|
578
578
|
const dir = join( withCruft, excluded );
|
|
579
579
|
mkdirSync( dir, { recursive: true } );
|
|
580
580
|
writeFileSync( join( dir, 'dump.json' ), JSON.stringify( { blob: 'x'.repeat( 50_000 ) } ) );
|
package/src/worker/log_hooks.js
CHANGED
|
@@ -47,6 +47,13 @@ messageBus.on( BusEventType.ACTIVITY_ERROR, ( { activityInfo, outputActivityKind
|
|
|
47
47
|
} )
|
|
48
48
|
);
|
|
49
49
|
|
|
50
|
+
messageBus.on( BusEventType.ACTIVITY_LOG, ( { level, message, metadata, activityInfo } ) =>
|
|
51
|
+
activityLog[level]( message, {
|
|
52
|
+
...metadata ?? {},
|
|
53
|
+
...serializedActivityFields( activityInfo )
|
|
54
|
+
} )
|
|
55
|
+
);
|
|
56
|
+
|
|
50
57
|
/*
|
|
51
58
|
╔═════════════════╗
|
|
52
59
|
║ Workflow events ║
|
|
@@ -82,3 +89,10 @@ messageBus.on( BusEventType.WORKFLOW_ERROR, ( { workflowDetails, error } ) =>
|
|
|
82
89
|
error: error.message
|
|
83
90
|
} )
|
|
84
91
|
);
|
|
92
|
+
|
|
93
|
+
messageBus.on( BusEventType.WORKFLOW_LOG, ( { level, message, metadata, workflowDetails } ) =>
|
|
94
|
+
workflowLog[level]( message, {
|
|
95
|
+
...metadata ?? {},
|
|
96
|
+
...serializeWorkflowFields( workflowDetails )
|
|
97
|
+
} )
|
|
98
|
+
);
|
|
@@ -6,8 +6,17 @@ import {
|
|
|
6
6
|
WORKFLOW_CATALOG
|
|
7
7
|
} from '#consts';
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const createLoggerMock = vi.hoisted( () => () => ( {
|
|
10
|
+
error: vi.fn(),
|
|
11
|
+
warn: vi.fn(),
|
|
12
|
+
info: vi.fn(),
|
|
13
|
+
http: vi.fn(),
|
|
14
|
+
verbose: vi.fn(),
|
|
15
|
+
debug: vi.fn(),
|
|
16
|
+
silly: vi.fn()
|
|
17
|
+
} ) );
|
|
18
|
+
const activityLogMock = vi.hoisted( () => createLoggerMock() );
|
|
19
|
+
const workflowLogMock = vi.hoisted( () => createLoggerMock() );
|
|
11
20
|
const createChildLoggerMock = vi.hoisted( () =>
|
|
12
21
|
vi.fn( name => ( name === 'Activity' ? activityLogMock : workflowLogMock ) )
|
|
13
22
|
);
|
|
@@ -125,6 +134,44 @@ describe( 'log_hooks', () => {
|
|
|
125
134
|
|
|
126
135
|
expect( activityLogMock.error ).not.toHaveBeenCalled();
|
|
127
136
|
} );
|
|
137
|
+
|
|
138
|
+
it( 'ACTIVITY_LOG logs dynamic levels with metadata and serialized activity fields', () => {
|
|
139
|
+
onHandlers[BusEventType.ACTIVITY_LOG]( {
|
|
140
|
+
activityInfo,
|
|
141
|
+
level: 'debug',
|
|
142
|
+
message: 'activity detail',
|
|
143
|
+
metadata: {
|
|
144
|
+
custom: 'value',
|
|
145
|
+
workflowId: 'metadata-workflow-id'
|
|
146
|
+
}
|
|
147
|
+
} );
|
|
148
|
+
|
|
149
|
+
expect( activityLogMock.debug ).toHaveBeenCalledTimes( 1 );
|
|
150
|
+
expect( activityLogMock.debug ).toHaveBeenCalledWith( 'activity detail', {
|
|
151
|
+
custom: 'value',
|
|
152
|
+
activityId: 'act-1',
|
|
153
|
+
activityType: 'myWorkflow#myStep',
|
|
154
|
+
workflowId: 'wf-1',
|
|
155
|
+
workflowType: 'myWorkflow',
|
|
156
|
+
runId: 'run-1'
|
|
157
|
+
} );
|
|
158
|
+
} );
|
|
159
|
+
|
|
160
|
+
it( 'ACTIVITY_LOG accepts omitted metadata', () => {
|
|
161
|
+
onHandlers[BusEventType.ACTIVITY_LOG]( {
|
|
162
|
+
activityInfo,
|
|
163
|
+
level: 'info',
|
|
164
|
+
message: 'activity detail'
|
|
165
|
+
} );
|
|
166
|
+
|
|
167
|
+
expect( activityLogMock.info ).toHaveBeenCalledWith( 'activity detail', {
|
|
168
|
+
activityId: 'act-1',
|
|
169
|
+
activityType: 'myWorkflow#myStep',
|
|
170
|
+
workflowId: 'wf-1',
|
|
171
|
+
workflowType: 'myWorkflow',
|
|
172
|
+
runId: 'run-1'
|
|
173
|
+
} );
|
|
174
|
+
} );
|
|
128
175
|
} );
|
|
129
176
|
|
|
130
177
|
describe( 'workflow events', () => {
|
|
@@ -210,5 +257,39 @@ describe( 'log_hooks', () => {
|
|
|
210
257
|
|
|
211
258
|
expect( workflowLogMock.error ).not.toHaveBeenCalled();
|
|
212
259
|
} );
|
|
260
|
+
|
|
261
|
+
it( 'WORKFLOW_LOG logs dynamic levels with metadata and serialized workflow fields', () => {
|
|
262
|
+
onHandlers[BusEventType.WORKFLOW_LOG]( {
|
|
263
|
+
workflowDetails,
|
|
264
|
+
level: 'warn',
|
|
265
|
+
message: 'workflow detail',
|
|
266
|
+
metadata: {
|
|
267
|
+
custom: 'value',
|
|
268
|
+
runId: 'metadata-run-id'
|
|
269
|
+
}
|
|
270
|
+
} );
|
|
271
|
+
|
|
272
|
+
expect( workflowLogMock.warn ).toHaveBeenCalledTimes( 1 );
|
|
273
|
+
expect( workflowLogMock.warn ).toHaveBeenCalledWith( 'workflow detail', {
|
|
274
|
+
custom: 'value',
|
|
275
|
+
workflowId: 'wf-1',
|
|
276
|
+
workflowType: 'myWorkflow',
|
|
277
|
+
runId: 'run-1'
|
|
278
|
+
} );
|
|
279
|
+
} );
|
|
280
|
+
|
|
281
|
+
it( 'WORKFLOW_LOG accepts omitted metadata', () => {
|
|
282
|
+
onHandlers[BusEventType.WORKFLOW_LOG]( {
|
|
283
|
+
workflowDetails,
|
|
284
|
+
level: 'info',
|
|
285
|
+
message: 'workflow detail'
|
|
286
|
+
} );
|
|
287
|
+
|
|
288
|
+
expect( workflowLogMock.info ).toHaveBeenCalledWith( 'workflow detail', {
|
|
289
|
+
workflowId: 'wf-1',
|
|
290
|
+
workflowType: 'myWorkflow',
|
|
291
|
+
runId: 'run-1'
|
|
292
|
+
} );
|
|
293
|
+
} );
|
|
213
294
|
} );
|
|
214
295
|
} );
|
package/src/worker/sinks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BusEventType, ComponentType } from '#consts';
|
|
2
2
|
import * as Tracing from '#tracing';
|
|
3
3
|
import { messageBus } from '#bus';
|
|
4
|
-
import { createWorkflowDetails } from '#
|
|
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.
|
|
7
7
|
export const sinks = {
|
|
@@ -10,6 +10,12 @@ export const sinks = {
|
|
|
10
10
|
* Workflow lifecycle sinks
|
|
11
11
|
*/
|
|
12
12
|
workflow: {
|
|
13
|
+
log: {
|
|
14
|
+
fn: ( workflowInfo, { level, message, metadata } ) => {
|
|
15
|
+
messageBus.emit( BusEventType.WORKFLOW_LOG, { level, message, metadata, workflowDetails: createWorkflowDetails( workflowInfo ) } );
|
|
16
|
+
},
|
|
17
|
+
callDuringReplay: false
|
|
18
|
+
},
|
|
13
19
|
start: {
|
|
14
20
|
fn: ( workflowInfo, input ) => {
|
|
15
21
|
const { runId, workflowType, memo: { traceInfo }, parent } = workflowInfo;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { BusEventType, ComponentType } from '#consts';
|
|
3
|
+
|
|
4
|
+
const messageBusMock = vi.hoisted( () => ( {
|
|
5
|
+
emit: vi.fn()
|
|
6
|
+
} ) );
|
|
7
|
+
|
|
8
|
+
const addEventStartMock = vi.hoisted( () => vi.fn() );
|
|
9
|
+
const addEventEndMock = vi.hoisted( () => vi.fn() );
|
|
10
|
+
const addEventErrorMock = vi.hoisted( () => vi.fn() );
|
|
11
|
+
|
|
12
|
+
const createWorkflowDetailsMock = vi.hoisted( () => vi.fn( workflowInfo => ( {
|
|
13
|
+
workflowId: workflowInfo.workflowId,
|
|
14
|
+
workflowType: workflowInfo.workflowType,
|
|
15
|
+
runId: workflowInfo.runId
|
|
16
|
+
} ) ) );
|
|
17
|
+
|
|
18
|
+
vi.mock( '#bus', () => ( { messageBus: messageBusMock } ) );
|
|
19
|
+
vi.mock( '#tracing', () => ( {
|
|
20
|
+
addEventStart: addEventStartMock,
|
|
21
|
+
addEventEnd: addEventEndMock,
|
|
22
|
+
addEventError: addEventErrorMock
|
|
23
|
+
} ) );
|
|
24
|
+
vi.mock( '#helpers/temporal_context', () => ( {
|
|
25
|
+
createWorkflowDetails: createWorkflowDetailsMock
|
|
26
|
+
} ) );
|
|
27
|
+
|
|
28
|
+
describe( 'worker/sinks', () => {
|
|
29
|
+
const workflowInfo = {
|
|
30
|
+
workflowId: 'wf-1',
|
|
31
|
+
workflowType: 'myWorkflow',
|
|
32
|
+
runId: 'run-1',
|
|
33
|
+
memo: {
|
|
34
|
+
traceInfo: { traceId: 'trace-1' }
|
|
35
|
+
},
|
|
36
|
+
parent: {
|
|
37
|
+
runId: 'parent-run-1'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const workflowDetails = {
|
|
41
|
+
workflowId: 'wf-1',
|
|
42
|
+
workflowType: 'myWorkflow',
|
|
43
|
+
runId: 'run-1'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
beforeEach( () => {
|
|
47
|
+
vi.clearAllMocks();
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
it( 'disables replay calls for side-effecting sinks', async () => {
|
|
51
|
+
const { sinks } = await import( './sinks.js' );
|
|
52
|
+
|
|
53
|
+
expect( sinks.workflow.start.callDuringReplay ).toBe( false );
|
|
54
|
+
expect( sinks.workflow.end.callDuringReplay ).toBe( false );
|
|
55
|
+
expect( sinks.workflow.error.callDuringReplay ).toBe( false );
|
|
56
|
+
expect( sinks.trace.start.callDuringReplay ).toBe( false );
|
|
57
|
+
expect( sinks.trace.end.callDuringReplay ).toBe( false );
|
|
58
|
+
expect( sinks.trace.error.callDuringReplay ).toBe( false );
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
it( 'workflow.log emits workflow log events with metadata and workflow details', async () => {
|
|
62
|
+
const { sinks } = await import( './sinks.js' );
|
|
63
|
+
const metadata = { requestId: 'req-1' };
|
|
64
|
+
|
|
65
|
+
sinks.workflow.log.fn( workflowInfo, {
|
|
66
|
+
level: 'info',
|
|
67
|
+
message: 'workflow detail',
|
|
68
|
+
metadata
|
|
69
|
+
} );
|
|
70
|
+
|
|
71
|
+
expect( createWorkflowDetailsMock ).toHaveBeenCalledWith( workflowInfo );
|
|
72
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_LOG, {
|
|
73
|
+
level: 'info',
|
|
74
|
+
message: 'workflow detail',
|
|
75
|
+
metadata,
|
|
76
|
+
workflowDetails
|
|
77
|
+
} );
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
it( 'workflow.start emits workflow start events and trace start events', async () => {
|
|
81
|
+
const { sinks } = await import( './sinks.js' );
|
|
82
|
+
const input = { value: 'input' };
|
|
83
|
+
|
|
84
|
+
sinks.workflow.start.fn( workflowInfo, input );
|
|
85
|
+
|
|
86
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_START, { workflowDetails } );
|
|
87
|
+
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
88
|
+
id: 'run-1',
|
|
89
|
+
kind: ComponentType.WORKFLOW,
|
|
90
|
+
name: 'myWorkflow',
|
|
91
|
+
details: input,
|
|
92
|
+
parentId: 'parent-run-1',
|
|
93
|
+
traceInfo: { traceId: 'trace-1' }
|
|
94
|
+
} );
|
|
95
|
+
} );
|
|
96
|
+
|
|
97
|
+
it( 'workflow.start skips tracing when trace info is absent', async () => {
|
|
98
|
+
const { sinks } = await import( './sinks.js' );
|
|
99
|
+
|
|
100
|
+
sinks.workflow.start.fn( { ...workflowInfo, memo: {} }, { value: 'input' } );
|
|
101
|
+
|
|
102
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_START, { workflowDetails } );
|
|
103
|
+
expect( addEventStartMock ).not.toHaveBeenCalled();
|
|
104
|
+
} );
|
|
105
|
+
|
|
106
|
+
it( 'workflow.end emits workflow end events and trace end events', async () => {
|
|
107
|
+
const { sinks } = await import( './sinks.js' );
|
|
108
|
+
const output = { value: 'output' };
|
|
109
|
+
|
|
110
|
+
sinks.workflow.end.fn( workflowInfo, output );
|
|
111
|
+
|
|
112
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_END, { workflowDetails } );
|
|
113
|
+
expect( addEventEndMock ).toHaveBeenCalledWith( {
|
|
114
|
+
id: 'run-1',
|
|
115
|
+
details: output,
|
|
116
|
+
traceInfo: { traceId: 'trace-1' }
|
|
117
|
+
} );
|
|
118
|
+
} );
|
|
119
|
+
|
|
120
|
+
it( 'workflow.end skips tracing when trace info is absent', async () => {
|
|
121
|
+
const { sinks } = await import( './sinks.js' );
|
|
122
|
+
|
|
123
|
+
sinks.workflow.end.fn( { ...workflowInfo, memo: {} }, { value: 'output' } );
|
|
124
|
+
|
|
125
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_END, { workflowDetails } );
|
|
126
|
+
expect( addEventEndMock ).not.toHaveBeenCalled();
|
|
127
|
+
} );
|
|
128
|
+
|
|
129
|
+
it( 'workflow.error emits workflow error events and trace error events', async () => {
|
|
130
|
+
const { sinks } = await import( './sinks.js' );
|
|
131
|
+
const error = new Error( 'workflow failed' );
|
|
132
|
+
|
|
133
|
+
sinks.workflow.error.fn( workflowInfo, error );
|
|
134
|
+
|
|
135
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, { workflowDetails, error } );
|
|
136
|
+
expect( addEventErrorMock ).toHaveBeenCalledWith( {
|
|
137
|
+
id: 'run-1',
|
|
138
|
+
details: error,
|
|
139
|
+
traceInfo: { traceId: 'trace-1' }
|
|
140
|
+
} );
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
it( 'workflow.error skips tracing when trace info is absent', async () => {
|
|
144
|
+
const { sinks } = await import( './sinks.js' );
|
|
145
|
+
const error = new Error( 'workflow failed' );
|
|
146
|
+
|
|
147
|
+
sinks.workflow.error.fn( { ...workflowInfo, memo: {} }, error );
|
|
148
|
+
|
|
149
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, { workflowDetails, error } );
|
|
150
|
+
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
151
|
+
} );
|
|
152
|
+
|
|
153
|
+
it( 'trace.start records trace start events with workflow parent context', async () => {
|
|
154
|
+
const { sinks } = await import( './sinks.js' );
|
|
155
|
+
|
|
156
|
+
sinks.trace.start.fn( workflowInfo, {
|
|
157
|
+
id: 'step-1',
|
|
158
|
+
kind: ComponentType.STEP,
|
|
159
|
+
name: 'myStep',
|
|
160
|
+
details: { input: true }
|
|
161
|
+
} );
|
|
162
|
+
|
|
163
|
+
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
164
|
+
id: 'step-1',
|
|
165
|
+
kind: ComponentType.STEP,
|
|
166
|
+
name: 'myStep',
|
|
167
|
+
details: { input: true },
|
|
168
|
+
parentId: 'parent-run-1',
|
|
169
|
+
traceInfo: { traceId: 'trace-1' }
|
|
170
|
+
} );
|
|
171
|
+
} );
|
|
172
|
+
|
|
173
|
+
it( 'trace.end records trace end events', async () => {
|
|
174
|
+
const { sinks } = await import( './sinks.js' );
|
|
175
|
+
|
|
176
|
+
sinks.trace.end.fn( workflowInfo, {
|
|
177
|
+
id: 'step-1',
|
|
178
|
+
details: { output: true }
|
|
179
|
+
} );
|
|
180
|
+
|
|
181
|
+
expect( addEventEndMock ).toHaveBeenCalledWith( {
|
|
182
|
+
id: 'step-1',
|
|
183
|
+
details: { output: true },
|
|
184
|
+
traceInfo: { traceId: 'trace-1' }
|
|
185
|
+
} );
|
|
186
|
+
} );
|
|
187
|
+
|
|
188
|
+
it( 'trace.error records trace error events', async () => {
|
|
189
|
+
const { sinks } = await import( './sinks.js' );
|
|
190
|
+
const error = new Error( 'step failed' );
|
|
191
|
+
|
|
192
|
+
sinks.trace.error.fn( workflowInfo, {
|
|
193
|
+
id: 'step-1',
|
|
194
|
+
details: error
|
|
195
|
+
} );
|
|
196
|
+
|
|
197
|
+
expect( addEventErrorMock ).toHaveBeenCalledWith( {
|
|
198
|
+
id: 'step-1',
|
|
199
|
+
details: error,
|
|
200
|
+
traceInfo: { traceId: 'trace-1' }
|
|
201
|
+
} );
|
|
202
|
+
} );
|
|
203
|
+
} );
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Info } from '@temporalio/activity';
|
|
2
|
-
/**
|
|
3
|
-
* Context returned by {@link getContext} when running inside a Temporal Activity (step or evaluator).
|
|
4
|
-
*/
|
|
5
|
-
export type Context = {
|
|
6
|
-
/** Temporal info about the current activity */
|
|
7
|
-
activityInfo: Info,
|
|
8
|
-
/** Path of the workflow file */
|
|
9
|
-
workflowFilename: string
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Returns information about the current Temporal execution.
|
|
14
|
-
*
|
|
15
|
-
* Only available when called from within a step or evaluator (Temporal Activities) running in the Temporal runtime.
|
|
16
|
-
*
|
|
17
|
-
* @remarks
|
|
18
|
-
* - Returns `null` when not called inside a Temporal Activity (steps/evaluators);
|
|
19
|
-
* - Returns `null` when not called from within a running Temporal worker, like in unit tests environment;
|
|
20
|
-
*
|
|
21
|
-
* @returns The workflow context, or `null` if unavailable or incomplete.
|
|
22
|
-
*/
|
|
23
|
-
export declare function getExecutionContext(): Context | null;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Storage } from '#async_storage';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns information trapped on AsyncStorage about the workflow invoking an activity
|
|
5
|
-
*
|
|
6
|
-
* @returns {object}
|
|
7
|
-
*/
|
|
8
|
-
export const getExecutionContext = () => {
|
|
9
|
-
const ctx = Storage.load();
|
|
10
|
-
if ( !ctx ) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
workflowFilename: ctx.workflowFilename,
|
|
16
|
-
activityInfo: ctx.activityInfo
|
|
17
|
-
};
|
|
18
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { messageBus } from '#bus';
|
|
3
|
-
import { emitEvent } from './events.js';
|
|
4
|
-
|
|
5
|
-
const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
6
|
-
|
|
7
|
-
describe( 'eventId integration', () => {
|
|
8
|
-
beforeEach( () => {
|
|
9
|
-
messageBus.removeAllListeners();
|
|
10
|
-
} );
|
|
11
|
-
|
|
12
|
-
it( 'stamps a UUID v4 eventId on every emit (end-to-end via messageBus)', () => {
|
|
13
|
-
const handler = vi.fn();
|
|
14
|
-
messageBus.on( 'external:cost:llm:request', handler );
|
|
15
|
-
|
|
16
|
-
emitEvent( 'cost:llm:request', { modelId: 'gpt-4o' } );
|
|
17
|
-
|
|
18
|
-
expect( handler ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
19
|
-
eventId: expect.stringMatching( UUID_V4_REGEX ),
|
|
20
|
-
modelId: 'gpt-4o'
|
|
21
|
-
} ) );
|
|
22
|
-
} );
|
|
23
|
-
|
|
24
|
-
it( 'cost:http:request and http:request for the same fetch get distinct eventIds', () => {
|
|
25
|
-
const costHandler = vi.fn();
|
|
26
|
-
const reqHandler = vi.fn();
|
|
27
|
-
messageBus.on( 'external:cost:http:request', costHandler );
|
|
28
|
-
messageBus.on( 'external:http:request', reqHandler );
|
|
29
|
-
|
|
30
|
-
const sharedRequestId = 'req-xyz';
|
|
31
|
-
emitEvent( 'cost:http:request', { requestId: sharedRequestId, url: 'https://x.test', cost: 1 } );
|
|
32
|
-
emitEvent( 'http:request', { requestId: sharedRequestId, url: 'https://x.test', status: 200 } );
|
|
33
|
-
|
|
34
|
-
const costEventId = costHandler.mock.calls[0][0].eventId;
|
|
35
|
-
const reqEventId = reqHandler.mock.calls[0][0].eventId;
|
|
36
|
-
expect( costEventId ).toMatch( UUID_V4_REGEX );
|
|
37
|
-
expect( reqEventId ).toMatch( UUID_V4_REGEX );
|
|
38
|
-
expect( costEventId ).not.toBe( reqEventId );
|
|
39
|
-
} );
|
|
40
|
-
|
|
41
|
-
it( 'honors a caller-supplied eventId end-to-end', () => {
|
|
42
|
-
const handler = vi.fn();
|
|
43
|
-
messageBus.on( 'external:custom:event', handler );
|
|
44
|
-
|
|
45
|
-
emitEvent( 'custom:event', { eventId: 'fixed-id-123', payload: 'hi' } );
|
|
46
|
-
|
|
47
|
-
expect( handler ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
48
|
-
eventId: 'fixed-id-123',
|
|
49
|
-
payload: 'hi'
|
|
50
|
-
} ) );
|
|
51
|
-
} );
|
|
52
|
-
} );
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Emits a custom event on the in-process message bus.
|
|
3
|
-
*
|
|
4
|
-
* When called inside an Output activity context, the framework automatically
|
|
5
|
-
* attaches `activityInfo`, `workflowDetails`, and `outputActivityKind` onto the emitted payload.
|
|
6
|
-
*
|
|
7
|
-
* @param eventName - The name of the event to emit
|
|
8
|
-
* @param payload - An optional payload to send to the event
|
|
9
|
-
*/
|
|
10
|
-
export declare function emitEvent( eventName: string, payload?: unknown ): void;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { messageBus } from '#bus';
|
|
2
|
-
import { Storage } from '#async_storage';
|
|
3
|
-
|
|
4
|
-
export const emitEvent = ( eventName, payload ) => {
|
|
5
|
-
const ctx = Storage.load();
|
|
6
|
-
|
|
7
|
-
messageBus.emit( `external:${eventName}`, {
|
|
8
|
-
...payload ?? {},
|
|
9
|
-
...( ctx && {
|
|
10
|
-
activityInfo: ctx.activityInfo,
|
|
11
|
-
workflowDetails: ctx.workflowDetails,
|
|
12
|
-
outputActivityKind: ctx.outputActivityKind
|
|
13
|
-
} )
|
|
14
|
-
} );
|
|
15
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* > [!WARNING]
|
|
3
|
-
* > **Internal use only.** Not part of the public API; may change without notice.
|
|
4
|
-
*
|
|
5
|
-
* @packageDocumentation
|
|
6
|
-
*/
|
|
7
|
-
export { getExecutionContext } from './context';
|
|
8
|
-
export { emitEvent } from './events';
|
|
9
|
-
export * as Tracing from './tracing';
|