@outputai/core 0.14.0 → 0.14.1-next.1ec4416.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.
Files changed (100) hide show
  1. package/dist/credentials/credentials.d.ts +6 -0
  2. package/dist/credentials/credentials.js +87 -0
  3. package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
  4. package/dist/credentials/encrypted_yaml_provider.js +77 -0
  5. package/dist/credentials/encryption.d.ts +3 -0
  6. package/dist/credentials/encryption.js +6 -0
  7. package/dist/credentials/errors.d.ts +13 -0
  8. package/dist/credentials/errors.js +39 -0
  9. package/dist/credentials/index.d.ts +6 -0
  10. package/dist/credentials/index.js +5 -0
  11. package/dist/credentials/init.d.ts +1 -0
  12. package/dist/credentials/init.js +7 -0
  13. package/dist/credentials/paths.d.ts +7 -0
  14. package/dist/credentials/paths.js +14 -0
  15. package/dist/credentials/provider_registry.d.ts +3 -0
  16. package/dist/credentials/provider_registry.js +6 -0
  17. package/dist/credentials/types.d.ts +12 -0
  18. package/dist/credentials/types.js +1 -0
  19. package/package.json +25 -14
  20. package/src/async_storage.d.ts +4 -0
  21. package/src/consts.js +0 -2
  22. package/src/helpers/object.d.ts +2 -0
  23. package/src/hooks/index.d.ts +0 -8
  24. package/src/hooks/index.js +0 -3
  25. package/src/logger/index.d.ts +4 -0
  26. package/src/worker/index.js +3 -1
  27. package/bin/healthcheck.spec.js +0 -90
  28. package/src/bus.spec.js +0 -196
  29. package/src/errors.spec.js +0 -19
  30. package/src/helpers/activity_options.spec.js +0 -55
  31. package/src/helpers/component.spec.js +0 -54
  32. package/src/helpers/error_serializer.spec.js +0 -479
  33. package/src/helpers/errors.spec.js +0 -189
  34. package/src/helpers/fetch.spec.js +0 -292
  35. package/src/helpers/function.spec.js +0 -24
  36. package/src/helpers/object.spec.js +0 -425
  37. package/src/helpers/promise.spec.js +0 -62
  38. package/src/helpers/redact.spec.js +0 -66
  39. package/src/helpers/string.spec.js +0 -99
  40. package/src/helpers/temporal_context.spec.ts +0 -83
  41. package/src/helpers/trace_info.spec.js +0 -45
  42. package/src/helpers/workflow_context.spec.js +0 -46
  43. package/src/hooks/index.spec.js +0 -265
  44. package/src/hooks/pending_hooks.spec.js +0 -62
  45. package/src/interface/evaluator.spec.js +0 -685
  46. package/src/interface/logger.spec.js +0 -172
  47. package/src/interface/step.spec.js +0 -128
  48. package/src/interface/validations/index.spec.js +0 -197
  49. package/src/interface/validations/schemas.spec.js +0 -229
  50. package/src/interface/webhook.spec.js +0 -170
  51. package/src/interface/workflow.spec.js +0 -710
  52. package/src/interface/workflow_utils.spec.js +0 -271
  53. package/src/interface/zod_integration.spec.js +0 -646
  54. package/src/internal_activities/index.spec.js +0 -186
  55. package/src/logger/development.spec.js +0 -87
  56. package/src/logger/index.spec.js +0 -27
  57. package/src/logger/production.spec.js +0 -71
  58. package/src/sdk/helpers/component_metadata.spec.js +0 -30
  59. package/src/sdk/helpers/objects.spec.js +0 -16
  60. package/src/sdk/helpers/path.spec.js +0 -102
  61. package/src/sdk/runtime/context.spec.js +0 -37
  62. package/src/sdk/runtime/events.spec.js +0 -40
  63. package/src/temporal/client.spec.js +0 -124
  64. package/src/tracing/processors/local/index.spec.js +0 -204
  65. package/src/tracing/processors/s3/configs.spec.js +0 -64
  66. package/src/tracing/processors/s3/index.spec.js +0 -185
  67. package/src/tracing/processors/s3/redis_client.spec.js +0 -179
  68. package/src/tracing/processors/s3/s3_client.spec.js +0 -68
  69. package/src/tracing/tools/build_trace_tree.spec.js +0 -216
  70. package/src/tracing/trace_engine.spec.js +0 -299
  71. package/src/worker/bundle.spec.js +0 -53
  72. package/src/worker/bundler_options.spec.js +0 -62
  73. package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
  74. package/src/worker/catalog_workflow/index.spec.js +0 -222
  75. package/src/worker/configs.spec.js +0 -213
  76. package/src/worker/configs_tuner_schema.spec.js +0 -127
  77. package/src/worker/connection_monitor.spec.js +0 -328
  78. package/src/worker/global_functions.spec.js +0 -55
  79. package/src/worker/index.spec.js +0 -526
  80. package/src/worker/interceptors/activity.spec.js +0 -369
  81. package/src/worker/interceptors/headers.spec.js +0 -73
  82. package/src/worker/interceptors/workflow.spec.js +0 -357
  83. package/src/worker/interruption.spec.js +0 -177
  84. package/src/worker/loader/activities.spec.js +0 -212
  85. package/src/worker/loader/hooks.spec.js +0 -64
  86. package/src/worker/loader/matchers.spec.js +0 -140
  87. package/src/worker/loader/tools.spec.js +0 -595
  88. package/src/worker/loader/workflows.spec.js +0 -287
  89. package/src/worker/log_hooks.spec.js +0 -303
  90. package/src/worker/shutdown.spec.js +0 -197
  91. package/src/worker/sinks.spec.js +0 -220
  92. package/src/worker/telemetry.spec.js +0 -80
  93. package/src/worker/temporal_logger.spec.js +0 -90
  94. package/src/worker/webpack_loaders/tools.spec.js +0 -314
  95. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
  96. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
  97. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
  98. package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
  99. package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
  100. package/src/worker/worker_runner.spec.js +0 -220
@@ -1,55 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
-
3
- const {
4
- ACTIVITY_LOGGER_SYMBOL,
5
- BusEventType
6
- } = vi.hoisted( () => ( {
7
- ACTIVITY_LOGGER_SYMBOL: Symbol( 'activity_logger' ),
8
- BusEventType: {
9
- ACTIVITY_LOG: 'activity:log'
10
- }
11
- } ) );
12
-
13
- const mainEventBusMock = vi.hoisted( () => ( {
14
- emit: vi.fn()
15
- } ) );
16
- const activityInfoMock = vi.hoisted( () => vi.fn( () => ( {
17
- activityId: 'act-1',
18
- activityType: 'myStep'
19
- } ) ) );
20
-
21
- vi.mock( '#consts', () => ( { ACTIVITY_LOGGER_SYMBOL, BusEventType } ) );
22
- vi.mock( '#bus', () => ( { mainEventBus: mainEventBusMock } ) );
23
- vi.mock( '@temporalio/activity', () => ( {
24
- activityInfo: activityInfoMock
25
- } ) );
26
-
27
- describe( 'worker/global_functions', () => {
28
- beforeEach( () => {
29
- vi.clearAllMocks();
30
- delete globalThis[ACTIVITY_LOGGER_SYMBOL];
31
- } );
32
-
33
- it( 'binds activity logger global function that emits activity log events with metadata', async () => {
34
- const { bindGlobalFunctions } = await import( './global_functions.js' );
35
- const metadata = { requestId: 'req-1' };
36
-
37
- bindGlobalFunctions();
38
- globalThis[ACTIVITY_LOGGER_SYMBOL]( {
39
- level: 'debug',
40
- message: 'activity detail',
41
- metadata
42
- } );
43
-
44
- expect( activityInfoMock ).toHaveBeenCalledTimes( 1 );
45
- expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.ACTIVITY_LOG, {
46
- level: 'debug',
47
- message: 'activity detail',
48
- metadata,
49
- activityInfo: {
50
- activityId: 'act-1',
51
- activityType: 'myStep'
52
- }
53
- } );
54
- } );
55
- } );
@@ -1,526 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { BusEventType } from '#consts';
3
-
4
- const {
5
- bindGlobalFunctionsMock,
6
- catalogPublisherInstance,
7
- configValues,
8
- connectionMonitorInstance,
9
- createCatalogMock,
10
- flushPendingHooksMock,
11
- hashSourceCodeMock,
12
- initInterceptorsMock,
13
- interruption,
14
- loadActivitiesMock,
15
- loadHooksMock,
16
- loadWorkflowsMock,
17
- mainEventBusMock,
18
- mockConnection,
19
- mockLog,
20
- mockWorker,
21
- promises,
22
- resetPromises,
23
- serializeErrorMock,
24
- setupClientConfigMock,
25
- setupInterruptionHandlerMock,
26
- setupTelemetryMock,
27
- setupTemporalLoggerMock,
28
- shutdownServicesMock,
29
- workerRunnerInstance
30
- } = vi.hoisted( () => {
31
- const createDeferred = () => {
32
- const state = {};
33
- state.promise = new Promise( ( resolve, reject ) => {
34
- state.resolve = resolve;
35
- state.reject = reject;
36
- } );
37
- return state;
38
- };
39
-
40
- const promises = {};
41
- const resetPromises = () => {
42
- promises.workerRunner = createDeferred();
43
- promises.connectionMonitor = createDeferred();
44
- };
45
- resetPromises();
46
-
47
- const configValues = {
48
- address: 'localhost:7233',
49
- apiKey: undefined,
50
- namespace: 'default',
51
- taskQueue: 'test-queue',
52
- catalogId: 'test-catalog',
53
- grpcProxy: undefined,
54
- maxConcurrentWorkflowTaskExecutions: 200,
55
- maxConcurrentActivityTaskExecutions: 40,
56
- maxCachedWorkflows: 1000,
57
- maxConcurrentActivityTaskPolls: 5,
58
- maxConcurrentWorkflowTaskPolls: 5,
59
- workerTuner: undefined,
60
- shutdownForceTime: undefined,
61
- shutdownGraceTime: undefined,
62
- hookFlushTimeoutMs: 5000
63
- };
64
-
65
- /**
66
- * The real runner only settles once the worker finished draining, which is triggered by stop().
67
- * Aborting alone leaves it running, so the shutdown sequence is the one awaiting the drain.
68
- */
69
- const workerRunnerInstance = {
70
- running: false,
71
- options: null,
72
- start: vi.fn( () => {
73
- workerRunnerInstance.running = true;
74
- return promises.workerRunner.promise.finally( () => {
75
- workerRunnerInstance.running = false;
76
- } );
77
- } ),
78
- stop: vi.fn( () => {
79
- promises.workerRunner.resolve();
80
- return promises.workerRunner.promise.catch( () => {} );
81
- } )
82
- };
83
-
84
- /** The real monitor exits its loop as soon as the signal aborts, so it settles on its own. */
85
- const connectionMonitorInstance = {
86
- running: false,
87
- options: null,
88
- start: vi.fn( () => {
89
- connectionMonitorInstance.running = true;
90
- connectionMonitorInstance.options?.signal.addEventListener(
91
- 'abort',
92
- () => promises.connectionMonitor.resolve(),
93
- { once: true }
94
- );
95
- return promises.connectionMonitor.promise.finally( () => {
96
- connectionMonitorInstance.running = false;
97
- } );
98
- } ),
99
- stop: vi.fn( () => {
100
- promises.connectionMonitor.resolve();
101
- return promises.connectionMonitor.promise.catch( () => {} );
102
- } )
103
- };
104
-
105
- const catalogPublisherInstance = {
106
- running: false,
107
- options: null,
108
- run: vi.fn().mockResolvedValue( undefined ),
109
- interrupt: vi.fn().mockResolvedValue( undefined )
110
- };
111
-
112
- class KillSignError extends Error {
113
- name = 'KillSignError';
114
- };
115
-
116
- class UncaughtError extends Error {
117
- name = 'UncaughtError';
118
- };
119
-
120
- const interruption = { controller: null, KillSignError, UncaughtError };
121
-
122
- return {
123
- bindGlobalFunctionsMock: vi.fn(),
124
- catalogPublisherInstance,
125
- configValues,
126
- connectionMonitorInstance,
127
- createCatalogMock: vi.fn().mockReturnValue( { workflowNames: [ 'demo' ] } ),
128
- flushPendingHooksMock: vi.fn().mockResolvedValue( undefined ),
129
- hashSourceCodeMock: vi.fn().mockResolvedValue( 'catalog-hash' ),
130
- initInterceptorsMock: vi.fn().mockReturnValue( [] ),
131
- interruption,
132
- loadActivitiesMock: vi.fn().mockResolvedValue( { activities: {} } ),
133
- loadHooksMock: vi.fn().mockResolvedValue( undefined ),
134
- loadWorkflowsMock: vi.fn().mockResolvedValue( { workflows: [], entrypoint: '/fake/workflows/path.js' } ),
135
- mainEventBusMock: { emit: vi.fn(), on: vi.fn() },
136
- mockConnection: { close: vi.fn().mockResolvedValue( undefined ) },
137
- mockLog: { error: vi.fn(), info: vi.fn(), warn: vi.fn() },
138
- mockWorker: { name: 'temporal-worker' },
139
- promises,
140
- resetPromises,
141
- serializeErrorMock: vi.fn( error => ( { name: error.name, message: error.message } ) ),
142
- setupClientConfigMock: vi.fn(),
143
- setupInterruptionHandlerMock: vi.fn( controller => {
144
- interruption.controller = controller;
145
- } ),
146
- setupTelemetryMock: vi.fn(),
147
- setupTemporalLoggerMock: vi.fn(),
148
- shutdownServicesMock: vi.fn().mockResolvedValue( [] ),
149
- workerRunnerInstance
150
- };
151
- } );
152
-
153
- const initTracing = vi.fn().mockResolvedValue( undefined );
154
-
155
- vi.mock( '#logger', () => ( { createChildLogger: () => mockLog } ) );
156
- vi.mock( '#helpers/error_serializer', () => ( { serializeError: serializeErrorMock } ) );
157
- vi.mock( '#tracing', () => ( { init: initTracing } ) );
158
- vi.mock( '#bus', () => ( { mainEventBus: mainEventBusMock } ) );
159
- vi.mock( '#hooks/pending_hooks', () => ( { flushPendingHooks: flushPendingHooksMock } ) );
160
- vi.mock( '#temporal/client', () => ( { setupClientConfig: setupClientConfigMock } ) );
161
- vi.mock( './configs.js', () => configValues );
162
- vi.mock( './loader/workflows.js', () => ( { loadWorkflows: loadWorkflowsMock } ) );
163
- vi.mock( './loader/activities.js', () => ( { loadActivities: loadActivitiesMock } ) );
164
- vi.mock( './loader/hooks.js', () => ( { loadHooks: loadHooksMock } ) );
165
- vi.mock( './loader/tools.js', () => ( { hashSourceCode: hashSourceCodeMock } ) );
166
- vi.mock( './sinks.js', () => ( { sinks: {} } ) );
167
- vi.mock( './catalog_workflow/index.js', () => ( { createCatalog: createCatalogMock } ) );
168
- vi.mock( './bundler_options.js', () => ( { webpackConfigHook: vi.fn() } ) );
169
- vi.mock( './interceptors/index.js', () => ( { initInterceptors: initInterceptorsMock } ) );
170
- vi.mock( './telemetry.js', () => ( { setupTelemetry: setupTelemetryMock } ) );
171
- vi.mock( './global_functions.js', () => ( { bindGlobalFunctions: bindGlobalFunctionsMock } ) );
172
- vi.mock( './temporal_logger.js', () => ( { setupTemporalLogger: setupTemporalLoggerMock } ) );
173
- vi.mock( './log_hooks.js', () => ( {} ) );
174
- vi.mock( './interruption.js', () => ( {
175
- setupInterruptionHandler: setupInterruptionHandlerMock,
176
- KillSignError: interruption.KillSignError,
177
- UncaughtError: interruption.UncaughtError
178
- } ) );
179
- vi.mock( './worker_runner.js', () => ( {
180
- WorkerRunner: vi.fn( function ( options ) {
181
- workerRunnerInstance.options = options;
182
- return workerRunnerInstance;
183
- } )
184
- } ) );
185
- vi.mock( './connection_monitor.js', () => ( {
186
- TemporalConnectionMonitor: vi.fn( function ( options ) {
187
- connectionMonitorInstance.options = options;
188
- return connectionMonitorInstance;
189
- } )
190
- } ) );
191
- vi.mock( './catalog_workflow/catalog_publisher.js', () => ( {
192
- CatalogPublisher: vi.fn( function ( options ) {
193
- catalogPublisherInstance.options = options;
194
- return catalogPublisherInstance;
195
- } )
196
- } ) );
197
- vi.mock( './shutdown.js', () => ( { shutdownServices: shutdownServicesMock } ) );
198
- vi.mock( '@temporalio/worker', () => ( {
199
- NativeConnection: { connect: vi.fn().mockResolvedValue( mockConnection ) },
200
- Worker: { create: vi.fn().mockResolvedValue( mockWorker ) }
201
- } ) );
202
-
203
- const importWorker = async () => {
204
- vi.resetModules();
205
- await import( './index.js' );
206
- };
207
-
208
- /** Imports the worker and waits until it is polling, which is where a healthy boot settles. */
209
- const bootWorker = async () => {
210
- await importWorker();
211
- await vi.waitFor( () => {
212
- expect( workerRunnerInstance.start ).toHaveBeenCalled();
213
- expect( connectionMonitorInstance.start ).toHaveBeenCalled();
214
- } );
215
- };
216
-
217
- const waitForExit = () => vi.waitFor( () => expect( process.exit ).toHaveBeenCalled() );
218
-
219
- describe( 'worker/index', () => {
220
- const originalArgv = process.argv;
221
-
222
- beforeEach( () => {
223
- vi.clearAllMocks();
224
- resetPromises();
225
- configValues.apiKey = undefined;
226
- configValues.grpcProxy = undefined;
227
- configValues.workerTuner = undefined;
228
- configValues.shutdownForceTime = undefined;
229
- configValues.shutdownGraceTime = undefined;
230
- workerRunnerInstance.running = false;
231
- workerRunnerInstance.options = null;
232
- connectionMonitorInstance.running = false;
233
- connectionMonitorInstance.options = null;
234
- catalogPublisherInstance.running = false;
235
- catalogPublisherInstance.options = null;
236
- interruption.controller = null;
237
- mockConnection.close.mockResolvedValue( undefined );
238
- process.argv = [ ...originalArgv.slice( 0, 2 ), '/test/caller/dir' ];
239
- vi.spyOn( process, 'exit' ).mockImplementation( () => undefined );
240
- } );
241
-
242
- afterEach( () => {
243
- process.argv = originalArgv;
244
- vi.restoreAllMocks();
245
- } );
246
-
247
- describe( 'startup', () => {
248
- it( 'wires every lifecycle component with the shared abort signal', async () => {
249
- const { NativeConnection, Worker } = await import( '@temporalio/worker' );
250
- const { TemporalConnectionMonitor } = await import( './connection_monitor.js' );
251
- const { CatalogPublisher } = await import( './catalog_workflow/catalog_publisher.js' );
252
- const { WorkerRunner } = await import( './worker_runner.js' );
253
-
254
- await bootWorker();
255
-
256
- const { signal } = interruption.controller;
257
-
258
- expect( setupInterruptionHandlerMock ).toHaveBeenCalledWith( expect.any( AbortController ) );
259
- expect( setupTemporalLoggerMock ).toHaveBeenCalledOnce();
260
- expect( loadHooksMock ).toHaveBeenCalledWith( '/test/caller/dir' );
261
- expect( loadWorkflowsMock ).toHaveBeenCalledWith( '/test/caller/dir' );
262
- expect( loadActivitiesMock ).toHaveBeenCalledWith( '/test/caller/dir', [] );
263
- expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.WORKER_BEFORE_START );
264
- expect( initTracing ).toHaveBeenCalledOnce();
265
- expect( createCatalogMock ).toHaveBeenCalledWith( { workflows: [], activities: {} } );
266
- expect( hashSourceCodeMock ).toHaveBeenCalledWith( '/test/caller/dir' );
267
- expect( NativeConnection.connect ).toHaveBeenCalledWith( {
268
- address: configValues.address,
269
- tls: false,
270
- apiKey: undefined,
271
- proxy: undefined
272
- } );
273
- expect( setupClientConfigMock ).toHaveBeenCalledWith( {
274
- connection: mockConnection,
275
- namespace: configValues.namespace
276
- } );
277
- expect( CatalogPublisher ).toHaveBeenCalledWith( {
278
- connection: mockConnection,
279
- namespace: configValues.namespace,
280
- catalog: { workflowNames: [ 'demo' ] },
281
- catalogHash: 'catalog-hash',
282
- signal
283
- } );
284
- expect( catalogPublisherInstance.run ).toHaveBeenCalledOnce();
285
- expect( TemporalConnectionMonitor ).toHaveBeenCalledWith( { connection: mockConnection, signal } );
286
- expect( Worker.create ).toHaveBeenCalledWith( expect.objectContaining( {
287
- connection: mockConnection,
288
- namespace: configValues.namespace,
289
- taskQueue: configValues.taskQueue,
290
- workflowsPath: '/fake/workflows/path.js',
291
- activities: {},
292
- maxConcurrentWorkflowTaskExecutions: configValues.maxConcurrentWorkflowTaskExecutions,
293
- maxConcurrentActivityTaskExecutions: configValues.maxConcurrentActivityTaskExecutions,
294
- maxCachedWorkflows: configValues.maxCachedWorkflows,
295
- maxConcurrentActivityTaskPolls: configValues.maxConcurrentActivityTaskPolls,
296
- maxConcurrentWorkflowTaskPolls: configValues.maxConcurrentWorkflowTaskPolls
297
- } ) );
298
- expect( Worker.create.mock.calls[0][0] ).not.toHaveProperty( 'shutdownForceTime' );
299
- expect( Worker.create.mock.calls[0][0] ).not.toHaveProperty( 'shutdownGraceTime' );
300
- expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [] } );
301
- expect( setupTelemetryMock ).toHaveBeenCalledWith( { worker: mockWorker } );
302
- expect( WorkerRunner ).toHaveBeenCalledWith( { worker: mockWorker, signal } );
303
- } );
304
-
305
- it( 'publishes the catalog before creating the worker', async () => {
306
- const { Worker } = await import( '@temporalio/worker' );
307
-
308
- await bootWorker();
309
-
310
- // The worker never exists unless its catalog matches its own source code.
311
- expect( catalogPublisherInstance.run.mock.invocationCallOrder[0] )
312
- .toBeLessThan( Worker.create.mock.invocationCallOrder[0] );
313
- } );
314
-
315
- it( 'passes worker tuner instead of incompatible execution concurrency options', async () => {
316
- configValues.workerTuner = { tunerOptions: { targetMemoryUsage: 0.8, targetCpuUsage: 0.9 } };
317
- const { Worker } = await import( '@temporalio/worker' );
318
-
319
- await bootWorker();
320
-
321
- const workerOptions = Worker.create.mock.calls[0][0];
322
- expect( workerOptions ).toEqual( expect.objectContaining( { tuner: configValues.workerTuner } ) );
323
- expect( workerOptions ).not.toHaveProperty( 'maxConcurrentWorkflowTaskExecutions' );
324
- expect( workerOptions ).not.toHaveProperty( 'maxConcurrentActivityTaskExecutions' );
325
- } );
326
-
327
- it( 'enables TLS when an apiKey is set', async () => {
328
- configValues.apiKey = 'secret';
329
- const { NativeConnection } = await import( '@temporalio/worker' );
330
-
331
- await bootWorker();
332
-
333
- expect( NativeConnection.connect ).toHaveBeenCalledWith( expect.objectContaining( { apiKey: 'secret', tls: true } ) );
334
- } );
335
-
336
- it( 'routes the connection through the configured gRPC proxy', async () => {
337
- configValues.grpcProxy = 'proxy:1234';
338
- const { NativeConnection } = await import( '@temporalio/worker' );
339
-
340
- await bootWorker();
341
-
342
- expect( NativeConnection.connect ).toHaveBeenCalledWith( expect.objectContaining( {
343
- proxy: { type: 'http-connect', targetHost: 'proxy:1234' }
344
- } ) );
345
- } );
346
-
347
- it( 'passes configured shutdown durations to the worker', async () => {
348
- configValues.shutdownForceTime = '30s';
349
- configValues.shutdownGraceTime = '10s';
350
- const { Worker } = await import( '@temporalio/worker' );
351
-
352
- await bootWorker();
353
-
354
- expect( Worker.create ).toHaveBeenCalledWith( expect.objectContaining( {
355
- shutdownForceTime: '30s',
356
- shutdownGraceTime: '10s'
357
- } ) );
358
- } );
359
-
360
- it( 'stops advancing through startup once the signal aborts', async () => {
361
- const { NativeConnection } = await import( '@temporalio/worker' );
362
- loadHooksMock.mockImplementationOnce( async () => {
363
- interruption.controller.abort( new interruption.KillSignError( 'SIGTERM' ) );
364
- } );
365
-
366
- await importWorker();
367
- await waitForExit();
368
-
369
- expect( loadWorkflowsMock ).not.toHaveBeenCalled();
370
- expect( NativeConnection.connect ).not.toHaveBeenCalled();
371
- expect( mockLog.error ).not.toHaveBeenCalled();
372
- expect( process.exit ).toHaveBeenCalledWith( 0 );
373
- } );
374
- } );
375
-
376
- describe( 'clean termination', () => {
377
- it( 'shuts every service down and exits successfully on a kill signal', async () => {
378
- await bootWorker();
379
-
380
- interruption.controller.abort( new interruption.KillSignError( 'SIGTERM' ) );
381
- await waitForExit();
382
-
383
- expect( shutdownServicesMock ).toHaveBeenCalledOnce();
384
- expect( shutdownServicesMock ).toHaveBeenCalledWith( {
385
- workerRunner: workerRunnerInstance,
386
- connectionMonitor: connectionMonitorInstance,
387
- catalogPublisher: catalogPublisherInstance,
388
- connection: mockConnection
389
- } );
390
- expect( mockLog.error ).not.toHaveBeenCalled();
391
- expect( mainEventBusMock.emit ).not.toHaveBeenCalledWith( BusEventType.RUNTIME_ERROR, expect.anything() );
392
- expect( mockLog.info ).toHaveBeenCalledWith( 'Bye', { exitCode: 0 } );
393
- expect( process.exit ).toHaveBeenCalledWith( 0 );
394
- } );
395
-
396
- it( 'still exits cleanly when a service other than the worker failed to stop', async () => {
397
- await bootWorker();
398
- shutdownServicesMock.mockResolvedValueOnce( [ { service: 'connection', error: new Error( 'close failed' ) } ] );
399
-
400
- interruption.controller.abort( new interruption.KillSignError( 'SIGTERM' ) );
401
- await waitForExit();
402
-
403
- expect( mockLog.error ).not.toHaveBeenCalled();
404
- expect( process.exit ).toHaveBeenCalledWith( 0 );
405
- } );
406
-
407
- it( 'does not report the worker as terminated when a signal ended the run', async () => {
408
- await bootWorker();
409
-
410
- interruption.controller.abort( new interruption.KillSignError( 'SIGTERM' ) );
411
- await waitForExit();
412
-
413
- expect( mockLog.info ).not.toHaveBeenCalledWith( 'Worker terminated' );
414
- } );
415
-
416
- it( 'reports a terminated worker when the run ends without an abort', async () => {
417
- await bootWorker();
418
-
419
- promises.connectionMonitor.resolve();
420
- await waitForExit();
421
-
422
- expect( mockLog.info ).toHaveBeenCalledWith( 'Worker terminated' );
423
- expect( process.exit ).toHaveBeenCalledWith( 0 );
424
- } );
425
-
426
- it( 'flushes pending hooks before exiting', async () => {
427
- await bootWorker();
428
-
429
- interruption.controller.abort( new interruption.KillSignError( 'SIGTERM' ) );
430
- await waitForExit();
431
-
432
- expect( flushPendingHooksMock ).toHaveBeenCalledWith( 5000 );
433
- expect( shutdownServicesMock.mock.invocationCallOrder[0] )
434
- .toBeLessThan( flushPendingHooksMock.mock.invocationCallOrder[0] );
435
- expect( flushPendingHooksMock.mock.invocationCallOrder[0] )
436
- .toBeLessThan( process.exit.mock.invocationCallOrder[0] );
437
- } );
438
- } );
439
-
440
- describe( 'failures', () => {
441
- it( 'reports an uncaught error captured by the interruption handler', async () => {
442
- const cause = new Error( 'boom' );
443
- await bootWorker();
444
-
445
- const fault = new interruption.UncaughtError( 'uncaughtException', { cause } );
446
- interruption.controller.abort( fault );
447
- await waitForExit();
448
-
449
- expect( mockLog.error ).toHaveBeenCalledWith( 'Worker error', {
450
- error: expect.objectContaining( { name: 'UncaughtError', message: 'uncaughtException' } )
451
- } );
452
- expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.RUNTIME_ERROR, { error: fault } );
453
- expect( process.exit ).toHaveBeenCalledWith( 1 );
454
- } );
455
-
456
- it( 'exits with a failure when the worker never finished draining', async () => {
457
- await bootWorker();
458
- shutdownServicesMock.mockResolvedValueOnce( [ { service: 'worker', error: new Error( 'drain failed' ) } ] );
459
-
460
- interruption.controller.abort( new interruption.KillSignError( 'SIGTERM' ) );
461
- await waitForExit();
462
-
463
- expect( process.exit ).toHaveBeenCalledWith( 1 );
464
- } );
465
- it( 'reports a lost connection and shuts down', async () => {
466
- const error = new Error( 'connection lost' );
467
- await bootWorker();
468
-
469
- promises.connectionMonitor.reject( error );
470
- await waitForExit();
471
-
472
- expect( shutdownServicesMock ).toHaveBeenCalledOnce();
473
- expect( serializeErrorMock ).toHaveBeenCalledWith( error );
474
- expect( mockLog.error ).toHaveBeenCalledWith( 'Worker error', {
475
- error: expect.objectContaining( { message: 'connection lost' } )
476
- } );
477
- expect( mainEventBusMock.emit ).toHaveBeenCalledWith( BusEventType.RUNTIME_ERROR, { error } );
478
- expect( process.exit ).toHaveBeenCalledWith( 1 );
479
- } );
480
-
481
- it( 'reports a failed worker run', async () => {
482
- const error = new Error( 'worker crashed' );
483
- await bootWorker();
484
-
485
- promises.workerRunner.reject( error );
486
- await waitForExit();
487
-
488
- expect( shutdownServicesMock ).toHaveBeenCalledOnce();
489
- expect( mockLog.error ).toHaveBeenCalledWith( 'Worker error', {
490
- error: expect.objectContaining( { message: 'worker crashed' } )
491
- } );
492
- expect( process.exit ).toHaveBeenCalledWith( 1 );
493
- } );
494
-
495
- it( 'never creates the worker when publishing the catalog fails', async () => {
496
- const { Worker } = await import( '@temporalio/worker' );
497
- catalogPublisherInstance.run.mockRejectedValueOnce( new Error( 'catalog failed' ) );
498
-
499
- await importWorker();
500
- await waitForExit();
501
-
502
- expect( Worker.create ).not.toHaveBeenCalled();
503
- expect( workerRunnerInstance.start ).not.toHaveBeenCalled();
504
- expect( shutdownServicesMock ).toHaveBeenCalledOnce();
505
- expect( mockLog.error ).toHaveBeenCalledWith( 'Worker error', {
506
- error: expect.objectContaining( { message: 'catalog failed' } )
507
- } );
508
- expect( process.exit ).toHaveBeenCalledWith( 1 );
509
- } );
510
-
511
- it( 'hands the partially built services to shutdown when startup fails', async () => {
512
- const { Worker } = await import( '@temporalio/worker' );
513
- Worker.create.mockRejectedValueOnce( new Error( 'worker create failed' ) );
514
-
515
- await importWorker();
516
- await waitForExit();
517
-
518
- expect( workerRunnerInstance.start ).not.toHaveBeenCalled();
519
- expect( shutdownServicesMock ).toHaveBeenCalledWith( expect.objectContaining( {
520
- workerRunner: null,
521
- connection: mockConnection
522
- } ) );
523
- expect( process.exit ).toHaveBeenCalledWith( 1 );
524
- } );
525
- } );
526
- } );