@outputai/core 0.8.2-next.013689b.0 → 0.8.2-next.0f9af4b.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 +3 -8
- package/src/bus.js +1 -1
- package/src/consts.js +5 -2
- package/src/helpers/component.js +12 -0
- package/src/helpers/component.spec.js +54 -0
- package/src/helpers/fetch.js +105 -0
- package/src/helpers/fetch.spec.js +203 -0
- package/src/helpers/function.js +15 -0
- package/src/helpers/function.spec.js +48 -0
- package/src/helpers/object.js +98 -0
- package/src/helpers/object.spec.js +377 -0
- package/src/helpers/promise.js +29 -0
- package/src/helpers/promise.spec.js +35 -0
- package/src/helpers/string.js +30 -0
- package/src/helpers/string.spec.js +64 -0
- package/src/interface/evaluator.js +14 -12
- package/src/interface/evaluator.spec.js +10 -6
- package/src/interface/index.d.ts +7 -6
- package/src/interface/index.js +2 -0
- package/src/interface/logger.d.ts +53 -0
- package/src/interface/logger.js +68 -0
- package/src/interface/logger.spec.js +138 -0
- package/src/interface/step.js +15 -12
- package/src/interface/step.spec.js +10 -6
- package/src/interface/webhook.d.ts +21 -2
- package/src/interface/workflow.js +85 -79
- package/src/interface/workflow.spec.js +11 -4
- package/src/internal_activities/index.js +38 -36
- package/src/internal_activities/index.spec.js +27 -4
- package/src/logger/development.js +2 -2
- package/src/logger/development.spec.js +19 -2
- package/src/logger/production.js +1 -1
- package/src/logger/production.spec.js +24 -5
- package/src/sdk/helpers/index.d.ts +1 -0
- package/src/sdk/helpers/index.js +1 -0
- package/src/sdk/helpers/objects.d.ts +51 -0
- package/src/sdk/helpers/objects.js +8 -0
- package/src/sdk/helpers/objects.spec.js +16 -0
- package/src/tracing/processors/s3/redis_client.spec.js +0 -6
- package/src/tracing/processors/s3/s3_client.spec.js +0 -6
- package/src/tracing/trace_engine.js +1 -1
- package/src/worker/catalog_workflow/catalog_job.js +1 -1
- package/src/worker/catalog_workflow/index.spec.js +8 -11
- package/src/worker/configs.js +1 -1
- package/src/worker/connection_monitor.js +1 -1
- package/src/worker/global_functions.js +14 -0
- package/src/worker/global_functions.spec.js +55 -0
- package/src/worker/index.js +4 -1
- package/src/worker/index.spec.js +7 -0
- package/src/worker/interceptors/activity.js +2 -2
- package/src/worker/interceptors/workflow.js +3 -3
- package/src/worker/interceptors/workflow.spec.js +1 -1
- package/src/worker/loader/matchers.js +1 -1
- package/src/worker/log_hooks.js +14 -0
- package/src/worker/log_hooks.spec.js +83 -2
- package/src/worker/sinks.js +7 -1
- package/src/worker/sinks.spec.js +203 -0
- package/src/internal_utils/component.js +0 -9
- package/src/utils/index.d.ts +0 -148
- package/src/utils/index.js +0 -1
- package/src/utils/utils.js +0 -307
- package/src/utils/utils.spec.js +0 -723
- /package/src/{internal_utils → helpers}/aggregations.js +0 -0
- /package/src/{internal_utils → helpers}/aggregations.spec.js +0 -0
- /package/src/{internal_utils → helpers}/errors.js +0 -0
- /package/src/{internal_utils → helpers}/errors.spec.js +0 -0
- /package/src/{internal_utils → helpers}/temporal_context.js +0 -0
- /package/src/{internal_utils → helpers}/temporal_context.spec.ts +0 -0
- /package/src/{internal_utils → helpers}/trace_info.js +0 -0
- /package/src/{internal_utils → helpers}/trace_info.spec.js +0 -0
- /package/src/{internal_utils → helpers}/workflow_context.js +0 -0
- /package/src/{internal_utils → helpers}/workflow_context.spec.js +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { FatalError } from '#errors';
|
|
2
2
|
import { fetch } from 'undici';
|
|
3
|
-
import { serializeFetchResponse, serializeBodyAndInferContentType } from '#
|
|
4
|
-
import { setMetadata } from '#internal_utils/component';
|
|
5
|
-
import { ComponentType } from '#consts';
|
|
3
|
+
import { serializeFetchResponse, serializeBodyAndInferContentType } from '#helpers/fetch';
|
|
6
4
|
import { createChildLogger } from '#logger';
|
|
7
5
|
import { getDestinations } from '#tracing';
|
|
6
|
+
import { createInternalStep } from '#helpers/component';
|
|
7
|
+
import { ACTIVITY_GET_TRACE_DESTINATIONS, ACTIVITY_SEND_HTTP_REQUEST } from '#consts';
|
|
8
8
|
|
|
9
9
|
const log = createChildLogger( 'HttpClient' );
|
|
10
10
|
|
|
@@ -20,41 +20,42 @@ const log = createChildLogger( 'HttpClient' );
|
|
|
20
20
|
* @returns {object} The serialized HTTP response
|
|
21
21
|
* @throws {FatalError}
|
|
22
22
|
*/
|
|
23
|
-
export const sendHttpRequest =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
export const sendHttpRequest = createInternalStep( {
|
|
24
|
+
name: ACTIVITY_SEND_HTTP_REQUEST,
|
|
25
|
+
handler: async ( { url, method, payload = undefined, headers = undefined, timeout = 30_000 } ) => {
|
|
26
|
+
const args = {
|
|
27
|
+
method,
|
|
28
|
+
headers: new Headers( headers ?? {} ),
|
|
29
|
+
signal: AbortSignal.timeout( timeout )
|
|
30
|
+
};
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const response = await ( async () => {
|
|
41
|
-
try {
|
|
42
|
-
return await fetch( url, args );
|
|
43
|
-
} catch ( e ) {
|
|
44
|
-
throw new FatalError( `${method} ${url} ${e.cause ?? e.message}` );
|
|
45
|
-
}
|
|
46
|
-
} )();
|
|
32
|
+
const methodsWithBody = [ 'DELETE', 'PATCH', 'POST', 'PUT', 'OPTIONS' ];
|
|
33
|
+
const hasBodyPayload = ![ undefined, null ].includes( payload );
|
|
34
|
+
if ( methodsWithBody.includes( method ) && hasBodyPayload ) {
|
|
35
|
+
const { body, contentType } = serializeBodyAndInferContentType( payload );
|
|
36
|
+
if ( contentType && !args.headers.has( 'content-type' ) ) {
|
|
37
|
+
args.headers.set( 'Content-Type', contentType );
|
|
38
|
+
}
|
|
39
|
+
Object.assign( args, { body } );
|
|
40
|
+
};
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
const response = await ( async () => {
|
|
43
|
+
try {
|
|
44
|
+
return await fetch( url, args );
|
|
45
|
+
} catch ( e ) {
|
|
46
|
+
throw new FatalError( `${method} ${url} ${e.cause ?? e.message}` );
|
|
47
|
+
}
|
|
48
|
+
} )();
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
throw new FatalError( `${method} ${url} ${response.status}` );
|
|
52
|
-
}
|
|
50
|
+
log.info( 'HTTP request completed', { url, method, status: response.status, statusText: response.statusText } );
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
};
|
|
52
|
+
if ( !response.ok ) {
|
|
53
|
+
throw new FatalError( `${method} ${url} ${response.status}` );
|
|
54
|
+
}
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
return serializeFetchResponse( response );
|
|
57
|
+
}
|
|
58
|
+
} );
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* Invokes a trace method that resolves all trace output paths based on the traceInfo
|
|
@@ -62,6 +63,7 @@ setMetadata( sendHttpRequest, { type: ComponentType.INTERNAL_STEP } );
|
|
|
62
63
|
* @param {object} traceInfo
|
|
63
64
|
* @returns {object} Information about enabled destinations
|
|
64
65
|
*/
|
|
65
|
-
export const getTraceDestinations =
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
export const getTraceDestinations = createInternalStep( {
|
|
67
|
+
name: ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
68
|
+
handler: traceInfo => getDestinations( traceInfo )
|
|
69
|
+
} );
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { MockAgent, setGlobalDispatcher } from 'undici';
|
|
3
3
|
import { FatalError } from '#errors';
|
|
4
|
-
import {
|
|
4
|
+
import { ACTIVITY_GET_TRACE_DESTINATIONS, ACTIVITY_SEND_HTTP_REQUEST } from '#consts';
|
|
5
|
+
import { serializeBodyAndInferContentType, serializeFetchResponse } from '#helpers/fetch';
|
|
5
6
|
import { getTraceDestinations, sendHttpRequest } from './index.js';
|
|
6
7
|
|
|
7
8
|
const getDestinationsMock = vi.hoisted( () => vi.fn() );
|
|
9
|
+
const createInternalStepMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
8
10
|
|
|
9
11
|
vi.mock( '#tracing', () => ( {
|
|
10
12
|
getDestinations: getDestinationsMock
|
|
@@ -15,9 +17,15 @@ vi.mock( '#logger', () => {
|
|
|
15
17
|
return { createChildLogger: vi.fn( () => log ) };
|
|
16
18
|
} );
|
|
17
19
|
|
|
18
|
-
vi.mock( '#
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
vi.mock( '#helpers/component', () => ( {
|
|
21
|
+
createInternalStep: createInternalStepMock
|
|
22
|
+
} ) );
|
|
23
|
+
|
|
24
|
+
vi.mock( '#helpers/string', () => ( {
|
|
25
|
+
isStringboolTrue: vi.fn( () => false )
|
|
26
|
+
} ) );
|
|
27
|
+
|
|
28
|
+
vi.mock( '#helpers/fetch', () => ( {
|
|
21
29
|
serializeBodyAndInferContentType: vi.fn(),
|
|
22
30
|
serializeFetchResponse: vi.fn()
|
|
23
31
|
} ) );
|
|
@@ -30,6 +38,21 @@ setGlobalDispatcher( mockAgent );
|
|
|
30
38
|
const url = 'https://growthx.ai';
|
|
31
39
|
const method = 'GET';
|
|
32
40
|
|
|
41
|
+
describe( 'internal_activities component registration', () => {
|
|
42
|
+
it( 'creates internal step components for exported activities', () => {
|
|
43
|
+
expect( createInternalStepMock ).toHaveBeenNthCalledWith( 1, {
|
|
44
|
+
name: ACTIVITY_SEND_HTTP_REQUEST,
|
|
45
|
+
handler: expect.any( Function )
|
|
46
|
+
} );
|
|
47
|
+
expect( createInternalStepMock ).toHaveBeenNthCalledWith( 2, {
|
|
48
|
+
name: ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
49
|
+
handler: expect.any( Function )
|
|
50
|
+
} );
|
|
51
|
+
expect( sendHttpRequest ).toBe( createInternalStepMock.mock.calls[0][0].handler );
|
|
52
|
+
expect( getTraceDestinations ).toBe( createInternalStepMock.mock.calls[1][0].handler );
|
|
53
|
+
} );
|
|
54
|
+
} );
|
|
55
|
+
|
|
33
56
|
describe( 'internal_activities/sendHttpRequest', () => {
|
|
34
57
|
beforeEach( async () => {
|
|
35
58
|
vi.restoreAllMocks();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { format, transports } from 'winston';
|
|
2
|
-
import {
|
|
2
|
+
import { shuffleArray, isPlainObject } from '#helpers/object';
|
|
3
3
|
|
|
4
4
|
/** Available colors enum */
|
|
5
5
|
const Color = {
|
|
@@ -47,7 +47,7 @@ const getColor = v =>
|
|
|
47
47
|
assignedColors.get( v ) ?? assignedColors.set( v, COLORS[assignedColors.size % COLORS.length] ).get( v );
|
|
48
48
|
|
|
49
49
|
export const options = {
|
|
50
|
-
level: 'debug',
|
|
50
|
+
level: process.env.OUTPUT_LOG_LEVEL ?? 'debug',
|
|
51
51
|
transports: [ new transports.Console() ],
|
|
52
52
|
format: format.combine(
|
|
53
53
|
format.colorize(),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
const LEVEL = Symbol.for( 'level' );
|
|
4
4
|
const MESSAGE = Symbol.for( 'message' );
|
|
5
5
|
|
|
6
|
-
vi.mock( '#
|
|
6
|
+
vi.mock( '#helpers/object', () => ( {
|
|
7
7
|
isPlainObject: v => Object.prototype.toString.call( v ) === '[object Object]',
|
|
8
8
|
shuffleArray: v => v
|
|
9
9
|
} ) );
|
|
@@ -14,6 +14,10 @@ const loadDevelopmentLogger = async () => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
describe( 'logger/development', () => {
|
|
17
|
+
afterEach( () => {
|
|
18
|
+
vi.unstubAllEnvs();
|
|
19
|
+
} );
|
|
20
|
+
|
|
17
21
|
describe( 'formatJson', () => {
|
|
18
22
|
it( 'formats nested plain objects and arrays', async () => {
|
|
19
23
|
const { formatJson } = await loadDevelopmentLogger();
|
|
@@ -40,6 +44,19 @@ describe( 'logger/development', () => {
|
|
|
40
44
|
} );
|
|
41
45
|
|
|
42
46
|
describe( 'options.format', () => {
|
|
47
|
+
it( 'uses debug level by default', async () => {
|
|
48
|
+
const { options } = await loadDevelopmentLogger();
|
|
49
|
+
|
|
50
|
+
expect( options.level ).toBe( 'debug' );
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
it( 'uses OUTPUT_LOG_LEVEL when configured', async () => {
|
|
54
|
+
vi.stubEnv( 'OUTPUT_LOG_LEVEL', 'http' );
|
|
55
|
+
const { options } = await loadDevelopmentLogger();
|
|
56
|
+
|
|
57
|
+
expect( options.level ).toBe( 'http' );
|
|
58
|
+
} );
|
|
59
|
+
|
|
43
60
|
it( 'formats level, namespace, message, and metadata fields', async () => {
|
|
44
61
|
const { options } = await loadDevelopmentLogger();
|
|
45
62
|
const info = options.format.transform( {
|
package/src/logger/production.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { options } from './production.js';
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
2
|
|
|
4
3
|
const LEVEL = Symbol.for( 'level' );
|
|
5
4
|
const MESSAGE = Symbol.for( 'message' );
|
|
6
5
|
|
|
6
|
+
const loadProductionLogger = async () => {
|
|
7
|
+
vi.resetModules();
|
|
8
|
+
return import( './production.js' );
|
|
9
|
+
};
|
|
10
|
+
|
|
7
11
|
describe( 'logger/production', () => {
|
|
8
|
-
|
|
12
|
+
afterEach( () => {
|
|
13
|
+
vi.unstubAllEnvs();
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
it( 'uses info level and default production metadata', async () => {
|
|
17
|
+
const { options } = await loadProductionLogger();
|
|
18
|
+
|
|
9
19
|
expect( options.level ).toBe( 'info' );
|
|
10
20
|
expect( options.defaultMeta ).toEqual( {
|
|
11
21
|
service: 'output-worker',
|
|
@@ -13,7 +23,15 @@ describe( 'logger/production', () => {
|
|
|
13
23
|
} );
|
|
14
24
|
} );
|
|
15
25
|
|
|
16
|
-
it( '
|
|
26
|
+
it( 'uses OUTPUT_LOG_LEVEL when configured', async () => {
|
|
27
|
+
vi.stubEnv( 'OUTPUT_LOG_LEVEL', 'debug' );
|
|
28
|
+
const { options } = await loadProductionLogger();
|
|
29
|
+
|
|
30
|
+
expect( options.level ).toBe( 'debug' );
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
it( 'formats logs as JSON with timestamp and metadata fields', async () => {
|
|
34
|
+
const { options } = await loadProductionLogger();
|
|
17
35
|
const info = options.format.transform( {
|
|
18
36
|
[LEVEL]: 'info',
|
|
19
37
|
level: 'info',
|
|
@@ -33,7 +51,8 @@ describe( 'logger/production', () => {
|
|
|
33
51
|
expect( output.timestamp ).toEqual( expect.stringMatching( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/u ) );
|
|
34
52
|
} );
|
|
35
53
|
|
|
36
|
-
it( 'includes error stack when formatting Error messages', () => {
|
|
54
|
+
it( 'includes error stack when formatting Error messages', async () => {
|
|
55
|
+
const { options } = await loadProductionLogger();
|
|
37
56
|
const error = new Error( 'boom' );
|
|
38
57
|
const info = options.format.transform( {
|
|
39
58
|
[LEVEL]: 'error',
|
package/src/sdk/helpers/index.js
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Tools to manipulate JS Objects */
|
|
2
|
+
export declare const Objects: {
|
|
3
|
+
/**
|
|
4
|
+
* Node safe clone implementation that doesn't use global structuredClone().
|
|
5
|
+
*
|
|
6
|
+
* Returns a cloned version of the object.
|
|
7
|
+
*
|
|
8
|
+
* Only clones static properties. Getters become static properties.
|
|
9
|
+
*
|
|
10
|
+
* @param object
|
|
11
|
+
*/
|
|
12
|
+
clone( object: object ): object,
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns true if the value is a plain object:
|
|
16
|
+
* - `{}`
|
|
17
|
+
* - `new Object()`
|
|
18
|
+
* - `Object.create(null)`
|
|
19
|
+
*
|
|
20
|
+
* @param object - The value to check.
|
|
21
|
+
* @returns Whether the value is a plain object.
|
|
22
|
+
*/
|
|
23
|
+
isPlainObject( object: unknown ): boolean,
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new object by merging object `b` onto object `a`, biased toward `b`:
|
|
27
|
+
* - Fields in `b` overwrite fields in `a`.
|
|
28
|
+
* - Fields in `b` that don't exist in `a` are created.
|
|
29
|
+
* - Fields in `a` that don't exist in `b` are left unchanged.
|
|
30
|
+
*
|
|
31
|
+
* @param a - The base object.
|
|
32
|
+
* @param b - The overriding object.
|
|
33
|
+
* @throws {Error} If either `a` or `b` is not a plain object.
|
|
34
|
+
* @returns A new merged object.
|
|
35
|
+
*/
|
|
36
|
+
deepMerge( a: object, b: object | null | undefined ): object,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates a new object by merging object `b` onto object `a`, biased toward `b`:
|
|
40
|
+
* - Fields in `b` that don't exist in `a` are created.
|
|
41
|
+
* - Fields in `a` that don't exist in `b` are left unchanged.
|
|
42
|
+
* - Fields in `a` and `b` are passed as arguments to the resolve function (a,b) and its return assigns the new value.
|
|
43
|
+
*
|
|
44
|
+
* @param a - The base object.
|
|
45
|
+
* @param b - The overriding object.
|
|
46
|
+
* @param resolver - The resolver function.
|
|
47
|
+
* @throws {Error} If either `a` or `b` is not a plain object.
|
|
48
|
+
* @returns A new merged object.
|
|
49
|
+
*/
|
|
50
|
+
deepMergeWithResolver( a: object, b: object | null | undefined, resolver: ( a: unknown, b: unknown ) => unknown ): object
|
|
51
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Objects } from './objects.js';
|
|
3
|
+
import { Objects as ObjectsFromIndex } from './index.js';
|
|
4
|
+
import { clone, deepMerge, deepMergeWithResolver, isPlainObject } from '#helpers/object';
|
|
5
|
+
|
|
6
|
+
describe( 'Objects', () => {
|
|
7
|
+
it( 'exports the same functions from the object helper module', () => {
|
|
8
|
+
expect( Objects ).toBe( ObjectsFromIndex );
|
|
9
|
+
expect( Objects ).toEqual( {
|
|
10
|
+
clone,
|
|
11
|
+
deepMerge,
|
|
12
|
+
deepMergeWithResolver,
|
|
13
|
+
isPlainObject
|
|
14
|
+
} );
|
|
15
|
+
} );
|
|
16
|
+
} );
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Storage } from '#async_storage';
|
|
2
2
|
import { EventEmitter } from 'node:events';
|
|
3
3
|
import { serializeError } from './tools/utils.js';
|
|
4
|
-
import { isStringboolTrue } from '#
|
|
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 { ComponentType } from '#consts';
|
|
@@ -3,7 +3,7 @@ import { WorkflowExecutionAlreadyStartedError, WorkflowIdConflictPolicy } from '
|
|
|
3
3
|
import { WORKFLOW_CATALOG } from '#consts';
|
|
4
4
|
import { catalogId, taskQueue } from '../configs.js';
|
|
5
5
|
import { createChildLogger } from '#logger';
|
|
6
|
-
import { CancellablePromise } from '#
|
|
6
|
+
import { CancellablePromise } from '#helpers/promise';
|
|
7
7
|
|
|
8
8
|
const log = createChildLogger( 'Catalog' );
|
|
9
9
|
|
|
@@ -7,9 +7,6 @@ vi.mock( '#consts', () => ( {
|
|
|
7
7
|
METADATA_ACCESS_SYMBOL
|
|
8
8
|
} ) );
|
|
9
9
|
|
|
10
|
-
const setMetadata = ( target, values ) =>
|
|
11
|
-
Object.defineProperty( target, METADATA_ACCESS_SYMBOL, { value: values, writable: false, enumerable: false, configurable: false } );
|
|
12
|
-
|
|
13
10
|
describe( 'createCatalog', () => {
|
|
14
11
|
it( 'builds catalog with activities grouped by workflow path and returns Catalog with CatalogWorkflow entries', async () => {
|
|
15
12
|
const { createCatalog } = await import( './index.js' );
|
|
@@ -32,40 +29,40 @@ describe( 'createCatalog', () => {
|
|
|
32
29
|
];
|
|
33
30
|
|
|
34
31
|
const activity1 = () => {};
|
|
35
|
-
|
|
32
|
+
activity1[METADATA_ACCESS_SYMBOL] = {
|
|
36
33
|
name: 'A1',
|
|
37
34
|
path: '/flows/flow1#A1',
|
|
38
35
|
description: 'desc-a1',
|
|
39
36
|
inputSchema: z.object( { in: z.literal( 'a1' ) } ),
|
|
40
37
|
outputSchema: z.object( { out: z.literal( 'a1' ) } )
|
|
41
|
-
}
|
|
38
|
+
};
|
|
42
39
|
|
|
43
40
|
const activity2 = () => {};
|
|
44
|
-
|
|
41
|
+
activity2[METADATA_ACCESS_SYMBOL] = {
|
|
45
42
|
name: 'A2',
|
|
46
43
|
path: '/flows/flow1#A2',
|
|
47
44
|
description: 'desc-a2',
|
|
48
45
|
inputSchema: z.object( { in: z.literal( 'a2' ) } ),
|
|
49
46
|
outputSchema: z.object( { out: z.literal( 'a2' ) } )
|
|
50
|
-
}
|
|
47
|
+
};
|
|
51
48
|
|
|
52
49
|
const activity3 = () => {};
|
|
53
|
-
|
|
50
|
+
activity3[METADATA_ACCESS_SYMBOL] = {
|
|
54
51
|
name: 'B1',
|
|
55
52
|
path: '/flows/flow2#B1',
|
|
56
53
|
description: 'desc-b1',
|
|
57
54
|
inputSchema: z.object( { in: z.literal( 'b1' ) } ),
|
|
58
55
|
outputSchema: z.object( { out: z.literal( 'b1' ) } )
|
|
59
|
-
}
|
|
56
|
+
};
|
|
60
57
|
|
|
61
58
|
const activity4 = () => {};
|
|
62
|
-
|
|
59
|
+
activity4[METADATA_ACCESS_SYMBOL] = {
|
|
63
60
|
name: 'X',
|
|
64
61
|
path: '/other#X',
|
|
65
62
|
description: 'desc-x',
|
|
66
63
|
inputSchema: z.object( { in: z.literal( 'x' ) } ),
|
|
67
64
|
outputSchema: z.object( { out: z.literal( 'x' ) } )
|
|
68
|
-
}
|
|
65
|
+
};
|
|
69
66
|
|
|
70
67
|
const activities = {
|
|
71
68
|
'/flows/flow1#A1': activity1,
|
package/src/worker/configs.js
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { messageBus } from '#bus';
|
|
2
|
+
import { ACTIVITY_LOGGER_SYMBOL, BusEventType } from '#consts';
|
|
3
|
+
import { activityInfo as activityInfoFn } from '@temporalio/activity';
|
|
4
|
+
import { assignImmutableProperty } from '#helpers/object';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sets global functions on globalThis
|
|
8
|
+
*/
|
|
9
|
+
export const bindGlobalFunctions = () => {
|
|
10
|
+
/** Defines the activity logger function, accessible in activity context via logger interface */
|
|
11
|
+
assignImmutableProperty( globalThis, ACTIVITY_LOGGER_SYMBOL, ( { level, message, metadata } ) =>
|
|
12
|
+
messageBus.emit( BusEventType.ACTIVITY_LOG, { level, message, metadata, activityInfo: activityInfoFn() } )
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
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 messageBusMock = 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', () => ( { messageBus: messageBusMock } ) );
|
|
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( messageBusMock.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
|
+
} );
|
package/src/worker/index.js
CHANGED
|
@@ -17,7 +17,8 @@ import { messageBus } from '#bus';
|
|
|
17
17
|
import { BusEventType } from '#consts';
|
|
18
18
|
import { setupTelemetry } from './telemetry.js';
|
|
19
19
|
import { TemporalConnectionMonitor } from './connection_monitor.js';
|
|
20
|
-
import {
|
|
20
|
+
import { bindGlobalFunctions } from './global_functions.js';
|
|
21
|
+
import { runOnce } from '#helpers/function';
|
|
21
22
|
|
|
22
23
|
import './log_hooks.js';
|
|
23
24
|
|
|
@@ -76,6 +77,8 @@ const execute = async () => {
|
|
|
76
77
|
log.info( 'Using gRPC proxy', { targetHost: grpcProxy } );
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
bindGlobalFunctions();
|
|
81
|
+
|
|
79
82
|
state.connection = await NativeConnection.connect( { address, tls: Boolean( apiKey ), apiKey, proxy } );
|
|
80
83
|
|
|
81
84
|
log.info( 'Creating connection monitor...' );
|
package/src/worker/index.spec.js
CHANGED
|
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
catalogJobInstance,
|
|
5
|
+
bindGlobalFunctionsMock,
|
|
5
6
|
configValues,
|
|
6
7
|
connectionMonitorInstance,
|
|
7
8
|
createCatalogMock,
|
|
@@ -98,6 +99,7 @@ const {
|
|
|
98
99
|
|
|
99
100
|
return {
|
|
100
101
|
catalogJobInstance,
|
|
102
|
+
bindGlobalFunctionsMock: vi.fn(),
|
|
101
103
|
configValues,
|
|
102
104
|
connectionMonitorInstance,
|
|
103
105
|
createCatalogMock: vi.fn().mockReturnValue( { workflows: [], activities: {} } ),
|
|
@@ -145,6 +147,7 @@ vi.mock( './interceptors/index.js', () => ( { initInterceptors: initInterceptors
|
|
|
145
147
|
vi.mock( './proxy.js', () => ( { bootstrapFetchProxy: vi.fn() } ) );
|
|
146
148
|
vi.mock( './telemetry.js', () => ( { setupTelemetry: setupTelemetryMock } ) );
|
|
147
149
|
vi.mock( './interruption.js', () => ( { setupInterruptionHandler: setupInterruptionHandlerMock } ) );
|
|
150
|
+
vi.mock( './global_functions.js', () => ( { bindGlobalFunctions: bindGlobalFunctionsMock } ) );
|
|
148
151
|
vi.mock( './connection_monitor.js', () => ( {
|
|
149
152
|
TemporalConnectionMonitor: vi.fn( function () {
|
|
150
153
|
return connectionMonitorInstance;
|
|
@@ -222,6 +225,10 @@ describe( 'worker/index', () => {
|
|
|
222
225
|
apiKey: undefined,
|
|
223
226
|
proxy: undefined
|
|
224
227
|
} );
|
|
228
|
+
expect( bindGlobalFunctionsMock ).toHaveBeenCalledTimes( 1 );
|
|
229
|
+
expect( bindGlobalFunctionsMock.mock.invocationCallOrder[0] ).toBeLessThan(
|
|
230
|
+
NativeConnection.connect.mock.invocationCallOrder[0]
|
|
231
|
+
);
|
|
225
232
|
expect( TemporalConnectionMonitor ).toHaveBeenCalledWith( mockConnection );
|
|
226
233
|
expect( CatalogJob ).toHaveBeenCalledWith( {
|
|
227
234
|
connection: mockConnection,
|
|
@@ -5,8 +5,8 @@ import { headersToObject } from './headers.js';
|
|
|
5
5
|
import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL } from '#consts';
|
|
6
6
|
import { activityHeartbeatEnabled, activityHeartbeatIntervalMs } from '../configs.js';
|
|
7
7
|
import { messageBus } from '#bus';
|
|
8
|
-
import { aggregateAttributes } from '#
|
|
9
|
-
import { buildApplicationFailureWithDetails } from '#
|
|
8
|
+
import { aggregateAttributes } from '#helpers/aggregations';
|
|
9
|
+
import { buildApplicationFailureWithDetails } from '#helpers/errors';
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
12
|
This interceptor wraps every activity execution with cross-cutting concerns:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
|
|
2
2
|
import { workflowInfo, proxySinks, ContinueAsNew, isCancellation } from '@temporalio/workflow';
|
|
3
3
|
import { memoToHeaders } from './headers.js';
|
|
4
|
-
import { deepMerge } from '#
|
|
5
|
-
import { buildApplicationFailureWithDetails } from '#
|
|
4
|
+
import { deepMerge } from '#helpers/object';
|
|
5
|
+
import { buildApplicationFailureWithDetails } from '#helpers/errors';
|
|
6
6
|
import { METADATA_ACCESS_SYMBOL, WorkflowSpecialOutput } from '#consts';
|
|
7
|
-
import { createWorkflowDetails } from '#
|
|
7
|
+
import { createWorkflowDetails } from '#helpers/temporal_context';
|
|
8
8
|
|
|
9
9
|
// this is a dynamic generated file with activity configs overwrites
|
|
10
10
|
import stepOptions from '../temp/__activity_options.js';
|
|
@@ -54,7 +54,7 @@ const memoToHeadersMock = vi.fn( memo => ( memo ? { ...memo, __asHeaders: true }
|
|
|
54
54
|
vi.mock( './headers.js', () => ( { memoToHeaders: ( ...args ) => memoToHeadersMock( ...args ) } ) );
|
|
55
55
|
|
|
56
56
|
const deepMergeMock = vi.fn( ( a, b ) => ( { ...( a || {} ), ...( b || {} ) } ) );
|
|
57
|
-
vi.mock( '#
|
|
57
|
+
vi.mock( '#helpers/object', () => ( { deepMerge: ( ...args ) => deepMergeMock( ...args ) } ) );
|
|
58
58
|
|
|
59
59
|
const stepOptionsDefault = {};
|
|
60
60
|
vi.mock( '../temp/__activity_options.js', () => ( { default: stepOptionsDefault } ) );
|