@outputai/core 0.13.1-next.38c895c.0 → 0.13.1-next.41b143a.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
CHANGED
|
@@ -40,10 +40,13 @@ export class ActivityExecutionInterceptor {
|
|
|
40
40
|
|
|
41
41
|
async execute( input, next ) {
|
|
42
42
|
const activityInfo = activityInfoFn();
|
|
43
|
-
const { workflowExecution: { runId }, activityId, activityType, workflowType } = activityInfo;
|
|
43
|
+
const { workflowExecution: { runId }, activityId, attempt, activityType, workflowType } = activityInfo;
|
|
44
44
|
const { traceInfo, workflowDetails } = headersToObject( input.headers );
|
|
45
45
|
const outputActivityKind = this.activityKindMap.get( activityType );
|
|
46
46
|
const workflowFilename = this.workflowsPathMap.get( workflowType );
|
|
47
|
+
// Retries reuse the same activityId, so the attempt is appended to keep each attempt a discrete trace entry.
|
|
48
|
+
// Also prefix with the runId to avoid conflict with child workflows.
|
|
49
|
+
const traceId = `${runId}:${activityId}:${attempt}`;
|
|
47
50
|
|
|
48
51
|
if ( !outputActivityKind ) {
|
|
49
52
|
throw new Error( `Activity interceptor: activity "${activityType}" was not registered.` );
|
|
@@ -58,7 +61,7 @@ export class ActivityExecutionInterceptor {
|
|
|
58
61
|
|
|
59
62
|
// Adds context accessible information
|
|
60
63
|
const storageContext = {
|
|
61
|
-
parentId:
|
|
64
|
+
parentId: traceId,
|
|
62
65
|
outputActivityKind,
|
|
63
66
|
activityInfo,
|
|
64
67
|
workflowDetails,
|
|
@@ -67,7 +70,7 @@ export class ActivityExecutionInterceptor {
|
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
mainEventBus.emit( BusEventType.ACTIVITY_START, { activityInfo, workflowDetails, outputActivityKind } );
|
|
70
|
-
Tracing.addEventStart( { id:
|
|
73
|
+
Tracing.addEventStart( { id: traceId, name: activityType, kind: outputActivityKind, parentId: runId, details: input.args[0], traceInfo } );
|
|
71
74
|
|
|
72
75
|
try {
|
|
73
76
|
// Sends heartbeat to communicate that activity is still alive
|
|
@@ -76,20 +79,20 @@ export class ActivityExecutionInterceptor {
|
|
|
76
79
|
const output = await Storage.runWithContext( async _ => next( input ), storageContext );
|
|
77
80
|
|
|
78
81
|
mainEventBus.emit( BusEventType.ACTIVITY_END, { activityInfo, workflowDetails, outputActivityKind } );
|
|
79
|
-
Tracing.addEventEnd( { id:
|
|
82
|
+
Tracing.addEventEnd( { id: traceId, details: output, traceInfo } );
|
|
80
83
|
|
|
81
84
|
return output;
|
|
82
85
|
|
|
83
86
|
} catch ( error ) {
|
|
84
87
|
// Record async completion handoff as a trace end without emitting a bus event.
|
|
85
88
|
if ( error instanceof CompleteAsyncError ) {
|
|
86
|
-
Tracing.addEventEnd( { id:
|
|
89
|
+
Tracing.addEventEnd( { id: traceId, details: ActivitySpecialOutput.ASYNC_HANDOFF, traceInfo } );
|
|
87
90
|
throw error;
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
const unwrappedError = error instanceof TransparentFatalError ? error.cause : error;
|
|
91
94
|
|
|
92
|
-
Tracing.addEventError( { id:
|
|
95
|
+
Tracing.addEventError( { id: traceId, details: unwrappedError, traceInfo } );
|
|
93
96
|
mainEventBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, workflowDetails, outputActivityKind, error: unwrappedError } );
|
|
94
97
|
|
|
95
98
|
// Native Temporal errors are just re-thrown
|
|
@@ -8,6 +8,7 @@ const runWithContextMock = vi.hoisted( () => vi.fn().mockImplementation( async f
|
|
|
8
8
|
const activityInfoMock = vi.hoisted( () => ( {
|
|
9
9
|
workflowExecution: { workflowId: 'wf-1', runId: 'run-1' },
|
|
10
10
|
activityId: 'act-1',
|
|
11
|
+
attempt: 1,
|
|
11
12
|
activityType: 'myWorkflow#myStep',
|
|
12
13
|
workflowType: 'myWorkflow'
|
|
13
14
|
} ) );
|
|
@@ -87,6 +88,8 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
87
88
|
beforeEach( () => {
|
|
88
89
|
vi.clearAllMocks();
|
|
89
90
|
activityInfoMock.workflowType = 'myWorkflow';
|
|
91
|
+
activityInfoMock.workflowExecution.runId = 'run-1';
|
|
92
|
+
activityInfoMock.attempt = 1;
|
|
90
93
|
delete activityInfoMock.retryPolicy;
|
|
91
94
|
vi.useFakeTimers();
|
|
92
95
|
// Default: heartbeat enabled with 50ms interval for fast tests
|
|
@@ -114,19 +117,19 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
114
117
|
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
115
118
|
);
|
|
116
119
|
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
117
|
-
id: 'act-1',
|
|
120
|
+
id: 'run-1:act-1:1',
|
|
118
121
|
name: 'myWorkflow#myStep',
|
|
119
122
|
kind: 'step',
|
|
120
123
|
parentId: 'run-1',
|
|
121
124
|
details: { someInput: 'data' },
|
|
122
125
|
traceInfo: traceInfoMock
|
|
123
126
|
} );
|
|
124
|
-
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'act-1', details: { result: 'ok' }, traceInfo: traceInfoMock } );
|
|
127
|
+
expect( addEventEndMock ).toHaveBeenCalledWith( { id: 'run-1:act-1:1', details: { result: 'ok' }, traceInfo: traceInfoMock } );
|
|
125
128
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
126
129
|
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
127
130
|
expect.any( Function ),
|
|
128
131
|
expect.objectContaining( {
|
|
129
|
-
parentId: 'act-1',
|
|
132
|
+
parentId: 'run-1:act-1:1',
|
|
130
133
|
activityInfo: activityInfoMock,
|
|
131
134
|
workflowDetails: workflowDetailsMock,
|
|
132
135
|
outputActivityKind: 'step',
|
|
@@ -136,6 +139,37 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
136
139
|
);
|
|
137
140
|
} );
|
|
138
141
|
|
|
142
|
+
it( 'scopes trace ids by attempt so retries do not reuse the same id', async () => {
|
|
143
|
+
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
144
|
+
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
145
|
+
activityInfoMock.attempt = 3;
|
|
146
|
+
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
147
|
+
|
|
148
|
+
await expect( interceptor.execute( makeInput(), next ) ).resolves.toEqual( { result: 'ok' } );
|
|
149
|
+
expect( addEventStartMock ).toHaveBeenCalledWith( expect.objectContaining( { id: 'run-1:act-1:3', parentId: 'run-1' } ) );
|
|
150
|
+
expect( addEventEndMock ).toHaveBeenCalledWith( expect.objectContaining( { id: 'run-1:act-1:3' } ) );
|
|
151
|
+
expect( runWithContextMock ).toHaveBeenCalledWith(
|
|
152
|
+
expect.any( Function ),
|
|
153
|
+
expect.objectContaining( { parentId: 'run-1:act-1:3' } )
|
|
154
|
+
);
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
it( 'scopes trace ids by run so a child workflow reusing an activityId gets a distinct id', async () => {
|
|
158
|
+
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
159
|
+
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
160
|
+
const next = vi.fn().mockResolvedValue( { result: 'ok' } );
|
|
161
|
+
|
|
162
|
+
// Temporal numbers activityIds per run, so a parent and its child both reach the same 'act-1' on attempt 1.
|
|
163
|
+
await interceptor.execute( makeInput(), next );
|
|
164
|
+
activityInfoMock.workflowExecution.runId = 'run-2';
|
|
165
|
+
await interceptor.execute( makeInput(), next );
|
|
166
|
+
|
|
167
|
+
expect( addEventStartMock.mock.calls.map( ( [ { id, parentId } ] ) => ( { id, parentId } ) ) ).toEqual( [
|
|
168
|
+
{ id: 'run-1:act-1:1', parentId: 'run-1' },
|
|
169
|
+
{ id: 'run-2:act-1:1', parentId: 'run-2' }
|
|
170
|
+
] );
|
|
171
|
+
} );
|
|
172
|
+
|
|
139
173
|
it( 'wraps native errors with serialized details', async () => {
|
|
140
174
|
const { ActivityExecutionInterceptor } = await import( './activity.js' );
|
|
141
175
|
const interceptor = new ActivityExecutionInterceptor( { activities: makeActivities(), workflows: makeWorkflows() } );
|
|
@@ -210,7 +244,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
210
244
|
}
|
|
211
245
|
} ]
|
|
212
246
|
} );
|
|
213
|
-
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'act-1', details: cause, traceInfo: traceInfoMock } );
|
|
247
|
+
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'run-1:act-1:1', details: cause, traceInfo: traceInfoMock } );
|
|
214
248
|
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
215
249
|
activityInfo: activityInfoMock,
|
|
216
250
|
workflowDetails: workflowDetailsMock,
|
|
@@ -226,7 +260,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
226
260
|
const next = vi.fn().mockRejectedValue( error );
|
|
227
261
|
|
|
228
262
|
await expect( interceptor.execute( makeInput(), next ) ).rejects.toBe( error );
|
|
229
|
-
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'act-1', details: error, traceInfo: traceInfoMock } );
|
|
263
|
+
expect( addEventErrorMock ).toHaveBeenCalledWith( { id: 'run-1:act-1:1', details: error, traceInfo: traceInfoMock } );
|
|
230
264
|
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
231
265
|
activityInfo: activityInfoMock,
|
|
232
266
|
workflowDetails: workflowDetailsMock,
|
|
@@ -245,7 +279,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
245
279
|
expect( mainEventBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_START, expect.any( Object ) );
|
|
246
280
|
expect( mainEventBusEmitMock ).toHaveBeenCalledTimes( 1 );
|
|
247
281
|
expect( addEventEndMock ).toHaveBeenCalledWith( {
|
|
248
|
-
id: 'act-1',
|
|
282
|
+
id: 'run-1:act-1:1',
|
|
249
283
|
details: ActivitySpecialOutput.ASYNC_HANDOFF,
|
|
250
284
|
traceInfo: traceInfoMock
|
|
251
285
|
} );
|
package/src/worker/sinks.js
CHANGED
|
@@ -65,7 +65,7 @@ export const sinks = {
|
|
|
65
65
|
trace: {
|
|
66
66
|
start: {
|
|
67
67
|
fn: ( workflowInfo, { id, name, kind, details } ) =>
|
|
68
|
-
Tracing.addEventStart( { id, kind, name, details, parentId: workflowInfo.
|
|
68
|
+
Tracing.addEventStart( { id, kind, name, details, parentId: workflowInfo.runId, traceInfo: workflowInfo.memo.traceInfo } ),
|
|
69
69
|
callDuringReplay: false
|
|
70
70
|
},
|
|
71
71
|
|
package/src/worker/sinks.spec.js
CHANGED
|
@@ -167,7 +167,7 @@ describe( 'worker/sinks', () => {
|
|
|
167
167
|
expect( addEventErrorMock ).not.toHaveBeenCalled();
|
|
168
168
|
} );
|
|
169
169
|
|
|
170
|
-
it( 'trace.start
|
|
170
|
+
it( 'trace.start parents trace start events on the current workflow, not its parent workflow', async () => {
|
|
171
171
|
const { sinks } = await import( './sinks.js' );
|
|
172
172
|
|
|
173
173
|
sinks.trace.start.fn( workflowInfo, {
|
|
@@ -182,7 +182,7 @@ describe( 'worker/sinks', () => {
|
|
|
182
182
|
kind: ComponentType.STEP,
|
|
183
183
|
name: 'myStep',
|
|
184
184
|
details: { input: true },
|
|
185
|
-
parentId: '
|
|
185
|
+
parentId: 'run-1',
|
|
186
186
|
traceInfo: { traceId: 'trace-1' }
|
|
187
187
|
} );
|
|
188
188
|
} );
|