@outputai/core 0.10.1-next.f6a7c1a.0 → 0.11.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 (72) hide show
  1. package/package.json +13 -7
  2. package/src/consts.js +5 -2
  3. package/src/errors.d.ts +12 -2
  4. package/src/errors.js +17 -3
  5. package/src/errors.spec.js +19 -0
  6. package/src/helpers/activity_options.js +21 -0
  7. package/src/helpers/activity_options.spec.js +55 -0
  8. package/src/helpers/error_serializer.js +236 -0
  9. package/src/helpers/error_serializer.spec.js +413 -0
  10. package/src/helpers/errors.js +55 -13
  11. package/src/helpers/errors.spec.js +166 -66
  12. package/src/helpers/fetch.js +1 -38
  13. package/src/helpers/fetch.spec.js +1 -39
  14. package/src/helpers/function.js +13 -0
  15. package/src/helpers/function.spec.js +23 -1
  16. package/src/helpers/redact.js +51 -0
  17. package/src/helpers/redact.spec.js +66 -0
  18. package/src/helpers/string.js +10 -0
  19. package/src/helpers/string.spec.js +36 -1
  20. package/src/hooks/index.d.ts +35 -44
  21. package/src/hooks/index.js +10 -7
  22. package/src/hooks/index.spec.js +34 -7
  23. package/src/hooks/pending_hooks.js +24 -0
  24. package/src/hooks/pending_hooks.spec.js +62 -0
  25. package/src/interface/evaluator.d.ts +10 -9
  26. package/src/interface/evaluator.js +1 -6
  27. package/src/interface/evaluator.spec.js +10 -10
  28. package/src/interface/index.js +2 -1
  29. package/src/interface/step.d.ts +16 -15
  30. package/src/interface/step.js +1 -6
  31. package/src/interface/step.spec.js +10 -10
  32. package/src/interface/validations/index.js +33 -53
  33. package/src/interface/validations/index.spec.js +25 -17
  34. package/src/interface/webhook.js +1 -2
  35. package/src/interface/webhook.spec.js +25 -19
  36. package/src/interface/workflow.d.ts +25 -18
  37. package/src/interface/workflow.js +24 -52
  38. package/src/interface/workflow.spec.js +109 -108
  39. package/src/interface/workflow_activity_options.js +2 -5
  40. package/src/interface/workflow_utils.d.ts +15 -0
  41. package/src/interface/workflow_utils.js +19 -0
  42. package/src/interface/workflow_utils.spec.js +82 -1
  43. package/src/temporal/client.js +47 -0
  44. package/src/temporal/client.spec.js +124 -0
  45. package/src/temporal/index.d.ts +32 -0
  46. package/src/temporal/index.js +1 -0
  47. package/src/tracing/trace_engine.js +9 -11
  48. package/src/tracing/trace_engine.spec.js +67 -28
  49. package/src/worker/catalog_workflow/catalog_job.js +3 -3
  50. package/src/worker/catalog_workflow/catalog_job.spec.js +1 -1
  51. package/src/worker/configs.js +0 -3
  52. package/src/worker/index.js +21 -8
  53. package/src/worker/index.spec.js +30 -10
  54. package/src/worker/interceptors/activity.js +32 -19
  55. package/src/worker/interceptors/activity.spec.js +71 -166
  56. package/src/worker/interceptors/workflow.js +53 -11
  57. package/src/worker/interceptors/workflow.spec.js +176 -49
  58. package/src/worker/loader/workflows.js +16 -3
  59. package/src/worker/loader/workflows.spec.js +37 -5
  60. package/src/worker/log_hooks.js +13 -10
  61. package/src/worker/log_hooks.spec.js +12 -4
  62. package/src/worker/sinks.js +3 -1
  63. package/src/worker/sinks.spec.js +21 -4
  64. package/src/worker/telemetry.js +2 -1
  65. package/src/worker/temporal_logger.js +33 -0
  66. package/src/worker/temporal_logger.spec.js +90 -0
  67. package/src/helpers/aggregations.js +0 -44
  68. package/src/helpers/aggregations.spec.js +0 -92
  69. package/src/tracing/tools/utils.js +0 -42
  70. package/src/tracing/tools/utils.spec.js +0 -83
  71. package/src/worker/proxy.js +0 -20
  72. package/src/worker/proxy.spec.js +0 -51
@@ -0,0 +1,47 @@
1
+ import { Client } from '@temporalio/client';
2
+ import { Storage } from '#async_storage';
3
+ import { FatalError } from '#errors';
4
+
5
+ // store the instance creation configs
6
+ const config = {
7
+ connection: null,
8
+ namespace: null
9
+ };
10
+
11
+ // store a single instance that is created on demand
12
+ const instance = {
13
+ client: null
14
+ };
15
+
16
+ // Adds the config necessary to init Clients
17
+ export const setupClientConfig = ( { connection, namespace } ) => {
18
+ config.connection = new Proxy( connection, {
19
+ get( target, property, receiver ) {
20
+ if ( property === 'close' ) {
21
+ return async () => {
22
+ throw new FatalError( 'Client connection cannot be closed. It is owned by the worker.' );
23
+ };
24
+ }
25
+ return Reflect.get( target, property, receiver );
26
+ }
27
+ } );
28
+ config.namespace = namespace;
29
+ };
30
+
31
+ /** Return a new Temporal client instance */
32
+ export const createTemporalClient = () => {
33
+ if ( !config.connection ) {
34
+ throw new FatalError( 'createTemporalClient() can only be called from within the worker runtime.' );
35
+ }
36
+ return new Client( { ...config } );
37
+ };
38
+
39
+ /** Get the workflow handle for the current workflow execution */
40
+ export const getCurrentWorkflowHandle = () => {
41
+ const ctx = Storage.load();
42
+ if ( !ctx || !config.connection ) {
43
+ throw new FatalError( 'getCurrentWorkflowHandle() can only be called from Temporal activities running in the worker.' );
44
+ }
45
+ const { workflowId, runId } = ctx.activityInfo.workflowExecution;
46
+ return ( instance.client ??= new Client( config ) ).workflow.getHandle( workflowId, runId );
47
+ };
@@ -0,0 +1,124 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const loadMock = vi.fn();
4
+ vi.mock( '#async_storage', () => ( {
5
+ Storage: { load: loadMock }
6
+ } ) );
7
+
8
+ const getHandleMock = vi.fn();
9
+ const clientConstructorMock = vi.fn( function () {
10
+ this.workflow = { getHandle: getHandleMock };
11
+ } );
12
+ vi.mock( '@temporalio/client', () => ( {
13
+ Client: clientConstructorMock
14
+ } ) );
15
+
16
+ const connection = {
17
+ fake: 'native-connection',
18
+ close: vi.fn().mockResolvedValue( undefined )
19
+ };
20
+ const activityContext = {
21
+ activityInfo: {
22
+ workflowExecution: {
23
+ workflowId: 'workflow-1',
24
+ runId: 'run-1'
25
+ }
26
+ }
27
+ };
28
+
29
+ describe( 'Temporal client', () => {
30
+ beforeEach( () => {
31
+ vi.clearAllMocks();
32
+ vi.resetModules();
33
+ } );
34
+
35
+ describe( 'createTemporalClient', () => {
36
+ it( 'throws a FatalError outside the worker runtime', async () => {
37
+ const { createTemporalClient } = await import( './client.js' );
38
+ const { FatalError } = await import( '#errors' );
39
+
40
+ expect( () => createTemporalClient() ).toThrow( FatalError );
41
+ } );
42
+
43
+ it( 'creates a client with the worker connection and namespace', async () => {
44
+ const { createTemporalClient, setupClientConfig } = await import( './client.js' );
45
+ setupClientConfig( { connection, namespace: 'namespace-1' } );
46
+
47
+ const client = createTemporalClient();
48
+
49
+ expect( clientConstructorMock ).toHaveBeenCalledWith( {
50
+ connection: expect.anything(),
51
+ namespace: 'namespace-1'
52
+ } );
53
+ expect( clientConstructorMock.mock.calls[0][0].connection ).not.toBe( connection );
54
+ expect( client ).toBeInstanceOf( clientConstructorMock );
55
+ } );
56
+
57
+ it( 'delegates connection access but prevents closing the worker connection', async () => {
58
+ const { createTemporalClient, setupClientConfig } = await import( './client.js' );
59
+ const { FatalError } = await import( '#errors' );
60
+ setupClientConfig( { connection, namespace: 'namespace-1' } );
61
+
62
+ createTemporalClient();
63
+ const clientConnection = clientConstructorMock.mock.calls[0][0].connection;
64
+
65
+ expect( clientConnection.fake ).toBe( 'native-connection' );
66
+ await expect( clientConnection.close() ).rejects.toThrow( FatalError );
67
+ expect( connection.close ).not.toHaveBeenCalled();
68
+ } );
69
+
70
+ it( 'returns a new client on each call', async () => {
71
+ const { createTemporalClient, setupClientConfig } = await import( './client.js' );
72
+ setupClientConfig( { connection, namespace: 'namespace-1' } );
73
+
74
+ expect( createTemporalClient() ).not.toBe( createTemporalClient() );
75
+ expect( clientConstructorMock ).toHaveBeenCalledTimes( 2 );
76
+ } );
77
+ } );
78
+
79
+ describe( 'getCurrentWorkflowHandle', () => {
80
+ it( 'throws a FatalError outside a Temporal activity context', async () => {
81
+ loadMock.mockReturnValue( undefined );
82
+ const { getCurrentWorkflowHandle, setupClientConfig } = await import( './client.js' );
83
+ const { FatalError } = await import( '#errors' );
84
+ setupClientConfig( { connection, namespace: 'namespace-1' } );
85
+
86
+ expect( () => getCurrentWorkflowHandle() ).toThrow( FatalError );
87
+ } );
88
+
89
+ it( 'throws a FatalError outside the worker runtime', async () => {
90
+ loadMock.mockReturnValue( activityContext );
91
+ const { getCurrentWorkflowHandle } = await import( './client.js' );
92
+ const { FatalError } = await import( '#errors' );
93
+
94
+ expect( () => getCurrentWorkflowHandle() ).toThrow( FatalError );
95
+ } );
96
+
97
+ it( 'returns the handle for the current workflow execution', async () => {
98
+ const handle = { signal: vi.fn() };
99
+ loadMock.mockReturnValue( activityContext );
100
+ getHandleMock.mockReturnValue( handle );
101
+ const { getCurrentWorkflowHandle, setupClientConfig } = await import( './client.js' );
102
+ setupClientConfig( { connection, namespace: 'namespace-1' } );
103
+
104
+ expect( getCurrentWorkflowHandle() ).toBe( handle );
105
+ expect( clientConstructorMock ).toHaveBeenCalledWith( {
106
+ connection: expect.anything(),
107
+ namespace: 'namespace-1'
108
+ } );
109
+ expect( getHandleMock ).toHaveBeenCalledWith( 'workflow-1', 'run-1' );
110
+ } );
111
+
112
+ it( 'reuses its client across calls', async () => {
113
+ loadMock.mockReturnValue( activityContext );
114
+ const { getCurrentWorkflowHandle, setupClientConfig } = await import( './client.js' );
115
+ setupClientConfig( { connection, namespace: 'namespace-1' } );
116
+
117
+ getCurrentWorkflowHandle();
118
+ getCurrentWorkflowHandle();
119
+
120
+ expect( clientConstructorMock ).toHaveBeenCalledTimes( 1 );
121
+ expect( getHandleMock ).toHaveBeenCalledTimes( 2 );
122
+ } );
123
+ } );
124
+ } );
@@ -0,0 +1,32 @@
1
+ import type { Client, WorkflowHandle } from '@temporalio/client';
2
+
3
+ /**
4
+ * Worker-side access to the Temporal layer.
5
+ *
6
+ * `createTemporalClient()` is available after the worker establishes its Temporal
7
+ * connection. `getCurrentWorkflowHandle()` is only available from Temporal Activities.
8
+ *
9
+ * > [!WARNING]
10
+ * > These are not Temporal sandbox safe, do not import in workflows directly.
11
+ * > These require the worker runtime, so in unit tests, mock the functions.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ /**
17
+ * Creates a Temporal [Client](https://typescript.temporal.io/api/classes/client.Client)
18
+ * instance with the same connection and namespace as the worker.
19
+ *
20
+ * @remarks
21
+ * - Only available after the worker establishes its Temporal connection.
22
+ */
23
+ export declare const createTemporalClient: () => Client;
24
+
25
+ /**
26
+ * Return the [WorkflowHandle](https://typescript.temporal.io/api/interfaces/client.WorkflowHandle)
27
+ * for the current workflow execution.
28
+ *
29
+ * @remarks
30
+ * - Only available from a Temporal Activity running in the worker.
31
+ */
32
+ export declare const getCurrentWorkflowHandle: () => WorkflowHandle;
@@ -0,0 +1 @@
1
+ export { createTemporalClient, getCurrentWorkflowHandle } from './client.js';
@@ -1,12 +1,13 @@
1
1
  import { Storage } from '#async_storage';
2
2
  import { EventEmitter } from 'node:events';
3
- import { serializeError } from './tools/utils.js';
3
+ import { serializeError } from '#helpers/error_serializer';
4
4
  import { isStringboolTrue } from '#helpers/string';
5
5
  import * as localProcessor from './processors/local/index.js';
6
6
  import * as s3Processor from './processors/s3/index.js';
7
7
  import { createChildLogger } from '#logger';
8
8
  import { EventAction } from './trace_consts.js';
9
9
  import { BaseAttribute } from './trace_attribute.js';
10
+ import { TransparentFatalError } from '#errors';
10
11
 
11
12
  const log = createChildLogger( 'Tracing' );
12
13
 
@@ -51,7 +52,7 @@ export const init = async () => {
51
52
  try {
52
53
  await p.exec( ...args );
53
54
  } catch ( error ) {
54
- log.error( 'Processor execution error', { processor: p.name, error: error.message, stack: error.stack } );
55
+ log.error( 'Processor execution error', { processor: p.name, error: serializeError( error ) } );
55
56
  }
56
57
  } );
57
58
  }
@@ -87,15 +88,12 @@ export const addEventAction = ( action, { kind, name, id, parentId, details, tra
87
88
  export function addEventActionWithContext( action, options ) {
88
89
  const storeContent = Storage.load();
89
90
  if ( storeContent ) { // If there is no storageContext this was not called from a Temporal environment
90
- const { parentId, traceInfo, addAttribute } = storeContent;
91
- if ( action === EventAction.ADD_ATTR ) {
92
- const attribute = options.details;
93
- if ( !( attribute instanceof BaseAttribute ) ) {
94
- throw new Error( `Event ${EventAction.ADD_ATTR} argument is not a BaseAttribute instance` );
95
- } else {
96
- addAttribute( options.details );
97
- }
91
+ const { parentId, traceInfo } = storeContent;
92
+ if ( action === EventAction.ADD_ATTR && !( options.details instanceof BaseAttribute ) ) {
93
+ throw new Error( `Event ${EventAction.ADD_ATTR} argument is not a BaseAttribute instance` );
98
94
  }
99
- addEventAction( action, { ...options, parentId, traceInfo } );
95
+ /** Unwraps Transparent error set in details so it behaves like in the interceptors */
96
+ const details = options.details instanceof TransparentFatalError ? options.details.cause : options.details;
97
+ addEventAction( action, { ...options, details, parentId, traceInfo } );
100
98
  }
101
99
  };
@@ -5,10 +5,15 @@ vi.mock( '#async_storage', () => ( {
5
5
  Storage: { load: storageLoadMock }
6
6
  } ) );
7
7
 
8
- const logWarnMock = vi.fn();
8
+ const serializeErrorMock = vi.fn( error => ( {
9
+ name: error.name,
10
+ message: error.message
11
+ } ) );
12
+ vi.mock( '#helpers/error_serializer', () => ( { serializeError: serializeErrorMock } ) );
13
+
9
14
  const logErrorMock = vi.fn();
10
15
  vi.mock( '#logger', () => ( {
11
- createChildLogger: () => ( { warn: logWarnMock, error: logErrorMock } )
16
+ createChildLogger: () => ( { error: logErrorMock } )
12
17
  } ) );
13
18
 
14
19
  const localInitMock = vi.fn( async () => {} );
@@ -71,6 +76,24 @@ describe( 'tracing/trace_engine', () => {
71
76
  expect( payload.traceInfo ).toBe( traceInfo );
72
77
  } );
73
78
 
79
+ it( 'init() logs processor failures with serialized stacks', async () => {
80
+ process.env.OUTPUT_TRACE_LOCAL_ON = '1';
81
+ const processorError = new Error( 'processor failed' );
82
+ localExecMock.mockRejectedValueOnce( processorError );
83
+ const { init, addEventAction } = await loadTraceEngine();
84
+ await init();
85
+
86
+ addEventAction( 'start', {
87
+ kind: 'step', name: 'N', id: '1', parentId: 'p', details: null, traceInfo
88
+ } );
89
+
90
+ await vi.waitFor( () => expect( logErrorMock ).toHaveBeenCalledWith( 'Processor execution error', {
91
+ processor: 'LOCAL',
92
+ error: { name: 'Error', message: 'processor failed' }
93
+ } ) );
94
+ expect( serializeErrorMock ).toHaveBeenCalledWith( processorError );
95
+ } );
96
+
74
97
  it( 'addEventAction() emits an entry consumed by processors', async () => {
75
98
  process.env.OUTPUT_TRACE_LOCAL_ON = 'on';
76
99
  const { init, addEventAction } = await loadTraceEngine();
@@ -87,34 +110,34 @@ describe( 'tracing/trace_engine', () => {
87
110
  expect( payload.entry.details ).toBe( 'done' );
88
111
  } );
89
112
 
90
- it( 'addEventAction() does not emit when traceInfo is absent', async () => {
91
- process.env.OUTPUT_TRACE_LOCAL_ON = '1';
113
+ it( 'addEventAction() serializes error details before emitting', async () => {
114
+ process.env.OUTPUT_TRACE_LOCAL_ON = 'on';
92
115
  const { init, addEventAction } = await loadTraceEngine();
93
116
  await init();
117
+ const error = new TypeError( 'step failed' );
94
118
 
95
- addEventAction( 'start', {
96
- kind: 'step', name: 'X', id: '1', parentId: 'p', details: {},
97
- traceInfo: undefined
119
+ addEventAction( 'error', {
120
+ kind: 'step', name: 'S', id: '3', parentId: 'p3', details: error,
121
+ traceInfo
122
+ } );
123
+
124
+ expect( serializeErrorMock ).toHaveBeenCalledWith( error );
125
+ expect( localExecMock.mock.calls[0][0].entry.details ).toEqual( {
126
+ name: 'TypeError',
127
+ message: 'step failed'
98
128
  } );
99
- expect( localExecMock ).not.toHaveBeenCalled();
100
129
  } );
101
130
 
102
- it( 'addEventAction() emits when kind is INTERNAL_STEP', async () => {
131
+ it( 'addEventAction() does not emit when traceInfo is absent', async () => {
103
132
  process.env.OUTPUT_TRACE_LOCAL_ON = '1';
104
133
  const { init, addEventAction } = await loadTraceEngine();
105
134
  await init();
106
135
 
107
136
  addEventAction( 'start', {
108
- kind: 'internal_step', name: 'Internal', id: '1', parentId: 'p', details: {},
109
- traceInfo
110
- } );
111
- expect( localExecMock ).toHaveBeenCalledTimes( 1 );
112
- expect( localExecMock.mock.calls[0][0].entry ).toMatchObject( {
113
- kind: 'internal_step',
114
- name: 'Internal',
115
- id: '1',
116
- parentId: 'p'
137
+ kind: 'step', name: 'X', id: '1', parentId: 'p', details: {},
138
+ traceInfo: undefined
117
139
  } );
140
+ expect( localExecMock ).not.toHaveBeenCalled();
118
141
  } );
119
142
 
120
143
  it( 'addEventActionWithContext() uses storage when available', async () => {
@@ -135,13 +158,34 @@ describe( 'tracing/trace_engine', () => {
135
158
  expect( payload.entry.action ).toBe( 'tick' );
136
159
  } );
137
160
 
138
- it( 'addEventActionWithContext() records ADD_ATTR attributes through storage context', async () => {
161
+ it( 'addEventActionWithContext() unwraps transparent errors without mutating options', async () => {
139
162
  process.env.OUTPUT_TRACE_LOCAL_ON = 'true';
140
- const addAttributeMock = vi.fn();
141
163
  storageLoadMock.mockReturnValue( {
142
164
  parentId: 'ctx-p',
143
- traceInfo,
144
- addAttribute: addAttributeMock
165
+ traceInfo
166
+ } );
167
+ const { init, addEventActionWithContext } = await loadTraceEngine();
168
+ const { TransparentFatalError } = await import( '#errors' );
169
+ await init();
170
+
171
+ const cause = new TypeError( 'provider rejected request' );
172
+ const error = new TransparentFatalError( cause );
173
+ const options = { kind: 'llm', name: 'generate', id: 'llm-1', details: error };
174
+ addEventActionWithContext( 'error', options );
175
+
176
+ expect( options.details ).toBe( error );
177
+ expect( serializeErrorMock ).toHaveBeenCalledWith( cause );
178
+ expect( localExecMock.mock.calls[0][0].entry.details ).toEqual( {
179
+ name: 'TypeError',
180
+ message: 'provider rejected request'
181
+ } );
182
+ } );
183
+
184
+ it( 'addEventActionWithContext() emits validated ADD_ATTR trace entries', async () => {
185
+ process.env.OUTPUT_TRACE_LOCAL_ON = 'true';
186
+ storageLoadMock.mockReturnValue( {
187
+ parentId: 'ctx-p',
188
+ traceInfo
145
189
  } );
146
190
  const { init, addEventActionWithContext } = await loadTraceEngine();
147
191
  const { EventAction } = await import( './trace_consts.js' );
@@ -151,8 +195,6 @@ describe( 'tracing/trace_engine', () => {
151
195
  const attribute = new Attribute.HTTPRequestCount( 'https://example.test', 'req-1' );
152
196
  addEventActionWithContext( EventAction.ADD_ATTR, { kind: 'http', name: 'request', id: 'req-1', details: attribute } );
153
197
 
154
- expect( addAttributeMock ).toHaveBeenCalledTimes( 1 );
155
- expect( addAttributeMock ).toHaveBeenCalledWith( attribute );
156
198
  expect( localExecMock ).toHaveBeenCalledTimes( 1 );
157
199
  expect( localExecMock.mock.calls[0][0] ).toEqual( {
158
200
  traceInfo,
@@ -170,11 +212,9 @@ describe( 'tracing/trace_engine', () => {
170
212
 
171
213
  it( 'addEventActionWithContext() throws on invalid ADD_ATTR signal payloads', async () => {
172
214
  process.env.OUTPUT_TRACE_LOCAL_ON = 'true';
173
- const addAttributeMock = vi.fn();
174
215
  storageLoadMock.mockReturnValue( {
175
216
  parentId: 'ctx-p',
176
- traceInfo,
177
- addAttribute: addAttributeMock
217
+ traceInfo
178
218
  } );
179
219
  const { init, addEventActionWithContext } = await loadTraceEngine();
180
220
  const { EventAction } = await import( './trace_consts.js' );
@@ -186,7 +226,6 @@ describe( 'tracing/trace_engine', () => {
186
226
  { kind: 'http', name: 'request', id: 'req-1', details: invalidAttribute }
187
227
  ) ).toThrow( /not a BaseAttribute instance/ );
188
228
 
189
- expect( addAttributeMock ).not.toHaveBeenCalled();
190
229
  expect( localExecMock ).not.toHaveBeenCalled();
191
230
  } );
192
231
 
@@ -38,7 +38,7 @@ export class CatalogJob {
38
38
  throw e;
39
39
  }
40
40
  if ( !( e instanceof WorkflowNotFoundError ) ) {
41
- log.warn( 'Error describing catalog workflow', { error: e } );
41
+ log.warn( 'Error describing catalog workflow', { error: e.message } );
42
42
  }
43
43
  return false;
44
44
  } );
@@ -52,7 +52,7 @@ export class CatalogJob {
52
52
  throw e;
53
53
  }
54
54
  if ( !( e instanceof WorkflowNotFoundError ) ) {
55
- log.warn( 'Error describing catalog workflow', { error: e } );
55
+ log.warn( 'Error describing catalog workflow', { error: e.message } );
56
56
  }
57
57
  return false;
58
58
  } );
@@ -66,7 +66,7 @@ export class CatalogJob {
66
66
  throw e;
67
67
  }
68
68
  if ( !( e instanceof WorkflowNotFoundError ) ) {
69
- log.warn( 'Error completing previous catalog workflow', { error: e } );
69
+ log.warn( 'Error completing previous catalog workflow', { error: e.message } );
70
70
  }
71
71
  } );
72
72
  };
@@ -121,7 +121,7 @@ describe( 'CatalogJob', () => {
121
121
 
122
122
  await job.run();
123
123
 
124
- expect( mockLog.warn ).toHaveBeenCalledWith( 'Error completing previous catalog workflow', { error: completeError } );
124
+ expect( mockLog.warn ).toHaveBeenCalledWith( 'Error completing previous catalog workflow', { error: completeError.message } );
125
125
  expect( workflowStartMock ).toHaveBeenCalledWith( 'catalog', expect.any( Object ) );
126
126
  expect( job.error ).toBeNull();
127
127
  } );
@@ -36,8 +36,6 @@ const envVarSchema = z.object( {
36
36
  OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 2 * 60 * 1000 ) ), // 2min
37
37
  // Whether to send activity heartbeats (enabled by default)
38
38
  OUTPUT_ACTIVITY_HEARTBEAT_ENABLED: z.transform( v => v === undefined ? true : isStringboolTrue( v ) ),
39
- // Time to allow for hooks to flush before shutdown
40
- OUTPUT_PROCESS_FAILURE_SHUTDOWN_DELAY: z.preprocess( coalesceEmptyString, z.coerce.number().int().positive().default( 3000 ) ),
41
39
  // Set temporal worker shutdown force time
42
40
  TEMPORAL_SHUTDOWN_FORCE_TIME: durationSchema,
43
41
  // Set temporal worker shutdown grace time
@@ -70,7 +68,6 @@ export const catalogId = envVars.OUTPUT_CATALOG_ID;
70
68
  export const workerTelemetryIntervalMs = envVars.OUTPUT_WORKER_TELEMETRY_INTERVAL_MS;
71
69
  export const activityHeartbeatIntervalMs = envVars.OUTPUT_ACTIVITY_HEARTBEAT_INTERVAL_MS;
72
70
  export const activityHeartbeatEnabled = envVars.OUTPUT_ACTIVITY_HEARTBEAT_ENABLED;
73
- export const processFailureShutdownDelay = envVars.OUTPUT_PROCESS_FAILURE_SHUTDOWN_DELAY;
74
71
  export const shutdownForceTime = envVars.TEMPORAL_SHUTDOWN_FORCE_TIME;
75
72
  export const shutdownGraceTime = envVars.TEMPORAL_SHUTDOWN_GRACE_TIME;
76
73
  export const grpcProxy = envVars.TEMPORAL_GRPC_PROXY;
@@ -12,13 +12,16 @@ import { initInterceptors } from './interceptors/index.js';
12
12
  import { createChildLogger } from '#logger';
13
13
  import { setupInterruptionHandler } from './interruption.js';
14
14
  import { CatalogJob } from './catalog_workflow/catalog_job.js';
15
- import { bootstrapFetchProxy } from './proxy.js';
16
15
  import { mainEventBus } from '#bus';
16
+ import { flushPendingHooks } from '#hooks/pending_hooks';
17
17
  import { BusEventType } from '#consts';
18
18
  import { setupTelemetry } from './telemetry.js';
19
19
  import { TemporalConnectionMonitor } from './connection_monitor.js';
20
20
  import { bindGlobalFunctions } from './global_functions.js';
21
+ import { setupClientConfig } from '#temporal/client';
21
22
  import { runOnce } from '#helpers/function';
23
+ import { serializeError } from '#helpers/error_serializer';
24
+ import { setupTemporalLogger } from './temporal_logger.js';
22
25
 
23
26
  import './log_hooks.js';
24
27
 
@@ -51,6 +54,9 @@ const state = {
51
54
  const callerDir = process.argv[2];
52
55
 
53
56
  const execute = async () => {
57
+ log.info( 'Setup Temporal Logger' );
58
+ setupTemporalLogger();
59
+
54
60
  log.info( 'Loading config...', { callerDir } );
55
61
  await loadHooks( callerDir );
56
62
 
@@ -61,7 +67,6 @@ const execute = async () => {
61
67
  const { activities } = await loadActivities( callerDir, workflows );
62
68
 
63
69
  mainEventBus.emit( BusEventType.WORKER_BEFORE_START );
64
- bootstrapFetchProxy();
65
70
 
66
71
  log.info( 'Initializing tracing...' );
67
72
  await initTracing();
@@ -82,6 +87,9 @@ const execute = async () => {
82
87
 
83
88
  state.connection = await NativeConnection.connect( { address, tls: Boolean( apiKey ), apiKey, proxy } );
84
89
 
90
+ // add configs for the /temporal endpoint
91
+ setupClientConfig( { connection: state.connection, namespace } );
92
+
85
93
  log.info( 'Creating connection monitor...' );
86
94
  state.connectionMonitor = new TemporalConnectionMonitor( state.connection );
87
95
 
@@ -214,13 +222,18 @@ execute()
214
222
  .catch( e => log.warn( 'Connection close error', { error: e.message } ) );
215
223
  }
216
224
  } )
217
- .then( () => log.info( 'Bye' ) )
218
- .catch( error => {
219
- log.error( 'Fatal error', { error: error.message, stack: error.stack } );
225
+ .then( async () => {
226
+ log.info( 'Flushing hook callbacks...' );
227
+ await flushPendingHooks();
228
+ log.info( 'Bye' );
229
+ } )
230
+ .catch( async error => {
231
+ log.error( 'Fatal error', { error: serializeError( error ) } );
220
232
 
221
233
  mainEventBus.emit( BusEventType.RUNTIME_ERROR, { error } );
222
234
 
223
- const timeToFlushEvent = configs.processFailureShutdownDelay;
224
- log.info( `Exiting in ${timeToFlushEvent}ms` );
225
- setTimeout( () => process.exit( 1 ), timeToFlushEvent );
235
+ log.info( 'Flushing hook callbacks...' );
236
+ await flushPendingHooks();
237
+ log.info( 'Exiting...' );
238
+ setTimeout( () => process.exit( 1 ) );
226
239
  } );