@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.
- package/package.json +13 -7
- package/src/consts.js +5 -2
- package/src/errors.d.ts +12 -2
- package/src/errors.js +17 -3
- package/src/errors.spec.js +19 -0
- package/src/helpers/activity_options.js +21 -0
- package/src/helpers/activity_options.spec.js +55 -0
- package/src/helpers/error_serializer.js +236 -0
- package/src/helpers/error_serializer.spec.js +413 -0
- package/src/helpers/errors.js +55 -13
- package/src/helpers/errors.spec.js +166 -66
- package/src/helpers/fetch.js +1 -38
- package/src/helpers/fetch.spec.js +1 -39
- package/src/helpers/function.js +13 -0
- package/src/helpers/function.spec.js +23 -1
- package/src/helpers/redact.js +51 -0
- package/src/helpers/redact.spec.js +66 -0
- package/src/helpers/string.js +10 -0
- package/src/helpers/string.spec.js +36 -1
- package/src/hooks/index.d.ts +35 -44
- package/src/hooks/index.js +10 -7
- package/src/hooks/index.spec.js +34 -7
- package/src/hooks/pending_hooks.js +24 -0
- package/src/hooks/pending_hooks.spec.js +62 -0
- package/src/interface/evaluator.d.ts +10 -9
- package/src/interface/evaluator.js +1 -6
- package/src/interface/evaluator.spec.js +10 -10
- package/src/interface/index.js +2 -1
- package/src/interface/step.d.ts +16 -15
- package/src/interface/step.js +1 -6
- package/src/interface/step.spec.js +10 -10
- package/src/interface/validations/index.js +33 -53
- package/src/interface/validations/index.spec.js +25 -17
- package/src/interface/webhook.js +1 -2
- package/src/interface/webhook.spec.js +25 -19
- package/src/interface/workflow.d.ts +25 -18
- package/src/interface/workflow.js +24 -52
- package/src/interface/workflow.spec.js +109 -108
- package/src/interface/workflow_activity_options.js +2 -5
- package/src/interface/workflow_utils.d.ts +15 -0
- package/src/interface/workflow_utils.js +19 -0
- package/src/interface/workflow_utils.spec.js +82 -1
- package/src/temporal/client.js +47 -0
- package/src/temporal/client.spec.js +124 -0
- package/src/temporal/index.d.ts +32 -0
- package/src/temporal/index.js +1 -0
- package/src/tracing/trace_engine.js +9 -11
- package/src/tracing/trace_engine.spec.js +67 -28
- package/src/worker/catalog_workflow/catalog_job.js +3 -3
- package/src/worker/catalog_workflow/catalog_job.spec.js +1 -1
- package/src/worker/configs.js +0 -3
- package/src/worker/index.js +21 -8
- package/src/worker/index.spec.js +30 -10
- package/src/worker/interceptors/activity.js +32 -19
- package/src/worker/interceptors/activity.spec.js +71 -166
- package/src/worker/interceptors/workflow.js +53 -11
- package/src/worker/interceptors/workflow.spec.js +176 -49
- package/src/worker/loader/workflows.js +16 -3
- package/src/worker/loader/workflows.spec.js +37 -5
- package/src/worker/log_hooks.js +13 -10
- package/src/worker/log_hooks.spec.js +12 -4
- package/src/worker/sinks.js +3 -1
- package/src/worker/sinks.spec.js +21 -4
- package/src/worker/telemetry.js +2 -1
- package/src/worker/temporal_logger.js +33 -0
- package/src/worker/temporal_logger.spec.js +90 -0
- package/src/helpers/aggregations.js +0 -44
- package/src/helpers/aggregations.spec.js +0 -92
- package/src/tracing/tools/utils.js +0 -42
- package/src/tracing/tools/utils.spec.js +0 -83
- package/src/worker/proxy.js +0 -20
- 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 '
|
|
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
|
|
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
|
|
91
|
-
if ( action === EventAction.ADD_ATTR ) {
|
|
92
|
-
|
|
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
|
-
|
|
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
|
|
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: () => ( {
|
|
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()
|
|
91
|
-
process.env.OUTPUT_TRACE_LOCAL_ON = '
|
|
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( '
|
|
96
|
-
kind: 'step', name: '
|
|
97
|
-
traceInfo
|
|
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()
|
|
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: '
|
|
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()
|
|
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
|
-
|
|
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
|
} );
|
package/src/worker/configs.js
CHANGED
|
@@ -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;
|
package/src/worker/index.js
CHANGED
|
@@ -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( () =>
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
235
|
+
log.info( 'Flushing hook callbacks...' );
|
|
236
|
+
await flushPendingHooks();
|
|
237
|
+
log.info( 'Exiting...' );
|
|
238
|
+
setTimeout( () => process.exit( 1 ) );
|
|
226
239
|
} );
|