@outputai/core 0.7.1-next.de30052.0 → 0.7.1-next.ed233ce.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/bin/worker.sh +6 -0
- package/package.json +1 -1
- package/src/consts.js +0 -4
- package/src/errors.js +6 -2
- package/src/interface/evaluator.js +7 -20
- package/src/interface/evaluator.spec.js +117 -1
- package/src/interface/step.js +8 -9
- package/src/interface/step.spec.js +124 -0
- package/src/interface/validations/index.js +108 -0
- package/src/interface/validations/index.spec.js +182 -0
- package/src/interface/validations/schemas.js +113 -0
- package/src/interface/validations/schemas.spec.js +209 -0
- package/src/interface/webhook.js +1 -1
- package/src/interface/webhook.spec.js +1 -1
- package/src/interface/workflow.d.ts +10 -9
- package/src/interface/workflow.js +76 -164
- package/src/interface/workflow.spec.js +637 -521
- package/src/interface/workflow_activity_options.js +16 -0
- package/src/interface/workflow_utils.js +1 -1
- package/src/interface/zod_integration.spec.js +2 -2
- package/src/internal_utils/aggregations.js +0 -10
- package/src/internal_utils/aggregations.spec.js +1 -48
- package/src/internal_utils/errors.js +14 -8
- package/src/internal_utils/errors.spec.js +73 -27
- package/src/utils/index.d.ts +19 -0
- package/src/utils/utils.js +53 -0
- package/src/utils/utils.spec.js +105 -1
- package/src/worker/bundle.js +26 -0
- package/src/worker/bundle.spec.js +53 -0
- package/src/worker/bundler_options.js +1 -1
- package/src/worker/bundler_options.spec.js +1 -1
- package/src/worker/catalog_workflow/catalog_job.js +148 -0
- package/src/worker/catalog_workflow/catalog_job.spec.js +232 -0
- package/src/worker/check.js +24 -0
- package/src/worker/connection_monitor.js +112 -0
- package/src/worker/connection_monitor.spec.js +199 -0
- package/src/worker/index.js +146 -41
- package/src/worker/index.spec.js +281 -109
- package/src/worker/interceptors/activity.js +7 -24
- package/src/worker/interceptors/activity.spec.js +97 -66
- package/src/worker/interceptors/index.js +4 -7
- package/src/worker/interceptors/modules.js +15 -0
- package/src/worker/interceptors/workflow.js +6 -8
- package/src/worker/interceptors/workflow.spec.js +49 -42
- package/src/worker/interruption.js +33 -0
- package/src/worker/interruption.spec.js +86 -0
- package/src/worker/loader/activities.js +75 -0
- package/src/worker/loader/activities.spec.js +213 -0
- package/src/worker/loader/hooks.js +28 -0
- package/src/worker/loader/hooks.spec.js +64 -0
- package/src/worker/loader/matchers.js +46 -0
- package/src/worker/loader/matchers.spec.js +140 -0
- package/src/worker/{loader_tools.js → loader/tools.js} +19 -67
- package/src/worker/{loader_tools.spec.js → loader/tools.spec.js} +53 -85
- package/src/worker/loader/workflows.js +82 -0
- package/src/worker/loader/workflows.spec.js +256 -0
- package/src/worker/{setup_telemetry.js → telemetry.js} +9 -4
- package/src/worker/{setup_telemetry.spec.js → telemetry.spec.js} +3 -3
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
- package/src/interface/validations/runtime.js +0 -20
- package/src/interface/validations/runtime.spec.js +0 -29
- package/src/interface/validations/schema_utils.js +0 -8
- package/src/interface/validations/schema_utils.spec.js +0 -67
- package/src/interface/validations/static.js +0 -137
- package/src/interface/validations/static.spec.js +0 -397
- package/src/interface/workflow.replay_compatibility.spec.js +0 -254
- package/src/worker/loader.js +0 -202
- package/src/worker/loader.spec.js +0 -498
- package/src/worker/shutdown.js +0 -26
- package/src/worker/shutdown.spec.js +0 -82
- package/src/worker/start_catalog.js +0 -96
- package/src/worker/start_catalog.spec.js +0 -179
package/src/worker/index.spec.js
CHANGED
|
@@ -1,125 +1,232 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
catalogJobInstance,
|
|
5
|
+
configValues,
|
|
6
|
+
connectionMonitorInstance,
|
|
7
|
+
createCatalogMock,
|
|
8
|
+
initInterceptorsMock,
|
|
9
|
+
messageBusMock,
|
|
10
|
+
mockConnection,
|
|
11
|
+
mockLog,
|
|
12
|
+
mockWorker,
|
|
13
|
+
promises,
|
|
14
|
+
resetPromises,
|
|
15
|
+
setupInterruptionHandlerMock,
|
|
16
|
+
setupTelemetryMock
|
|
17
|
+
} = vi.hoisted( () => {
|
|
18
|
+
const createDeferred = () => {
|
|
19
|
+
const state = {};
|
|
20
|
+
state.promise = new Promise( ( resolve, reject ) => {
|
|
21
|
+
state.resolve = resolve;
|
|
22
|
+
state.reject = reject;
|
|
23
|
+
} );
|
|
24
|
+
return state;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const promises = {};
|
|
28
|
+
const resetPromises = () => {
|
|
29
|
+
promises.workerRun = createDeferred();
|
|
30
|
+
promises.connectionMonitor = createDeferred();
|
|
31
|
+
promises.catalogJob = createDeferred();
|
|
32
|
+
};
|
|
33
|
+
resetPromises();
|
|
34
|
+
|
|
35
|
+
const configValues = {
|
|
36
|
+
address: 'localhost:7233',
|
|
37
|
+
apiKey: undefined,
|
|
38
|
+
namespace: 'default',
|
|
39
|
+
taskQueue: 'test-queue',
|
|
40
|
+
catalogId: 'test-catalog',
|
|
41
|
+
grpcProxy: undefined,
|
|
42
|
+
maxConcurrentWorkflowTaskExecutions: 200,
|
|
43
|
+
maxConcurrentActivityTaskExecutions: 40,
|
|
44
|
+
maxCachedWorkflows: 1000,
|
|
45
|
+
maxConcurrentActivityTaskPolls: 5,
|
|
46
|
+
maxConcurrentWorkflowTaskPolls: 5,
|
|
47
|
+
processFailureShutdownDelay: 0
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const connectionMonitorInstance = {
|
|
51
|
+
running: false,
|
|
52
|
+
connectionLossError: null,
|
|
53
|
+
onConnectionLost: vi.fn( cb => {
|
|
54
|
+
connectionMonitorInstance.connectionLostCb = cb;
|
|
55
|
+
} ),
|
|
56
|
+
start: vi.fn( () => {
|
|
57
|
+
connectionMonitorInstance.running = true;
|
|
58
|
+
return promises.connectionMonitor.promise.finally( () => {
|
|
59
|
+
connectionMonitorInstance.running = false;
|
|
60
|
+
} );
|
|
61
|
+
} ),
|
|
62
|
+
stop: vi.fn( () => {
|
|
63
|
+
connectionMonitorInstance.running = false;
|
|
64
|
+
promises.connectionMonitor.resolve();
|
|
65
|
+
return promises.connectionMonitor.promise;
|
|
66
|
+
} ),
|
|
67
|
+
connectionLostCb: null
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const catalogJobInstance = {
|
|
71
|
+
running: false,
|
|
72
|
+
error: null,
|
|
73
|
+
onError: vi.fn( cb => {
|
|
74
|
+
catalogJobInstance.errorCb = cb;
|
|
75
|
+
} ),
|
|
76
|
+
run: vi.fn( () => {
|
|
77
|
+
catalogJobInstance.running = true;
|
|
78
|
+
return promises.catalogJob.promise.finally( () => {
|
|
79
|
+
catalogJobInstance.running = false;
|
|
80
|
+
} );
|
|
81
|
+
} ),
|
|
82
|
+
interrupt: vi.fn( () => {
|
|
83
|
+
catalogJobInstance.running = false;
|
|
84
|
+
promises.catalogJob.resolve();
|
|
85
|
+
return promises.catalogJob.promise;
|
|
86
|
+
} ),
|
|
87
|
+
errorCb: null
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const mockWorker = {
|
|
91
|
+
getStatus: vi.fn( () => ( { runState: mockWorker.runState } ) ),
|
|
92
|
+
run: vi.fn( () => promises.workerRun.promise ),
|
|
93
|
+
runState: 'RUNNING',
|
|
94
|
+
shutdown: vi.fn()
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
catalogJobInstance,
|
|
99
|
+
configValues,
|
|
100
|
+
connectionMonitorInstance,
|
|
101
|
+
createCatalogMock: vi.fn().mockReturnValue( { workflows: [], activities: {} } ),
|
|
102
|
+
createWorkflowsEntryPointMock: vi.fn().mockReturnValue( '/fake/workflows/path.js' ),
|
|
103
|
+
hashSourceCodeMock: vi.fn().mockResolvedValue( 'catalog-hash' ),
|
|
104
|
+
initInterceptorsMock: vi.fn().mockReturnValue( [] ),
|
|
105
|
+
loadActivitiesMock: vi.fn().mockResolvedValue( {} ),
|
|
106
|
+
loadHooksMock: vi.fn().mockResolvedValue( undefined ),
|
|
107
|
+
loadWorkflowsMock: vi.fn().mockResolvedValue( [] ),
|
|
108
|
+
messageBusMock: { emit: vi.fn(), on: vi.fn() },
|
|
109
|
+
mockConnection: { close: vi.fn().mockResolvedValue( undefined ) },
|
|
110
|
+
mockLog: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
|
|
111
|
+
mockWorker,
|
|
112
|
+
promises,
|
|
113
|
+
resetPromises,
|
|
114
|
+
setupInterruptionHandlerMock: vi.fn(),
|
|
115
|
+
setupTelemetryMock: vi.fn()
|
|
116
|
+
};
|
|
117
|
+
} );
|
|
2
118
|
|
|
3
|
-
const mockLog = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
|
4
119
|
vi.mock( '#logger', () => ( { createChildLogger: () => mockLog } ) );
|
|
5
|
-
|
|
6
120
|
vi.mock( '#consts', async importOriginal => {
|
|
7
121
|
const actual = await importOriginal();
|
|
8
122
|
return { ...actual };
|
|
9
123
|
} );
|
|
10
|
-
|
|
11
|
-
vi.mock( '#tracing', () => ( { init:
|
|
12
|
-
|
|
13
|
-
const configValues = {
|
|
14
|
-
address: 'localhost:7233',
|
|
15
|
-
apiKey: undefined,
|
|
16
|
-
namespace: 'default',
|
|
17
|
-
taskQueue: 'test-queue',
|
|
18
|
-
catalogId: 'test-catalog',
|
|
19
|
-
grpcProxy: undefined,
|
|
20
|
-
maxConcurrentWorkflowTaskExecutions: 200,
|
|
21
|
-
maxConcurrentActivityTaskExecutions: 40,
|
|
22
|
-
maxCachedWorkflows: 1000,
|
|
23
|
-
maxConcurrentActivityTaskPolls: 5,
|
|
24
|
-
maxConcurrentWorkflowTaskPolls: 5,
|
|
25
|
-
processFailureShutdownDelay: 0
|
|
26
|
-
};
|
|
27
|
-
vi.mock( './configs.js', () => configValues );
|
|
28
|
-
|
|
29
|
-
const messageBusMock = { on: vi.fn(), emit: vi.fn() };
|
|
124
|
+
const initTracing = vi.fn().mockResolvedValue( undefined );
|
|
125
|
+
vi.mock( '#tracing', () => ( { init: initTracing } ) );
|
|
30
126
|
vi.mock( '#bus', () => ( { messageBus: messageBusMock } ) );
|
|
31
127
|
|
|
32
|
-
const loadWorkflowsMock = vi.fn().mockResolvedValue( [] );
|
|
33
|
-
const loadActivitiesMock = vi.fn().mockResolvedValue( {} );
|
|
128
|
+
const loadWorkflowsMock = vi.fn().mockResolvedValue( { workflows: [], entrypoint: '/fake/workflows/path.js' } );
|
|
129
|
+
const loadActivitiesMock = vi.fn().mockResolvedValue( { activities: {} } );
|
|
34
130
|
const loadHooksMock = vi.fn().mockResolvedValue( undefined );
|
|
35
|
-
|
|
36
|
-
vi.mock( './loader.js', () => ( {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
loadHooks: loadHooksMock,
|
|
40
|
-
createWorkflowsEntryPoint: createWorkflowsEntryPointMock
|
|
41
|
-
} ) );
|
|
131
|
+
vi.mock( './loader/workflows.js', () => ( { loadWorkflows: loadWorkflowsMock } ) );
|
|
132
|
+
vi.mock( './loader/activities.js', () => ( { loadActivities: loadActivitiesMock } ) );
|
|
133
|
+
vi.mock( './loader/hooks.js', () => ( { loadHooks: loadHooksMock } ) );
|
|
134
|
+
vi.mock( './configs.js', () => configValues );
|
|
42
135
|
|
|
43
136
|
const hashSourceCodeMock = vi.fn().mockResolvedValue( 'catalog-hash' );
|
|
44
|
-
vi.mock( './
|
|
137
|
+
vi.mock( './loader/tools.js', () => ( { hashSourceCode: hashSourceCodeMock } ) );
|
|
45
138
|
|
|
46
139
|
vi.mock( './sinks.js', () => ( { sinks: {} } ) );
|
|
47
|
-
|
|
48
|
-
const createCatalogMock = vi.fn().mockReturnValue( { workflows: [], activities: {} } );
|
|
49
140
|
vi.mock( './catalog_workflow/index.js', () => ( { createCatalog: createCatalogMock } ) );
|
|
50
|
-
|
|
51
141
|
vi.mock( './bundler_options.js', () => ( { webpackConfigHook: vi.fn() } ) );
|
|
52
|
-
|
|
53
|
-
const initInterceptorsMock = vi.fn().mockReturnValue( [] );
|
|
54
142
|
vi.mock( './interceptors/index.js', () => ( { initInterceptors: initInterceptorsMock } ) );
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
vi.mock( './
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
vi.mock( './
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
143
|
+
vi.mock( './proxy.js', () => ( { bootstrapFetchProxy: vi.fn() } ) );
|
|
144
|
+
vi.mock( './telemetry.js', () => ( { setupTelemetry: setupTelemetryMock } ) );
|
|
145
|
+
vi.mock( './interruption.js', () => ( { setupInterruptionHandler: setupInterruptionHandlerMock } ) );
|
|
146
|
+
vi.mock( './connection_monitor.js', () => ( {
|
|
147
|
+
TemporalConnectionMonitor: vi.fn( function () {
|
|
148
|
+
return connectionMonitorInstance;
|
|
149
|
+
} )
|
|
150
|
+
} ) );
|
|
151
|
+
vi.mock( './catalog_workflow/catalog_job.js', () => ( {
|
|
152
|
+
CatalogJob: vi.fn( function () {
|
|
153
|
+
return catalogJobInstance;
|
|
154
|
+
} )
|
|
155
|
+
} ) );
|
|
68
156
|
vi.mock( './log_hooks.js', () => ( {} ) );
|
|
69
|
-
|
|
70
|
-
const runState = { resolve: null };
|
|
71
|
-
const runPromise = new Promise( r => {
|
|
72
|
-
runState.resolve = r;
|
|
73
|
-
} );
|
|
74
|
-
const shutdownMock = vi.fn();
|
|
75
|
-
const mockConnection = { close: vi.fn().mockResolvedValue( undefined ) };
|
|
76
|
-
const mockWorker = { run: () => runPromise, shutdown: shutdownMock };
|
|
77
|
-
|
|
78
157
|
vi.mock( '@temporalio/worker', () => ( {
|
|
79
|
-
|
|
80
|
-
|
|
158
|
+
NativeConnection: { connect: vi.fn().mockResolvedValue( mockConnection ) },
|
|
159
|
+
Worker: { create: vi.fn().mockResolvedValue( mockWorker ) }
|
|
81
160
|
} ) );
|
|
82
161
|
|
|
162
|
+
const importWorker = async () => {
|
|
163
|
+
vi.resetModules();
|
|
164
|
+
await import( './index.js' );
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const settleWorker = async () => {
|
|
168
|
+
promises.catalogJob.resolve();
|
|
169
|
+
promises.connectionMonitor.resolve();
|
|
170
|
+
promises.workerRun.resolve();
|
|
171
|
+
await vi.waitFor( () => expect( mockConnection.close ).toHaveBeenCalled() );
|
|
172
|
+
};
|
|
173
|
+
|
|
83
174
|
describe( 'worker/index', () => {
|
|
84
175
|
const exitMock = vi.fn();
|
|
85
176
|
const originalArgv = process.argv;
|
|
86
|
-
const originalExit = process.exit;
|
|
87
177
|
|
|
88
178
|
beforeEach( () => {
|
|
89
179
|
vi.clearAllMocks();
|
|
180
|
+
resetPromises();
|
|
181
|
+
configValues.apiKey = undefined;
|
|
182
|
+
configValues.grpcProxy = undefined;
|
|
183
|
+
catalogJobInstance.error = null;
|
|
184
|
+
catalogJobInstance.errorCb = null;
|
|
185
|
+
catalogJobInstance.running = false;
|
|
186
|
+
connectionMonitorInstance.connectionLossError = null;
|
|
187
|
+
connectionMonitorInstance.connectionLostCb = null;
|
|
188
|
+
connectionMonitorInstance.running = false;
|
|
189
|
+
mockConnection.close.mockResolvedValue( undefined );
|
|
190
|
+
mockWorker.runState = 'RUNNING';
|
|
90
191
|
process.argv = [ ...originalArgv.slice( 0, 2 ), '/test/caller/dir' ];
|
|
91
|
-
process
|
|
192
|
+
vi.spyOn( process, 'exit' ).mockImplementation( exitMock );
|
|
92
193
|
} );
|
|
93
194
|
|
|
94
195
|
afterEach( () => {
|
|
95
196
|
process.argv = originalArgv;
|
|
96
|
-
|
|
97
|
-
configValues.apiKey = undefined;
|
|
197
|
+
vi.restoreAllMocks();
|
|
98
198
|
} );
|
|
99
199
|
|
|
100
|
-
it( '
|
|
101
|
-
const {
|
|
102
|
-
const {
|
|
200
|
+
it( 'creates the worker lifecycle jobs with expected dependencies', async () => {
|
|
201
|
+
const { NativeConnection, Worker } = await import( '@temporalio/worker' );
|
|
202
|
+
const { TemporalConnectionMonitor } = await import( './connection_monitor.js' );
|
|
203
|
+
const { CatalogJob } = await import( './catalog_workflow/catalog_job.js' );
|
|
103
204
|
|
|
104
|
-
|
|
205
|
+
await importWorker();
|
|
105
206
|
|
|
106
|
-
await vi.waitFor( () =>
|
|
107
|
-
|
|
108
|
-
|
|
207
|
+
await vi.waitFor( () => expect( Worker.create ).toHaveBeenCalled() );
|
|
208
|
+
|
|
209
|
+
expect( loadHooksMock ).toHaveBeenCalledWith( '/test/caller/dir' );
|
|
109
210
|
expect( loadWorkflowsMock ).toHaveBeenCalledWith( '/test/caller/dir' );
|
|
110
211
|
expect( loadActivitiesMock ).toHaveBeenCalledWith( '/test/caller/dir', [] );
|
|
111
|
-
expect( createWorkflowsEntryPointMock ).toHaveBeenCalledWith( [] );
|
|
112
212
|
expect( initTracing ).toHaveBeenCalled();
|
|
113
213
|
expect( createCatalogMock ).toHaveBeenCalledWith( { workflows: [], activities: {} } );
|
|
114
214
|
expect( hashSourceCodeMock ).toHaveBeenCalledWith( '/test/caller/dir' );
|
|
115
|
-
expect( bootstrapFetchProxyMock ).toHaveBeenCalled();
|
|
116
215
|
expect( NativeConnection.connect ).toHaveBeenCalledWith( {
|
|
117
216
|
address: configValues.address,
|
|
118
217
|
tls: false,
|
|
119
218
|
apiKey: undefined,
|
|
120
219
|
proxy: undefined
|
|
121
220
|
} );
|
|
221
|
+
expect( TemporalConnectionMonitor ).toHaveBeenCalledWith( mockConnection );
|
|
222
|
+
expect( CatalogJob ).toHaveBeenCalledWith( {
|
|
223
|
+
connection: mockConnection,
|
|
224
|
+
namespace: configValues.namespace,
|
|
225
|
+
catalog: { workflows: [], activities: {} },
|
|
226
|
+
catalogHash: 'catalog-hash'
|
|
227
|
+
} );
|
|
122
228
|
expect( Worker.create ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
229
|
+
connection: mockConnection,
|
|
123
230
|
namespace: configValues.namespace,
|
|
124
231
|
taskQueue: configValues.taskQueue,
|
|
125
232
|
workflowsPath: '/fake/workflows/path.js',
|
|
@@ -130,63 +237,128 @@ describe( 'worker/index', () => {
|
|
|
130
237
|
maxConcurrentActivityTaskPolls: configValues.maxConcurrentActivityTaskPolls,
|
|
131
238
|
maxConcurrentWorkflowTaskPolls: configValues.maxConcurrentWorkflowTaskPolls
|
|
132
239
|
} ) );
|
|
133
|
-
expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: []
|
|
134
|
-
expect( registerShutdownMock ).toHaveBeenCalledWith( { worker: mockWorker, log: mockLog } );
|
|
240
|
+
expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [] } );
|
|
135
241
|
expect( setupTelemetryMock ).toHaveBeenCalledWith( { worker: mockWorker } );
|
|
136
|
-
expect(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
expect( mockConnection.close ).toHaveBeenCalled();
|
|
146
|
-
} );
|
|
147
|
-
expect( exitMock ).toHaveBeenCalledWith( 0 );
|
|
242
|
+
expect( setupInterruptionHandlerMock ).toHaveBeenCalledWith( expect.any( Function ) );
|
|
243
|
+
expect( connectionMonitorInstance.onConnectionLost ).toHaveBeenCalledWith( expect.any( Function ) );
|
|
244
|
+
expect( catalogJobInstance.onError ).toHaveBeenCalledWith( expect.any( Function ) );
|
|
245
|
+
expect( mockWorker.run ).toHaveBeenCalled();
|
|
246
|
+
expect( connectionMonitorInstance.start ).toHaveBeenCalled();
|
|
247
|
+
expect( catalogJobInstance.run ).toHaveBeenCalled();
|
|
248
|
+
|
|
249
|
+
await settleWorker();
|
|
250
|
+
expect( mockLog.info ).toHaveBeenCalledWith( 'Bye' );
|
|
148
251
|
} );
|
|
149
252
|
|
|
150
253
|
it( 'enables TLS when apiKey is set', async () => {
|
|
151
254
|
configValues.apiKey = 'secret';
|
|
152
|
-
vi.resetModules();
|
|
153
|
-
|
|
154
255
|
const { NativeConnection } = await import( '@temporalio/worker' );
|
|
155
|
-
|
|
256
|
+
|
|
257
|
+
await importWorker();
|
|
258
|
+
|
|
259
|
+
await vi.waitFor( () => expect( NativeConnection.connect ).toHaveBeenCalledWith( expect.objectContaining( {
|
|
260
|
+
apiKey: 'secret',
|
|
261
|
+
tls: true
|
|
262
|
+
} ) ) );
|
|
263
|
+
|
|
264
|
+
await settleWorker();
|
|
265
|
+
} );
|
|
266
|
+
|
|
267
|
+
it( 'runs graceful shutdown when interrupted', async () => {
|
|
268
|
+
await importWorker();
|
|
269
|
+
|
|
270
|
+
await vi.waitFor( () => expect( setupInterruptionHandlerMock ).toHaveBeenCalled() );
|
|
271
|
+
const [ shutdown ] = setupInterruptionHandlerMock.mock.calls[0];
|
|
272
|
+
|
|
273
|
+
shutdown();
|
|
274
|
+
|
|
275
|
+
expect( mockWorker.shutdown ).toHaveBeenCalledOnce();
|
|
276
|
+
expect( connectionMonitorInstance.stop ).toHaveBeenCalledOnce();
|
|
277
|
+
expect( catalogJobInstance.interrupt ).toHaveBeenCalledOnce();
|
|
278
|
+
|
|
279
|
+
promises.workerRun.resolve();
|
|
280
|
+
await vi.waitFor( () => expect( mockConnection.close ).toHaveBeenCalled() );
|
|
281
|
+
expect( mockLog.info ).toHaveBeenCalledWith( 'Bye' );
|
|
282
|
+
} );
|
|
283
|
+
|
|
284
|
+
it( 'does not call worker.shutdown when worker has already failed', async () => {
|
|
285
|
+
const error = new Error( 'Big Failure' );
|
|
286
|
+
|
|
287
|
+
await importWorker();
|
|
288
|
+
await vi.waitFor( () => expect( mockWorker.run ).toHaveBeenCalled() );
|
|
289
|
+
|
|
290
|
+
mockWorker.runState = 'FAILED';
|
|
291
|
+
promises.workerRun.reject( error );
|
|
292
|
+
|
|
293
|
+
await vi.waitFor( () => expect( connectionMonitorInstance.stop ).toHaveBeenCalled() );
|
|
294
|
+
expect( mockWorker.shutdown ).not.toHaveBeenCalled();
|
|
295
|
+
|
|
296
|
+
promises.connectionMonitor.resolve();
|
|
297
|
+
promises.catalogJob.resolve();
|
|
156
298
|
|
|
157
299
|
await vi.waitFor( () => {
|
|
158
|
-
expect(
|
|
159
|
-
|
|
160
|
-
apiKey: 'secret'
|
|
300
|
+
expect( mockLog.error ).toHaveBeenCalledWith( 'Fatal error', expect.objectContaining( {
|
|
301
|
+
error: 'Big Failure'
|
|
161
302
|
} ) );
|
|
162
303
|
} );
|
|
163
|
-
|
|
164
|
-
await vi.waitFor( () => expect( exitMock ).
|
|
304
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( expect.any( String ), { error } );
|
|
305
|
+
await vi.waitFor( () => expect( exitMock ).toHaveBeenCalledWith( 1 ) );
|
|
165
306
|
} );
|
|
166
307
|
|
|
167
|
-
it( '
|
|
168
|
-
|
|
308
|
+
it( 'throws connection monitor errors after graceful shutdown', async () => {
|
|
309
|
+
const error = new Error( 'connection lost' );
|
|
310
|
+
|
|
311
|
+
await importWorker();
|
|
312
|
+
await vi.waitFor( () => expect( connectionMonitorInstance.onConnectionLost ).toHaveBeenCalled() );
|
|
169
313
|
|
|
170
|
-
|
|
314
|
+
connectionMonitorInstance.connectionLossError = error;
|
|
315
|
+
connectionMonitorInstance.connectionLostCb( error );
|
|
316
|
+
promises.workerRun.resolve();
|
|
171
317
|
|
|
172
318
|
await vi.waitFor( () => {
|
|
173
|
-
expect(
|
|
319
|
+
expect( mockLog.error ).toHaveBeenCalledWith( 'Fatal error', expect.objectContaining( {
|
|
320
|
+
error: 'connection lost'
|
|
321
|
+
} ) );
|
|
174
322
|
} );
|
|
175
|
-
|
|
176
|
-
|
|
323
|
+
expect( mockWorker.shutdown ).toHaveBeenCalledOnce();
|
|
324
|
+
expect( catalogJobInstance.interrupt ).toHaveBeenCalledOnce();
|
|
325
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( expect.any( String ), { error } );
|
|
177
326
|
} );
|
|
178
327
|
|
|
179
|
-
it( '
|
|
180
|
-
|
|
181
|
-
|
|
328
|
+
it( 'throws catalog job errors after graceful shutdown', async () => {
|
|
329
|
+
const error = new Error( 'catalog failed' );
|
|
330
|
+
|
|
331
|
+
await importWorker();
|
|
332
|
+
await vi.waitFor( () => expect( catalogJobInstance.onError ).toHaveBeenCalled() );
|
|
182
333
|
|
|
183
|
-
|
|
334
|
+
catalogJobInstance.error = error;
|
|
335
|
+
catalogJobInstance.errorCb( error );
|
|
336
|
+
promises.workerRun.resolve();
|
|
184
337
|
|
|
185
338
|
await vi.waitFor( () => {
|
|
186
|
-
expect( mockLog.error ).toHaveBeenCalledWith( 'Fatal error', expect.
|
|
339
|
+
expect( mockLog.error ).toHaveBeenCalledWith( 'Fatal error', expect.objectContaining( {
|
|
340
|
+
error: 'catalog failed'
|
|
341
|
+
} ) );
|
|
187
342
|
} );
|
|
343
|
+
expect( mockWorker.shutdown ).toHaveBeenCalledOnce();
|
|
344
|
+
expect( connectionMonitorInstance.stop ).toHaveBeenCalledOnce();
|
|
345
|
+
expect( messageBusMock.emit ).toHaveBeenCalledWith( expect.any( String ), { error } );
|
|
346
|
+
} );
|
|
347
|
+
|
|
348
|
+
it( 'cleans up partial startup failures after connecting', async () => {
|
|
349
|
+
const { Worker } = await import( '@temporalio/worker' );
|
|
350
|
+
const error = new Error( 'worker create failed' );
|
|
351
|
+
Worker.create.mockRejectedValueOnce( error );
|
|
352
|
+
|
|
353
|
+
await importWorker();
|
|
354
|
+
|
|
355
|
+
await vi.waitFor( () => expect( mockConnection.close ).toHaveBeenCalled() );
|
|
356
|
+
expect( connectionMonitorInstance.stop ).not.toHaveBeenCalled();
|
|
357
|
+
expect( catalogJobInstance.interrupt ).not.toHaveBeenCalled();
|
|
188
358
|
await vi.waitFor( () => {
|
|
189
|
-
expect(
|
|
359
|
+
expect( mockLog.error ).toHaveBeenCalledWith( 'Fatal error', expect.objectContaining( {
|
|
360
|
+
error: 'worker create failed'
|
|
361
|
+
} ) );
|
|
190
362
|
} );
|
|
191
363
|
} );
|
|
192
364
|
} );
|
|
@@ -2,14 +2,11 @@ import { Context, activityInfo as activityInfoFn } from '@temporalio/activity';
|
|
|
2
2
|
import { Storage } from '#async_storage';
|
|
3
3
|
import * as Tracing from '#tracing';
|
|
4
4
|
import { headersToObject } from './headers.js';
|
|
5
|
-
import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL
|
|
6
|
-
import { activityHeartbeatEnabled, activityHeartbeatIntervalMs
|
|
5
|
+
import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL } from '#consts';
|
|
6
|
+
import { activityHeartbeatEnabled, activityHeartbeatIntervalMs } from '../configs.js';
|
|
7
7
|
import { messageBus } from '#bus';
|
|
8
|
-
import { Client } from '@temporalio/client';
|
|
9
|
-
import { createChildLogger } from '#logger';
|
|
10
8
|
import { aggregateAttributes } from '#internal_utils/aggregations';
|
|
11
|
-
|
|
12
|
-
const log = createChildLogger( 'ActivityInterceptor' );
|
|
9
|
+
import { buildApplicationFailureWithDetails } from '#internal_utils/errors';
|
|
13
10
|
|
|
14
11
|
/*
|
|
15
12
|
This interceptor wraps every activity execution with cross-cutting concerns:
|
|
@@ -28,7 +25,7 @@ const log = createChildLogger( 'ActivityInterceptor' );
|
|
|
28
25
|
- Headers injected by the workflow interceptor
|
|
29
26
|
*/
|
|
30
27
|
export class ActivityExecutionInterceptor {
|
|
31
|
-
constructor( { activities, workflows
|
|
28
|
+
constructor( { activities, workflows } ) {
|
|
32
29
|
// convert activities{} object to a map: activityType:kind
|
|
33
30
|
this.activityKindMap = new Map( Object.entries( activities )
|
|
34
31
|
.map( ( [ type, fn ] ) => ( [ type, fn[METADATA_ACCESS_SYMBOL].type ] ) ) );
|
|
@@ -37,12 +34,11 @@ export class ActivityExecutionInterceptor {
|
|
|
37
34
|
this.workflowsPathMap = new Map( workflows.flatMap( ( { name, aliases, path } ) =>
|
|
38
35
|
[ name, ...aliases ?? [] ].map( a => ( [ a, path ] ) )
|
|
39
36
|
) );
|
|
40
|
-
this.connection = connection;
|
|
41
37
|
};
|
|
42
38
|
|
|
43
39
|
async execute( input, next ) {
|
|
44
40
|
const activityInfo = activityInfoFn();
|
|
45
|
-
const { workflowExecution: {
|
|
41
|
+
const { workflowExecution: { runId }, activityId, activityType, workflowType } = activityInfo;
|
|
46
42
|
const { traceInfo, workflowDetails } = headersToObject( input.headers );
|
|
47
43
|
const outputActivityKind = this.activityKindMap.get( activityType );
|
|
48
44
|
const workflowFilename = this.workflowsPathMap.get( workflowType );
|
|
@@ -61,19 +57,6 @@ export class ActivityExecutionInterceptor {
|
|
|
61
57
|
|
|
62
58
|
const addAttribute = attribute => state.attributes.push( attribute );
|
|
63
59
|
|
|
64
|
-
const sendAggregationsViaSignal = async () => {
|
|
65
|
-
if ( state.attributes.length > 0 ) {
|
|
66
|
-
try {
|
|
67
|
-
const client = new Client( { connection: this.connection, namespace } );
|
|
68
|
-
const workflowHandle = client.workflow.getHandle( workflowId );
|
|
69
|
-
await workflowHandle.signal( Signal.SEND_AGGREGATIONS, aggregateAttributes( state.attributes ) );
|
|
70
|
-
} catch ( error ) {
|
|
71
|
-
const errorContext = { message: error.message, stack: error.stack, activityId, activityType, workflowId, workflowType, runId };
|
|
72
|
-
log.warn( `Signal "${Signal.SEND_AGGREGATIONS}" failed`, errorContext );
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
60
|
// Adds context accessible information
|
|
78
61
|
const storageContext = {
|
|
79
62
|
parentId: activityId,
|
|
@@ -107,9 +90,9 @@ export class ActivityExecutionInterceptor {
|
|
|
107
90
|
messageBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, workflowDetails, outputActivityKind, error } );
|
|
108
91
|
Tracing.addEventError( { id: activityId, details: error, traceInfo } );
|
|
109
92
|
|
|
110
|
-
|
|
93
|
+
const aggregations = state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null;
|
|
111
94
|
|
|
112
|
-
throw error;
|
|
95
|
+
throw aggregations ? buildApplicationFailureWithDetails( error, { aggregations } ) : error;
|
|
113
96
|
} finally {
|
|
114
97
|
clearInterval( state.heartbeat );
|
|
115
98
|
}
|