@outputai/core 0.8.2-next.e1cd79b.0 → 0.8.2-next.e658cc2.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 +11 -11
- 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/hooks/index.d.ts +102 -30
- package/src/hooks/index.js +16 -1
- package/src/hooks/index.spec.js +55 -1
- package/src/index.d.ts +2 -2
- package/src/interface/evaluator.d.ts +2 -2
- 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 +61 -0
- package/src/interface/logger.js +73 -0
- package/src/interface/logger.spec.js +172 -0
- package/src/interface/step.d.ts +1 -1
- 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.d.ts +2 -2
- package/src/interface/workflow.js +85 -78
- package/src/interface/workflow.spec.js +11 -4
- package/src/internal_activities/index.js +38 -35
- 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/README.md +47 -0
- package/src/sdk/helpers/component_metadata.d.ts +17 -0
- package/src/sdk/helpers/component_metadata.js +6 -0
- package/src/sdk/helpers/component_metadata.spec.js +30 -0
- package/src/sdk/helpers/index.d.ts +12 -0
- package/src/sdk/helpers/index.js +3 -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/sdk/helpers/path.d.ts +11 -0
- package/src/sdk/helpers/path.js +32 -0
- package/src/{utils/resolve_invocation_dir.spec.js → sdk/helpers/path.spec.js} +9 -9
- package/src/sdk/runtime/context.d.ts +30 -0
- package/src/sdk/runtime/context.js +15 -0
- package/src/{activity_integration → sdk/runtime}/context.spec.js +5 -5
- package/src/sdk/runtime/events.d.ts +15 -0
- package/src/sdk/runtime/events.js +18 -0
- package/src/{activity_integration → sdk/runtime}/events.spec.js +8 -9
- package/src/sdk/runtime/index.d.ts +12 -0
- package/src/sdk/runtime/index.js +3 -0
- package/src/sdk/runtime/tracing.d.ts +46 -0
- package/src/sdk/runtime/tracing.js +11 -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 +8 -11
- package/src/worker/interceptors/activity.spec.js +25 -26
- 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/loader/tools.js +1 -1
- package/src/worker/loader/tools.spec.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/activity_integration/context.d.ts +0 -23
- package/src/activity_integration/context.js +0 -18
- package/src/activity_integration/event_id_integration.spec.js +0 -52
- package/src/activity_integration/events.d.ts +0 -10
- package/src/activity_integration/events.js +0 -15
- package/src/activity_integration/index.d.ts +0 -9
- package/src/activity_integration/index.js +0 -3
- package/src/activity_integration/tracing.d.ts +0 -40
- package/src/activity_integration/tracing.js +0 -48
- package/src/utils/index.d.ts +0 -180
- package/src/utils/index.js +0 -2
- package/src/utils/resolve_invocation_dir.js +0 -34
- package/src/utils/utils.js +0 -334
- 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
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { ACTIVITY_LOGGER_SYMBOL } from '#consts';
|
|
3
|
+
|
|
4
|
+
const workflowLogMock = vi.fn();
|
|
5
|
+
const proxySinksMock = vi.fn( () => ( {
|
|
6
|
+
workflow: {
|
|
7
|
+
log: workflowLogMock
|
|
8
|
+
}
|
|
9
|
+
} ) );
|
|
10
|
+
const inWorkflowContextMock = vi.fn( () => false );
|
|
11
|
+
|
|
12
|
+
vi.mock( '@temporalio/workflow', () => ( {
|
|
13
|
+
inWorkflowContext: inWorkflowContextMock,
|
|
14
|
+
proxySinks: proxySinksMock
|
|
15
|
+
} ) );
|
|
16
|
+
|
|
17
|
+
const logLevels = [
|
|
18
|
+
'error',
|
|
19
|
+
'warn',
|
|
20
|
+
'info',
|
|
21
|
+
'http',
|
|
22
|
+
'verbose',
|
|
23
|
+
'debug',
|
|
24
|
+
'silly'
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const consoleMethodsByLevel = {
|
|
28
|
+
error: 'error',
|
|
29
|
+
warn: 'warn',
|
|
30
|
+
info: 'info',
|
|
31
|
+
http: 'log',
|
|
32
|
+
verbose: 'log',
|
|
33
|
+
debug: 'debug',
|
|
34
|
+
silly: 'log'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const loadLogger = async () => ( await import( './logger.js' ) ).Logger;
|
|
38
|
+
|
|
39
|
+
describe( 'interface/logger', () => {
|
|
40
|
+
beforeEach( () => {
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
inWorkflowContextMock.mockReturnValue( false );
|
|
43
|
+
delete globalThis[ACTIVITY_LOGGER_SYMBOL];
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
afterEach( () => {
|
|
47
|
+
delete globalThis[ACTIVITY_LOGGER_SYMBOL];
|
|
48
|
+
vi.restoreAllMocks();
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
it( 'proxies sinks once when the module is loaded', async () => {
|
|
52
|
+
await loadLogger();
|
|
53
|
+
|
|
54
|
+
expect( proxySinksMock ).toHaveBeenCalledTimes( 1 );
|
|
55
|
+
} );
|
|
56
|
+
|
|
57
|
+
it( 'logs every level through workflow sinks inside workflow context', async () => {
|
|
58
|
+
const logger = await loadLogger();
|
|
59
|
+
inWorkflowContextMock.mockReturnValue( true );
|
|
60
|
+
|
|
61
|
+
logLevels.forEach( level => {
|
|
62
|
+
logger[level]( `${level} message`, { requestId: level } );
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
logLevels.forEach( ( level, index ) => {
|
|
66
|
+
const payload = {
|
|
67
|
+
level,
|
|
68
|
+
message: `${level} message`,
|
|
69
|
+
metadata: { requestId: level }
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect( workflowLogMock ).toHaveBeenNthCalledWith( index + 1, payload );
|
|
73
|
+
} );
|
|
74
|
+
} );
|
|
75
|
+
|
|
76
|
+
it( 'sanitizes messages and metadata before logging', async () => {
|
|
77
|
+
const logger = await loadLogger();
|
|
78
|
+
inWorkflowContextMock.mockReturnValue( true );
|
|
79
|
+
|
|
80
|
+
logger.info( 123, {
|
|
81
|
+
requestId: 'req-1',
|
|
82
|
+
level: 'error',
|
|
83
|
+
message: 'metadata message'
|
|
84
|
+
} );
|
|
85
|
+
|
|
86
|
+
expect( workflowLogMock ).toHaveBeenCalledWith( {
|
|
87
|
+
level: 'info',
|
|
88
|
+
message: '123',
|
|
89
|
+
metadata: { requestId: 'req-1' }
|
|
90
|
+
} );
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
it( 'logs every level through the activity global logger when it is set outside workflow context', async () => {
|
|
94
|
+
const logger = await loadLogger();
|
|
95
|
+
const activityLoggerMock = vi.fn();
|
|
96
|
+
globalThis[ACTIVITY_LOGGER_SYMBOL] = activityLoggerMock;
|
|
97
|
+
|
|
98
|
+
logLevels.forEach( level => {
|
|
99
|
+
logger[level]( `${level} message`, { requestId: level } );
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
logLevels.forEach( ( level, index ) => {
|
|
103
|
+
expect( activityLoggerMock ).toHaveBeenNthCalledWith( index + 1, {
|
|
104
|
+
level,
|
|
105
|
+
message: `${level} message`,
|
|
106
|
+
metadata: { requestId: level }
|
|
107
|
+
} );
|
|
108
|
+
} );
|
|
109
|
+
expect( workflowLogMock ).not.toHaveBeenCalled();
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'logs every level to its native console method when no workflow or activity logger is available', async () => {
|
|
113
|
+
const logger = await loadLogger();
|
|
114
|
+
const consoleMocks = Object.fromEntries(
|
|
115
|
+
[ 'debug', 'error', 'info', 'log', 'warn' ].map( method => [
|
|
116
|
+
method,
|
|
117
|
+
vi.spyOn( console, method ).mockImplementation( () => {} )
|
|
118
|
+
] )
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
logLevels.forEach( level => {
|
|
122
|
+
logger[level]( `${level} message`, { requestId: level } );
|
|
123
|
+
} );
|
|
124
|
+
|
|
125
|
+
logLevels.forEach( level => {
|
|
126
|
+
expect( consoleMocks[consoleMethodsByLevel[level]] ).toHaveBeenCalledWith(
|
|
127
|
+
`${level} message`,
|
|
128
|
+
{ requestId: level }
|
|
129
|
+
);
|
|
130
|
+
} );
|
|
131
|
+
expect( consoleMocks.error ).toHaveBeenCalledTimes( 1 );
|
|
132
|
+
expect( consoleMocks.warn ).toHaveBeenCalledTimes( 1 );
|
|
133
|
+
expect( consoleMocks.info ).toHaveBeenCalledTimes( 1 );
|
|
134
|
+
expect( consoleMocks.debug ).toHaveBeenCalledTimes( 1 );
|
|
135
|
+
expect( consoleMocks.log ).toHaveBeenCalledTimes( 3 );
|
|
136
|
+
expect( workflowLogMock ).not.toHaveBeenCalled();
|
|
137
|
+
} );
|
|
138
|
+
|
|
139
|
+
it( 'creates a logger with a default namespace for every level', async () => {
|
|
140
|
+
const logger = await loadLogger();
|
|
141
|
+
inWorkflowContextMock.mockReturnValue( true );
|
|
142
|
+
|
|
143
|
+
const namespacedLogger = logger.createLogger( 'Namespace' );
|
|
144
|
+
logLevels.forEach( level => {
|
|
145
|
+
namespacedLogger[level]( `${level} message`, { requestId: level } );
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
logLevels.forEach( ( level, index ) => {
|
|
149
|
+
expect( workflowLogMock ).toHaveBeenNthCalledWith( index + 1, {
|
|
150
|
+
level,
|
|
151
|
+
message: `${level} message`,
|
|
152
|
+
metadata: {
|
|
153
|
+
namespace: 'Namespace',
|
|
154
|
+
requestId: level
|
|
155
|
+
}
|
|
156
|
+
} );
|
|
157
|
+
} );
|
|
158
|
+
} );
|
|
159
|
+
|
|
160
|
+
it( 'lets log metadata override the default namespace', async () => {
|
|
161
|
+
const logger = await loadLogger();
|
|
162
|
+
inWorkflowContextMock.mockReturnValue( true );
|
|
163
|
+
|
|
164
|
+
logger.createLogger( 'Default namespace' ).info( 'message', { namespace: 'Inline namespace' } );
|
|
165
|
+
|
|
166
|
+
expect( workflowLogMock ).toHaveBeenCalledWith( {
|
|
167
|
+
level: 'info',
|
|
168
|
+
message: 'message',
|
|
169
|
+
metadata: { namespace: 'Inline namespace' }
|
|
170
|
+
} );
|
|
171
|
+
} );
|
|
172
|
+
} );
|
package/src/interface/step.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export type StepFunction<
|
|
|
36
36
|
* @param input - The Step input; it matches the schema defined by `inputSchema`.
|
|
37
37
|
* @returns A value matching the schema defined by `outputSchema`.
|
|
38
38
|
*/
|
|
39
|
-
export type StepFunctionWrapper<StepFunction> =
|
|
39
|
+
export type StepFunctionWrapper<StepFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
40
40
|
Parameters<StepFunction> extends [infer Input] ?
|
|
41
41
|
( input: Input ) => ReturnType<StepFunction> :
|
|
42
42
|
() => ReturnType<StepFunction>;
|
package/src/interface/step.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { StepValidator } from './validations/index.js';
|
|
2
|
-
import {
|
|
3
|
-
import { ComponentType } from '#consts';
|
|
2
|
+
import { createStep } from '#helpers/component';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Create a new step (activity flavor) and return a wrapper function around its fn handler
|
|
@@ -9,13 +8,17 @@ export function step( { name, description, inputSchema, outputSchema, fn, option
|
|
|
9
8
|
StepValidator.validateDefinition( { name, description, inputSchema, outputSchema, fn, options } );
|
|
10
9
|
const validator = new StepValidator( { name, inputSchema, outputSchema } );
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
return createStep( {
|
|
12
|
+
name,
|
|
13
|
+
description,
|
|
14
|
+
inputSchema,
|
|
15
|
+
outputSchema,
|
|
16
|
+
options,
|
|
17
|
+
handler: async input => {
|
|
18
|
+
validator.validateInput( input );
|
|
19
|
+
const output = await fn( input );
|
|
20
|
+
validator.validateOutput( output );
|
|
21
|
+
return output;
|
|
22
|
+
}
|
|
23
|
+
} );
|
|
24
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { ValidationError } from '#errors';
|
|
3
|
-
import { ComponentType } from '#consts';
|
|
4
3
|
|
|
5
4
|
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
6
5
|
const validateInputMock = vi.hoisted( () => vi.fn() );
|
|
7
6
|
const validateOutputMock = vi.hoisted( () => vi.fn() );
|
|
8
7
|
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
8
|
+
const createStepMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
9
9
|
|
|
10
10
|
vi.mock( './validations/index.js', () => {
|
|
11
11
|
class StepValidator {
|
|
@@ -23,12 +23,16 @@ vi.mock( './validations/index.js', () => {
|
|
|
23
23
|
return { StepValidator };
|
|
24
24
|
} );
|
|
25
25
|
|
|
26
|
+
vi.mock( '#helpers/component', () => ( {
|
|
27
|
+
createStep: createStepMock
|
|
28
|
+
} ) );
|
|
29
|
+
|
|
26
30
|
describe( 'step()', () => {
|
|
27
31
|
beforeEach( () => {
|
|
28
32
|
vi.clearAllMocks();
|
|
29
33
|
} );
|
|
30
34
|
|
|
31
|
-
it( 'validates the definition, creates a runtime validator, and
|
|
35
|
+
it( 'validates the definition, creates a runtime validator, and creates a step component', async () => {
|
|
32
36
|
const { step } = await import( './step.js' );
|
|
33
37
|
const inputSchema = { safeParse: vi.fn() };
|
|
34
38
|
const outputSchema = { safeParse: vi.fn() };
|
|
@@ -58,15 +62,15 @@ describe( 'step()', () => {
|
|
|
58
62
|
outputSchema
|
|
59
63
|
} );
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
expect( wrapper[metadataSymbol] ).toEqual( {
|
|
65
|
+
expect( createStepMock ).toHaveBeenCalledWith( {
|
|
63
66
|
name: 'test_step',
|
|
64
67
|
description: 'Test step',
|
|
65
68
|
inputSchema,
|
|
66
69
|
outputSchema,
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
options,
|
|
71
|
+
handler: expect.any( Function )
|
|
69
72
|
} );
|
|
73
|
+
expect( wrapper ).toBe( createStepMock.mock.calls[0][0].handler );
|
|
70
74
|
} );
|
|
71
75
|
|
|
72
76
|
it( 'validates input and output around the step function', async () => {
|
|
@@ -1,10 +1,29 @@
|
|
|
1
|
-
import type { SerializedFetchResponse } from '../utils/index.d.ts';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Allowed HTTP methods for request helpers.
|
|
5
3
|
*/
|
|
6
4
|
export type HttpMethod = 'HEAD' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
7
5
|
|
|
6
|
+
/** Represents a {Response} serialized to plain object */
|
|
7
|
+
export type SerializedFetchResponse = {
|
|
8
|
+
/** The response url */
|
|
9
|
+
url: string,
|
|
10
|
+
|
|
11
|
+
/** The response status code */
|
|
12
|
+
status: number,
|
|
13
|
+
|
|
14
|
+
/** The response status text */
|
|
15
|
+
statusText: string,
|
|
16
|
+
|
|
17
|
+
/** Flag indicating if the request succeeded */
|
|
18
|
+
ok: boolean,
|
|
19
|
+
|
|
20
|
+
/** Object with response headers */
|
|
21
|
+
headers: Record<string, string>,
|
|
22
|
+
|
|
23
|
+
/** Response body, either JSON, text, or an arrayBuffer converted to base64 */
|
|
24
|
+
body: object | string
|
|
25
|
+
};
|
|
26
|
+
|
|
8
27
|
/**
|
|
9
28
|
* Send an POST HTTP request to a URL, optionally with a payload, then wait for a webhook response.
|
|
10
29
|
*
|
|
@@ -133,7 +133,7 @@ export type WorkflowFunction<
|
|
|
133
133
|
> = InputSchema extends AnyZodSchema ?
|
|
134
134
|
( input: z.infer<InputSchema>, context: WorkflowContext<InputSchema, OutputSchema> ) =>
|
|
135
135
|
Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
|
|
136
|
-
( input
|
|
136
|
+
( input: undefined | null, context: WorkflowContext<InputSchema, OutputSchema> ) =>
|
|
137
137
|
Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
|
|
138
138
|
|
|
139
139
|
/**
|
|
@@ -149,7 +149,7 @@ export type WorkflowFunction<
|
|
|
149
149
|
* @param options - Additional options for the invocation.
|
|
150
150
|
* @returns A value matching the schema defined by `outputSchema`.
|
|
151
151
|
*/
|
|
152
|
-
export type WorkflowFunctionWrapper<WorkflowFunction> =
|
|
152
|
+
export type WorkflowFunctionWrapper<WorkflowFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
153
153
|
[Parameters<WorkflowFunction>[0]] extends [undefined | null] ?
|
|
154
154
|
( input?: undefined | null, options?: WorkflowInvocationOptions ) =>
|
|
155
155
|
ReturnType<WorkflowFunction> :
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
|
|
2
2
|
import { proxyActivities, inWorkflowContext, executeChild, workflowInfo, uuid4, ParentClosePolicy } from '@temporalio/workflow';
|
|
3
3
|
import { WorkflowValidator } from './validations/index.js';
|
|
4
|
-
import {
|
|
5
|
-
import { WorkflowContext } from '#
|
|
6
|
-
import { TraceInfo } from '#
|
|
4
|
+
import { toUrlSafeBase64 } from '#helpers/string';
|
|
5
|
+
import { WorkflowContext } from '#helpers/workflow_context';
|
|
6
|
+
import { TraceInfo } from '#helpers/trace_info';
|
|
7
|
+
import { deepMerge } from '#helpers/object';
|
|
7
8
|
import { defaultOptions } from './workflow_activity_options.js';
|
|
9
|
+
import { createWorkflow } from '#helpers/component';
|
|
8
10
|
import {
|
|
9
11
|
ACTIVITY_WRAPPER_VERSION_FIELD,
|
|
10
12
|
ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
@@ -34,84 +36,89 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
|
|
|
34
36
|
const { disableTrace, activityOptions } = deepMerge( defaultOptions, options );
|
|
35
37
|
const validator = new WorkflowValidator( { name, inputSchema, outputSchema } );
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
return createWorkflow( {
|
|
40
|
+
name,
|
|
41
|
+
description,
|
|
42
|
+
inputSchema,
|
|
43
|
+
outputSchema,
|
|
44
|
+
options,
|
|
45
|
+
aliases,
|
|
46
|
+
handler: async ( input, extra = {} ) => {
|
|
47
|
+
validator.validateInvocationOptions( extra );
|
|
48
|
+
|
|
49
|
+
// this returns a plain function, for example, in unit tests
|
|
50
|
+
if ( !inWorkflowContext() ) {
|
|
51
|
+
validator.validateInput( input );
|
|
52
|
+
const output = await fn( input, deepMerge( WorkflowContext.build(), extra?.context ) );
|
|
53
|
+
validator.validateOutput( output );
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
const { workflowId, workflowType, memo, root } = workflowInfo();
|
|
58
|
+
|
|
59
|
+
// if the stack already includes this workflowId, means the workflow() function was called
|
|
60
|
+
// from within a running workflow, meaning it is suppose to start a child workflow
|
|
61
|
+
const isChild = Array.isArray( memo.stack ) ? memo.stack.includes( workflowId ) :
|
|
62
|
+
checkChildFallback( { workflowType, aliases, name } );
|
|
63
|
+
|
|
64
|
+
if ( isChild ) {
|
|
65
|
+
const result = await executeChild( name, {
|
|
66
|
+
args: undefined === input ? [] : [ input ],
|
|
67
|
+
workflowId: `${workflowId}-${toUrlSafeBase64( uuid4() )}`,
|
|
68
|
+
parentClosePolicy: ParentClosePolicy[extra?.detached ? 'ABANDON' : 'TERMINATE'],
|
|
69
|
+
memo: {
|
|
70
|
+
...memo, // Preserve memo and mix activityOptions, if provided
|
|
71
|
+
...( extra?.activityOptions && {
|
|
72
|
+
activityOptions: deepMerge( memo?.activityOptions ?? {}, extra?.activityOptions )
|
|
73
|
+
} )
|
|
74
|
+
}
|
|
75
|
+
} );
|
|
76
|
+
return result.output;
|
|
77
|
+
}
|
|
69
78
|
|
|
70
|
-
|
|
79
|
+
const isRoot = !root;
|
|
71
80
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
memo.stack = [ ...memo.stack ?? [], workflowId ];
|
|
82
|
+
// Parent options have prevalence on nested calls, child will be overwritten
|
|
83
|
+
memo.activityOptions = deepMerge( activityOptions, memo.activityOptions );
|
|
84
|
+
// Trace info is only added in the root workflow
|
|
85
|
+
if ( isRoot ) {
|
|
86
|
+
memo.traceInfo = TraceInfo.build( { disableTrace } );
|
|
87
|
+
}
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
89
|
+
const steps = proxyActivities( memo.activityOptions );
|
|
90
|
+
const traceDest = isRoot && parseActivityOutput( await steps[ACTIVITY_GET_TRACE_DESTINATIONS]( memo.traceInfo ) );
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
validator.validateInput( input );
|
|
94
|
+
|
|
95
|
+
// Creates an activity caller based on a prefix
|
|
96
|
+
const createCaller = prefix => async ( t, ...args ) => parseActivityOutput( await steps[`${prefix}#${t}`]( ...args ) );
|
|
97
|
+
|
|
98
|
+
// This are functions used by the AST to replace direct activity (step/evaluator) calls
|
|
99
|
+
const dispatchers = {
|
|
100
|
+
invokeStep: createCaller( name ),
|
|
101
|
+
invokeSharedStep: createCaller( SHARED_STEP_PREFIX ),
|
|
102
|
+
invokeEvaluator: createCaller( name ),
|
|
103
|
+
invokeSharedEvaluator: createCaller( SHARED_STEP_PREFIX )
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// The workflow function execution with "this" set with the dispatchers
|
|
107
|
+
const output = await fn.call( dispatchers, input, WorkflowContext.build() );
|
|
108
|
+
validator.validateOutput( output );
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
[WORKFLOW_WRAPPER_VERSION_FIELD]: 1,
|
|
112
|
+
output,
|
|
113
|
+
...( traceDest && { trace: { destinations: traceDest } } )
|
|
114
|
+
};
|
|
115
|
+
} catch ( error ) {
|
|
116
|
+
if ( isRoot && traceDest ) {
|
|
117
|
+
// Append the trace destinations so it is carried to interceptor
|
|
118
|
+
error[METADATA_ACCESS_SYMBOL] = { trace: { destinations: traceDest } };
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
110
121
|
}
|
|
111
|
-
throw error;
|
|
112
122
|
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
setMetadata( wrapper, { name, description, inputSchema, outputSchema, aliases } );
|
|
116
|
-
return wrapper;
|
|
117
|
-
};
|
|
123
|
+
} );
|
|
124
|
+
}
|
|
@@ -19,6 +19,7 @@ const validateInputMock = vi.hoisted( () => vi.fn() );
|
|
|
19
19
|
const validateOutputMock = vi.hoisted( () => vi.fn() );
|
|
20
20
|
const validateInvocationOptionsMock = vi.hoisted( () => vi.fn() );
|
|
21
21
|
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
22
|
+
const createWorkflowMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
22
23
|
|
|
23
24
|
vi.mock( './validations/index.js', () => {
|
|
24
25
|
class WorkflowValidator {
|
|
@@ -37,6 +38,10 @@ vi.mock( './validations/index.js', () => {
|
|
|
37
38
|
return { WorkflowValidator };
|
|
38
39
|
} );
|
|
39
40
|
|
|
41
|
+
vi.mock( '#helpers/component', () => ( {
|
|
42
|
+
createWorkflow: createWorkflowMock
|
|
43
|
+
} ) );
|
|
44
|
+
|
|
40
45
|
vi.mock( '@temporalio/workflow', async importOriginal => {
|
|
41
46
|
const actual = await importOriginal();
|
|
42
47
|
return {
|
|
@@ -151,7 +156,7 @@ describe( 'workflow()', () => {
|
|
|
151
156
|
expect( () => workflow( workflowDefinition( { name: 'invalid_name' } ) ) ).toThrow( error );
|
|
152
157
|
} );
|
|
153
158
|
|
|
154
|
-
it( '
|
|
159
|
+
it( 'creates a workflow component with definition metadata', async () => {
|
|
155
160
|
const { workflow } = await import( './workflow.js' );
|
|
156
161
|
const inputSchema = z.object( { value: z.string() } );
|
|
157
162
|
const outputSchema = z.object( { ok: z.boolean() } );
|
|
@@ -165,14 +170,16 @@ describe( 'workflow()', () => {
|
|
|
165
170
|
fn: async () => ( { ok: true } )
|
|
166
171
|
} ) );
|
|
167
172
|
|
|
168
|
-
|
|
169
|
-
expect( wf[metadataSymbol] ).toEqual( {
|
|
173
|
+
expect( createWorkflowMock ).toHaveBeenCalledWith( {
|
|
170
174
|
name: 'metadata_wf',
|
|
171
175
|
description: 'Metadata workflow',
|
|
172
176
|
inputSchema,
|
|
173
177
|
outputSchema,
|
|
174
|
-
|
|
178
|
+
options: {},
|
|
179
|
+
aliases: [ 'metadata_alias' ],
|
|
180
|
+
handler: expect.any( Function )
|
|
175
181
|
} );
|
|
182
|
+
expect( wf ).toBe( createWorkflowMock.mock.calls[0][0].handler );
|
|
176
183
|
} );
|
|
177
184
|
|
|
178
185
|
describe( 'outside Temporal workflow context', () => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FatalError } from '#errors';
|
|
2
2
|
import { fetch } from 'undici';
|
|
3
|
-
import {
|
|
4
|
-
import { ComponentType } from '#consts';
|
|
3
|
+
import { serializeFetchResponse, serializeBodyAndInferContentType } from '#helpers/fetch';
|
|
5
4
|
import { createChildLogger } from '#logger';
|
|
6
5
|
import { getDestinations } from '#tracing';
|
|
6
|
+
import { createInternalStep } from '#helpers/component';
|
|
7
|
+
import { ACTIVITY_GET_TRACE_DESTINATIONS, ACTIVITY_SEND_HTTP_REQUEST } from '#consts';
|
|
7
8
|
|
|
8
9
|
const log = createChildLogger( 'HttpClient' );
|
|
9
10
|
|
|
@@ -19,41 +20,42 @@ const log = createChildLogger( 'HttpClient' );
|
|
|
19
20
|
* @returns {object} The serialized HTTP response
|
|
20
21
|
* @throws {FatalError}
|
|
21
22
|
*/
|
|
22
|
-
export const sendHttpRequest =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
};
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const response = await ( async () => {
|
|
40
|
-
try {
|
|
41
|
-
return await fetch( url, args );
|
|
42
|
-
} catch ( e ) {
|
|
43
|
-
throw new FatalError( `${method} ${url} ${e.cause ?? e.message}` );
|
|
44
|
-
}
|
|
45
|
-
} )();
|
|
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
|
+
};
|
|
46
41
|
|
|
47
|
-
|
|
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
|
+
} )();
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
throw new FatalError( `${method} ${url} ${response.status}` );
|
|
51
|
-
}
|
|
50
|
+
log.info( 'HTTP request completed', { url, method, status: response.status, statusText: response.statusText } );
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
};
|
|
52
|
+
if ( !response.ok ) {
|
|
53
|
+
throw new FatalError( `${method} ${url} ${response.status}` );
|
|
54
|
+
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
return serializeFetchResponse( response );
|
|
57
|
+
}
|
|
58
|
+
} );
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
61
|
* Invokes a trace method that resolves all trace output paths based on the traceInfo
|
|
@@ -61,6 +63,7 @@ setMetadata( sendHttpRequest, { type: ComponentType.INTERNAL_STEP } );
|
|
|
61
63
|
* @param {object} traceInfo
|
|
62
64
|
* @returns {object} Information about enabled destinations
|
|
63
65
|
*/
|
|
64
|
-
export const getTraceDestinations =
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
export const getTraceDestinations = createInternalStep( {
|
|
67
|
+
name: ACTIVITY_GET_TRACE_DESTINATIONS,
|
|
68
|
+
handler: traceInfo => getDestinations( traceInfo )
|
|
69
|
+
} );
|