@outputai/core 0.7.1-dev.144d64f.0 → 0.7.1-next.0b398c7.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/bin/worker.sh +6 -0
- package/package.json +1 -1
- package/src/consts.js +0 -4
- package/src/errors.js +6 -2
- package/src/interface/evaluator.js +7 -20
- package/src/interface/evaluator.spec.js +117 -1
- package/src/interface/step.js +8 -9
- package/src/interface/step.spec.js +124 -0
- package/src/interface/validations/index.js +108 -0
- package/src/interface/validations/index.spec.js +182 -0
- package/src/interface/validations/schemas.js +113 -0
- package/src/interface/validations/schemas.spec.js +209 -0
- package/src/interface/webhook.js +1 -1
- package/src/interface/webhook.spec.js +1 -1
- package/src/interface/workflow.d.ts +10 -9
- package/src/interface/workflow.js +76 -164
- package/src/interface/workflow.spec.js +637 -521
- package/src/interface/workflow_activity_options.js +16 -0
- package/src/interface/workflow_utils.js +1 -1
- package/src/interface/zod_integration.spec.js +2 -2
- package/src/internal_utils/aggregations.js +0 -10
- package/src/internal_utils/aggregations.spec.js +1 -48
- package/src/internal_utils/errors.js +14 -8
- package/src/internal_utils/errors.spec.js +73 -27
- package/src/utils/index.d.ts +19 -0
- package/src/utils/utils.js +53 -0
- package/src/utils/utils.spec.js +105 -1
- package/src/worker/bundle.js +26 -0
- package/src/worker/bundle.spec.js +53 -0
- package/src/worker/bundler_options.js +1 -1
- package/src/worker/bundler_options.spec.js +1 -1
- package/src/worker/catalog_workflow/catalog_job.js +148 -0
- package/src/worker/catalog_workflow/catalog_job.spec.js +232 -0
- package/src/worker/check.js +24 -0
- package/src/worker/connection_monitor.js +112 -0
- package/src/worker/connection_monitor.spec.js +199 -0
- package/src/worker/index.js +146 -41
- package/src/worker/index.spec.js +281 -109
- package/src/worker/interceptors/activity.js +7 -24
- package/src/worker/interceptors/activity.spec.js +97 -66
- package/src/worker/interceptors/index.js +4 -7
- package/src/worker/interceptors/modules.js +15 -0
- package/src/worker/interceptors/workflow.js +6 -8
- package/src/worker/interceptors/workflow.spec.js +49 -42
- package/src/worker/interruption.js +33 -0
- package/src/worker/interruption.spec.js +98 -0
- package/src/worker/loader/activities.js +75 -0
- package/src/worker/loader/activities.spec.js +213 -0
- package/src/worker/loader/hooks.js +28 -0
- package/src/worker/loader/hooks.spec.js +64 -0
- package/src/worker/loader/matchers.js +46 -0
- package/src/worker/loader/matchers.spec.js +140 -0
- package/src/worker/{loader_tools.js → loader/tools.js} +18 -66
- package/src/worker/{loader_tools.spec.js → loader/tools.spec.js} +24 -92
- package/src/worker/loader/workflows.js +82 -0
- package/src/worker/loader/workflows.spec.js +256 -0
- package/src/worker/{setup_telemetry.js → telemetry.js} +9 -4
- package/src/worker/{setup_telemetry.spec.js → telemetry.spec.js} +3 -3
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
- package/src/interface/validations/runtime.js +0 -20
- package/src/interface/validations/runtime.spec.js +0 -29
- package/src/interface/validations/schema_utils.js +0 -8
- package/src/interface/validations/schema_utils.spec.js +0 -67
- package/src/interface/validations/static.js +0 -137
- package/src/interface/validations/static.spec.js +0 -397
- package/src/interface/workflow.replay_compatibility.spec.js +0 -254
- package/src/worker/loader.js +0 -202
- package/src/worker/loader.spec.js +0 -498
- package/src/worker/shutdown.js +0 -26
- package/src/worker/shutdown.spec.js +0 -82
- package/src/worker/start_catalog.js +0 -96
- package/src/worker/start_catalog.spec.js +0 -179
package/bin/worker.sh
CHANGED
|
@@ -23,4 +23,10 @@ sdk_dir="$(dirname "$script_dir")"
|
|
|
23
23
|
|
|
24
24
|
cd ${sdk_dir}
|
|
25
25
|
|
|
26
|
+
# Route `output-worker --check` to the bundle-check entry, which validates the
|
|
27
|
+
# workflow bundle without a Temporal connection or worker config.
|
|
28
|
+
if [[ " $* " == *" --check "* ]]; then
|
|
29
|
+
exec node "${sdk_dir}/src/worker/check.js" "${invocation_dir}"
|
|
30
|
+
fi
|
|
31
|
+
|
|
26
32
|
exec node "${sdk_dir}/src/worker/index.js" "${invocation_dir}" "${@:2}"
|
package/package.json
CHANGED
package/src/consts.js
CHANGED
package/src/errors.js
CHANGED
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Any generic fatal errors
|
|
8
8
|
*/
|
|
9
|
-
export class FatalError extends Error {
|
|
9
|
+
export class FatalError extends Error {
|
|
10
|
+
name = 'FatalError';
|
|
11
|
+
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Any validation error
|
|
13
15
|
*/
|
|
14
|
-
export class ValidationError extends Error {
|
|
16
|
+
export class ValidationError extends Error {
|
|
17
|
+
name = 'ValidationError';
|
|
18
|
+
}
|
|
@@ -1,31 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { validateWithSchema } from './validations/runtime.js';
|
|
1
|
+
import { EvaluatorValidator } from './validations/index.js';
|
|
3
2
|
import { setMetadata } from '#utils';
|
|
4
|
-
import { ValidationError } from '#errors';
|
|
5
3
|
import { ComponentType } from '#consts';
|
|
6
|
-
|
|
4
|
+
|
|
7
5
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @param {object} opts
|
|
10
|
-
* @param {string} opts.name
|
|
11
|
-
* @param {string} opts.description
|
|
12
|
-
* @param {z.ZodType} opts.inputSchema
|
|
13
|
-
* @param {Function} opts.fn
|
|
14
|
-
* @param {object} opts.options
|
|
15
|
-
* @returns {Function}
|
|
6
|
+
* Create a new evaluator (activity flavor) and return a wrapper function around its fn handler
|
|
16
7
|
*/
|
|
17
8
|
export function evaluator( { name, description, inputSchema, fn, options } ) {
|
|
18
|
-
|
|
9
|
+
EvaluatorValidator.validateDefinition( { name, description, inputSchema, fn, options } );
|
|
10
|
+
const validator = new EvaluatorValidator( { name, inputSchema } );
|
|
19
11
|
|
|
20
12
|
const wrapper = async input => {
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
validator.validateInput( input );
|
|
23
14
|
const output = await fn( input );
|
|
24
|
-
|
|
25
|
-
if ( !( output instanceof EvaluationResult ) ) {
|
|
26
|
-
throw new ValidationError( 'Evaluators must return an EvaluationResult' );
|
|
27
|
-
}
|
|
28
|
-
|
|
15
|
+
validator.validateOutput( output );
|
|
29
16
|
return output;
|
|
30
17
|
};
|
|
31
18
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import {
|
|
3
3
|
EvaluationResult,
|
|
4
4
|
EvaluationStringResult,
|
|
@@ -7,6 +7,122 @@ import {
|
|
|
7
7
|
EvaluationFeedback
|
|
8
8
|
} from './evaluation_result.js';
|
|
9
9
|
import { ValidationError } from '#errors';
|
|
10
|
+
import { ComponentType } from '#consts';
|
|
11
|
+
|
|
12
|
+
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
13
|
+
const validateInputMock = vi.hoisted( () => vi.fn() );
|
|
14
|
+
const validateOutputMock = vi.hoisted( () => vi.fn() );
|
|
15
|
+
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
16
|
+
|
|
17
|
+
vi.mock( './validations/index.js', () => {
|
|
18
|
+
class EvaluatorValidator {
|
|
19
|
+
static validateDefinition( ...args ) {
|
|
20
|
+
return validateDefinitionMock( ...args );
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor( ...args ) {
|
|
24
|
+
validatorConstructorMock( ...args );
|
|
25
|
+
this.validateInput = validateInputMock;
|
|
26
|
+
this.validateOutput = validateOutputMock;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { EvaluatorValidator };
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
describe( 'evaluator()', () => {
|
|
34
|
+
beforeEach( () => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
it( 'validates the definition, creates a runtime validator, and attaches metadata', async () => {
|
|
39
|
+
const { evaluator } = await import( './evaluator.js' );
|
|
40
|
+
const inputSchema = { safeParse: vi.fn() };
|
|
41
|
+
const fn = vi.fn().mockResolvedValue( new EvaluationResult( { value: 'ok', confidence: 1 } ) );
|
|
42
|
+
const options = { activityOptions: { startToCloseTimeout: '1m' } };
|
|
43
|
+
|
|
44
|
+
const wrapper = evaluator( {
|
|
45
|
+
name: 'test_evaluator',
|
|
46
|
+
description: 'Test evaluator',
|
|
47
|
+
inputSchema,
|
|
48
|
+
fn,
|
|
49
|
+
options
|
|
50
|
+
} );
|
|
51
|
+
|
|
52
|
+
expect( validateDefinitionMock ).toHaveBeenCalledWith( {
|
|
53
|
+
name: 'test_evaluator',
|
|
54
|
+
description: 'Test evaluator',
|
|
55
|
+
inputSchema,
|
|
56
|
+
fn,
|
|
57
|
+
options
|
|
58
|
+
} );
|
|
59
|
+
expect( validatorConstructorMock ).toHaveBeenCalledWith( {
|
|
60
|
+
name: 'test_evaluator',
|
|
61
|
+
inputSchema
|
|
62
|
+
} );
|
|
63
|
+
|
|
64
|
+
const [ metadataSymbol ] = Object.getOwnPropertySymbols( wrapper );
|
|
65
|
+
expect( wrapper[metadataSymbol] ).toEqual( {
|
|
66
|
+
name: 'test_evaluator',
|
|
67
|
+
description: 'Test evaluator',
|
|
68
|
+
inputSchema,
|
|
69
|
+
type: ComponentType.EVALUATOR,
|
|
70
|
+
options
|
|
71
|
+
} );
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
it( 'validates input and output around the evaluator function', async () => {
|
|
75
|
+
const { evaluator } = await import( './evaluator.js' );
|
|
76
|
+
const output = new EvaluationResult( { value: 'ok', confidence: 1 } );
|
|
77
|
+
const fn = vi.fn().mockResolvedValue( output );
|
|
78
|
+
const wrapper = evaluator( {
|
|
79
|
+
name: 'runtime_evaluator',
|
|
80
|
+
inputSchema: undefined,
|
|
81
|
+
fn
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
await expect( wrapper( { value: 'input' } ) ).resolves.toBe( output );
|
|
85
|
+
|
|
86
|
+
expect( validateInputMock ).toHaveBeenCalledWith( { value: 'input' } );
|
|
87
|
+
expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
|
|
88
|
+
expect( validateOutputMock ).toHaveBeenCalledWith( output );
|
|
89
|
+
} );
|
|
90
|
+
|
|
91
|
+
it( 'does not call the evaluator function when input validation throws', async () => {
|
|
92
|
+
const { evaluator } = await import( './evaluator.js' );
|
|
93
|
+
const error = new ValidationError( 'invalid input' );
|
|
94
|
+
validateInputMock.mockImplementationOnce( () => {
|
|
95
|
+
throw error;
|
|
96
|
+
} );
|
|
97
|
+
const fn = vi.fn();
|
|
98
|
+
const wrapper = evaluator( {
|
|
99
|
+
name: 'invalid_input_evaluator',
|
|
100
|
+
fn
|
|
101
|
+
} );
|
|
102
|
+
|
|
103
|
+
await expect( wrapper( { value: 'bad' } ) ).rejects.toBe( error );
|
|
104
|
+
expect( fn ).not.toHaveBeenCalled();
|
|
105
|
+
expect( validateOutputMock ).not.toHaveBeenCalled();
|
|
106
|
+
} );
|
|
107
|
+
|
|
108
|
+
it( 'propagates output validation errors after the evaluator function runs', async () => {
|
|
109
|
+
const { evaluator } = await import( './evaluator.js' );
|
|
110
|
+
const error = new ValidationError( 'invalid output' );
|
|
111
|
+
validateOutputMock.mockImplementationOnce( () => {
|
|
112
|
+
throw error;
|
|
113
|
+
} );
|
|
114
|
+
const output = { value: 'not-an-evaluation-result' };
|
|
115
|
+
const fn = vi.fn().mockResolvedValue( output );
|
|
116
|
+
const wrapper = evaluator( {
|
|
117
|
+
name: 'invalid_output_evaluator',
|
|
118
|
+
fn
|
|
119
|
+
} );
|
|
120
|
+
|
|
121
|
+
await expect( wrapper( { value: 'input' } ) ).rejects.toBe( error );
|
|
122
|
+
expect( fn ).toHaveBeenCalledOnce();
|
|
123
|
+
expect( validateOutputMock ).toHaveBeenCalledWith( output );
|
|
124
|
+
} );
|
|
125
|
+
} );
|
|
10
126
|
|
|
11
127
|
describe( 'interface/evaluator - EvaluationResult classes', () => {
|
|
12
128
|
describe( 'class inheritance', () => {
|
package/src/interface/step.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import { StepValidator } from './validations/index.js';
|
|
2
2
|
import { setMetadata } from '#utils';
|
|
3
|
-
import { validateStep } from './validations/static.js';
|
|
4
|
-
import { validateWithSchema } from './validations/runtime.js';
|
|
5
3
|
import { ComponentType } from '#consts';
|
|
6
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Create a new step (activity flavor) and return a wrapper function around its fn handler
|
|
7
|
+
*/
|
|
7
8
|
export function step( { name, description, inputSchema, outputSchema, fn, options } ) {
|
|
8
|
-
|
|
9
|
+
StepValidator.validateDefinition( { name, description, inputSchema, outputSchema, fn, options } );
|
|
10
|
+
const validator = new StepValidator( { name, inputSchema, outputSchema } );
|
|
9
11
|
|
|
10
12
|
const wrapper = async input => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
validator.validateInput( input );
|
|
13
14
|
const output = await fn( input );
|
|
14
|
-
|
|
15
|
-
validateWithSchema( outputSchema, output, `Step ${name} output` );
|
|
16
|
-
|
|
15
|
+
validator.validateOutput( output );
|
|
17
16
|
return output;
|
|
18
17
|
};
|
|
19
18
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { ValidationError } from '#errors';
|
|
3
|
+
import { ComponentType } from '#consts';
|
|
4
|
+
|
|
5
|
+
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
6
|
+
const validateInputMock = vi.hoisted( () => vi.fn() );
|
|
7
|
+
const validateOutputMock = vi.hoisted( () => vi.fn() );
|
|
8
|
+
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
9
|
+
|
|
10
|
+
vi.mock( './validations/index.js', () => {
|
|
11
|
+
class StepValidator {
|
|
12
|
+
static validateDefinition( ...args ) {
|
|
13
|
+
return validateDefinitionMock( ...args );
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor( ...args ) {
|
|
17
|
+
validatorConstructorMock( ...args );
|
|
18
|
+
this.validateInput = validateInputMock;
|
|
19
|
+
this.validateOutput = validateOutputMock;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return { StepValidator };
|
|
24
|
+
} );
|
|
25
|
+
|
|
26
|
+
describe( 'step()', () => {
|
|
27
|
+
beforeEach( () => {
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
it( 'validates the definition, creates a runtime validator, and attaches metadata', async () => {
|
|
32
|
+
const { step } = await import( './step.js' );
|
|
33
|
+
const inputSchema = { safeParse: vi.fn() };
|
|
34
|
+
const outputSchema = { safeParse: vi.fn() };
|
|
35
|
+
const fn = vi.fn().mockResolvedValue( { ok: true } );
|
|
36
|
+
const options = { activityOptions: { startToCloseTimeout: '1m' } };
|
|
37
|
+
|
|
38
|
+
const wrapper = step( {
|
|
39
|
+
name: 'test_step',
|
|
40
|
+
description: 'Test step',
|
|
41
|
+
inputSchema,
|
|
42
|
+
outputSchema,
|
|
43
|
+
fn,
|
|
44
|
+
options
|
|
45
|
+
} );
|
|
46
|
+
|
|
47
|
+
expect( validateDefinitionMock ).toHaveBeenCalledWith( {
|
|
48
|
+
name: 'test_step',
|
|
49
|
+
description: 'Test step',
|
|
50
|
+
inputSchema,
|
|
51
|
+
outputSchema,
|
|
52
|
+
fn,
|
|
53
|
+
options
|
|
54
|
+
} );
|
|
55
|
+
expect( validatorConstructorMock ).toHaveBeenCalledWith( {
|
|
56
|
+
name: 'test_step',
|
|
57
|
+
inputSchema,
|
|
58
|
+
outputSchema
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
const [ metadataSymbol ] = Object.getOwnPropertySymbols( wrapper );
|
|
62
|
+
expect( wrapper[metadataSymbol] ).toEqual( {
|
|
63
|
+
name: 'test_step',
|
|
64
|
+
description: 'Test step',
|
|
65
|
+
inputSchema,
|
|
66
|
+
outputSchema,
|
|
67
|
+
type: ComponentType.STEP,
|
|
68
|
+
options
|
|
69
|
+
} );
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
it( 'validates input and output around the step function', async () => {
|
|
73
|
+
const { step } = await import( './step.js' );
|
|
74
|
+
const output = { ok: true };
|
|
75
|
+
const fn = vi.fn().mockResolvedValue( output );
|
|
76
|
+
const wrapper = step( {
|
|
77
|
+
name: 'runtime_step',
|
|
78
|
+
inputSchema: undefined,
|
|
79
|
+
outputSchema: undefined,
|
|
80
|
+
fn
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
await expect( wrapper( { value: 'input' } ) ).resolves.toBe( output );
|
|
84
|
+
|
|
85
|
+
expect( validateInputMock ).toHaveBeenCalledWith( { value: 'input' } );
|
|
86
|
+
expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
|
|
87
|
+
expect( validateOutputMock ).toHaveBeenCalledWith( output );
|
|
88
|
+
} );
|
|
89
|
+
|
|
90
|
+
it( 'does not call the step function when input validation throws', async () => {
|
|
91
|
+
const { step } = await import( './step.js' );
|
|
92
|
+
const error = new ValidationError( 'invalid input' );
|
|
93
|
+
validateInputMock.mockImplementationOnce( () => {
|
|
94
|
+
throw error;
|
|
95
|
+
} );
|
|
96
|
+
const fn = vi.fn();
|
|
97
|
+
const wrapper = step( {
|
|
98
|
+
name: 'invalid_input_step',
|
|
99
|
+
fn
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
await expect( wrapper( { value: 'bad' } ) ).rejects.toBe( error );
|
|
103
|
+
expect( fn ).not.toHaveBeenCalled();
|
|
104
|
+
expect( validateOutputMock ).not.toHaveBeenCalled();
|
|
105
|
+
} );
|
|
106
|
+
|
|
107
|
+
it( 'propagates output validation errors after the step function runs', async () => {
|
|
108
|
+
const { step } = await import( './step.js' );
|
|
109
|
+
const error = new ValidationError( 'invalid output' );
|
|
110
|
+
validateOutputMock.mockImplementationOnce( () => {
|
|
111
|
+
throw error;
|
|
112
|
+
} );
|
|
113
|
+
const output = { ok: false };
|
|
114
|
+
const fn = vi.fn().mockResolvedValue( output );
|
|
115
|
+
const wrapper = step( {
|
|
116
|
+
name: 'invalid_output_step',
|
|
117
|
+
fn
|
|
118
|
+
} );
|
|
119
|
+
|
|
120
|
+
await expect( wrapper( { value: 'input' } ) ).rejects.toBe( error );
|
|
121
|
+
expect( fn ).toHaveBeenCalledOnce();
|
|
122
|
+
expect( validateOutputMock ).toHaveBeenCalledWith( output );
|
|
123
|
+
} );
|
|
124
|
+
} );
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ValidationError } from '#errors';
|
|
2
|
+
import { prettifyError } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
evaluatorOutputSchema,
|
|
5
|
+
evaluatorSchema,
|
|
6
|
+
httpRequestSchema,
|
|
7
|
+
executeInParallelSchema,
|
|
8
|
+
stepSchema,
|
|
9
|
+
workflowInvocationOptionsSchema,
|
|
10
|
+
workflowSchema
|
|
11
|
+
} from './schemas.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates data using a Zod schema
|
|
15
|
+
* @param {unknown} data
|
|
16
|
+
* @param {ZodType} schema
|
|
17
|
+
* @param {string} prefix - Validation error prefix
|
|
18
|
+
* @throws {ValidationError} If validation fails
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
21
|
+
const validate = ( data, schema, prefix = '' ) => {
|
|
22
|
+
if ( !schema ) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = schema.safeParse( data );
|
|
27
|
+
if ( !result.success ) {
|
|
28
|
+
throw new ValidationError( `${prefix} validation failed: ${prettifyError( result.error ) }` );
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class WorkflowValidator {
|
|
33
|
+
static validateDefinition( definition ) {
|
|
34
|
+
validate( definition, workflowSchema, 'Workflow' );
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor( { name, inputSchema, outputSchema } ) {
|
|
38
|
+
this.name = name;
|
|
39
|
+
this.inputSchema = inputSchema;
|
|
40
|
+
this.outputSchema = outputSchema;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validateInput( input ) {
|
|
44
|
+
validate( input, this.inputSchema, `Workflow "${this.name}" input` );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
validateOutput( output ) {
|
|
48
|
+
validate( output, this.outputSchema, `Workflow "${this.name}" output` );
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
validateInvocationOptions( options ) {
|
|
52
|
+
validate( options, workflowInvocationOptionsSchema, `Workflow "${this.name}" invocation options` );
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class StepValidator {
|
|
57
|
+
static validateDefinition( definition ) {
|
|
58
|
+
validate( definition, stepSchema, 'Step' );
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
constructor( { name, inputSchema, outputSchema } ) {
|
|
62
|
+
this.name = name;
|
|
63
|
+
this.inputSchema = inputSchema;
|
|
64
|
+
this.outputSchema = outputSchema;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
validateInput( input ) {
|
|
68
|
+
validate( input, this.inputSchema, `Step "${this.name}" input` );
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
validateOutput( output ) {
|
|
72
|
+
validate( output, this.outputSchema, `Step "${this.name}" output` );
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class EvaluatorValidator {
|
|
77
|
+
static validateDefinition( definition ) {
|
|
78
|
+
validate( definition, evaluatorSchema, 'Evaluator' );
|
|
79
|
+
}
|
|
80
|
+
constructor( { name, inputSchema } ) {
|
|
81
|
+
this.name = name;
|
|
82
|
+
this.inputSchema = inputSchema;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
validateInput( input ) {
|
|
86
|
+
validate( input, this.inputSchema, `Evaluator "${this.name}" input` );
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
validateOutput( output ) {
|
|
90
|
+
validate( output, evaluatorOutputSchema, `Evaluator "${this.name}" output` );
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate request payload
|
|
96
|
+
* @param {object} args - The request arguments
|
|
97
|
+
*/
|
|
98
|
+
export function validateRequestPayload( args ) {
|
|
99
|
+
validate( args, httpRequestSchema, 'Request payload' );
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validate executeInParallel
|
|
104
|
+
* @param {object} args - The request arguments
|
|
105
|
+
*/
|
|
106
|
+
export function validateExecuteInParallel( args ) {
|
|
107
|
+
validate( args, executeInParallelSchema, 'ExecuteInParallel' );
|
|
108
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ValidationError } from '#errors';
|
|
4
|
+
import { EvaluationResult } from '../evaluation_result.js';
|
|
5
|
+
import {
|
|
6
|
+
EvaluatorValidator,
|
|
7
|
+
StepValidator,
|
|
8
|
+
WorkflowValidator,
|
|
9
|
+
validateExecuteInParallel,
|
|
10
|
+
validateRequestPayload
|
|
11
|
+
} from './index.js';
|
|
12
|
+
|
|
13
|
+
const fn = async () => {};
|
|
14
|
+
|
|
15
|
+
const workflowArgs = {
|
|
16
|
+
name: 'valid_workflow',
|
|
17
|
+
description: 'Valid workflow',
|
|
18
|
+
inputSchema: z.object( { value: z.string() } ),
|
|
19
|
+
outputSchema: z.object( { result: z.string() } ),
|
|
20
|
+
fn,
|
|
21
|
+
options: {
|
|
22
|
+
activityOptions: {
|
|
23
|
+
startToCloseTimeout: '5m',
|
|
24
|
+
retry: { maximumAttempts: 2 }
|
|
25
|
+
},
|
|
26
|
+
disableTrace: true
|
|
27
|
+
},
|
|
28
|
+
aliases: [ 'old_workflow' ]
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const stepArgs = {
|
|
32
|
+
name: 'valid_step',
|
|
33
|
+
description: 'Valid step',
|
|
34
|
+
inputSchema: z.object( { value: z.string() } ),
|
|
35
|
+
outputSchema: z.object( { result: z.string() } ),
|
|
36
|
+
fn,
|
|
37
|
+
options: {
|
|
38
|
+
activityOptions: {
|
|
39
|
+
heartbeatTimeout: '30s'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const evaluatorArgs = {
|
|
45
|
+
name: 'valid_evaluator',
|
|
46
|
+
description: 'Valid evaluator',
|
|
47
|
+
inputSchema: z.object( { value: z.string() } ),
|
|
48
|
+
fn
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe( 'interface validators', () => {
|
|
52
|
+
describe( 'WorkflowValidator', () => {
|
|
53
|
+
it( 'validates workflow definitions, input, output, and invocation options', () => {
|
|
54
|
+
expect( () => WorkflowValidator.validateDefinition( workflowArgs ) ).not.toThrow();
|
|
55
|
+
const validator = new WorkflowValidator( workflowArgs );
|
|
56
|
+
|
|
57
|
+
expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
|
|
58
|
+
expect( () => validator.validateOutput( { result: 'ok' } ) ).not.toThrow();
|
|
59
|
+
expect( () => validator.validateInvocationOptions( {
|
|
60
|
+
detached: true,
|
|
61
|
+
activityOptions: { retry: { maximumAttempts: 1 } },
|
|
62
|
+
context: { info: { workflowId: 'test-workflow' } }
|
|
63
|
+
} ) ).not.toThrow();
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
it( 'throws ValidationError with useful prefixes for invalid workflow data', () => {
|
|
67
|
+
expect( () => WorkflowValidator.validateDefinition( { ...workflowArgs, name: 'bad-name' } ) ).toThrow( ValidationError );
|
|
68
|
+
expect( () => WorkflowValidator.validateDefinition( { ...workflowArgs, name: 'bad-name' } ) ).toThrow(
|
|
69
|
+
/Workflow validation failed/
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const validator = new WorkflowValidator( workflowArgs );
|
|
73
|
+
|
|
74
|
+
expect( () => validator.validateInput( { value: 1 } ) ).toThrow( ValidationError );
|
|
75
|
+
expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Workflow "valid_workflow" input validation failed/ );
|
|
76
|
+
expect( () => validator.validateOutput( { result: 1 } ) ).toThrow( /Workflow "valid_workflow" output validation failed/ );
|
|
77
|
+
expect( () => validator.validateInvocationOptions( {
|
|
78
|
+
options: { activityOptions: { retry: { maximumAttempts: 1 } } }
|
|
79
|
+
} ) ).toThrow( /Workflow "valid_workflow" invocation options validation failed/ );
|
|
80
|
+
} );
|
|
81
|
+
|
|
82
|
+
it( 'skips input and output validation when schemas are omitted', () => {
|
|
83
|
+
const validator = new WorkflowValidator( {
|
|
84
|
+
name: 'schema_less_workflow',
|
|
85
|
+
fn
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
expect( () => validator.validateInput( { anything: true } ) ).not.toThrow();
|
|
89
|
+
expect( () => validator.validateOutput( { anything: true } ) ).not.toThrow();
|
|
90
|
+
} );
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
describe( 'StepValidator', () => {
|
|
94
|
+
it( 'validates step definitions, input, and output', () => {
|
|
95
|
+
expect( () => StepValidator.validateDefinition( stepArgs ) ).not.toThrow();
|
|
96
|
+
const validator = new StepValidator( stepArgs );
|
|
97
|
+
|
|
98
|
+
expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
|
|
99
|
+
expect( () => validator.validateOutput( { result: 'ok' } ) ).not.toThrow();
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
it( 'throws ValidationError with useful prefixes for invalid step data', () => {
|
|
103
|
+
expect( () => StepValidator.validateDefinition( { ...stepArgs, fn: 'not-a-function' } ) ).toThrow(
|
|
104
|
+
/Step validation failed/
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const validator = new StepValidator( stepArgs );
|
|
108
|
+
|
|
109
|
+
expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Step "valid_step" input validation failed/ );
|
|
110
|
+
expect( () => validator.validateOutput( { result: 1 } ) ).toThrow( /Step "valid_step" output validation failed/ );
|
|
111
|
+
} );
|
|
112
|
+
} );
|
|
113
|
+
|
|
114
|
+
describe( 'EvaluatorValidator', () => {
|
|
115
|
+
it( 'validates evaluator definitions, input, and EvaluationResult output', () => {
|
|
116
|
+
expect( () => EvaluatorValidator.validateDefinition( evaluatorArgs ) ).not.toThrow();
|
|
117
|
+
const validator = new EvaluatorValidator( evaluatorArgs );
|
|
118
|
+
|
|
119
|
+
expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
|
|
120
|
+
expect( () => validator.validateOutput( new EvaluationResult( { value: 'pass', confidence: 1 } ) ) ).not.toThrow();
|
|
121
|
+
} );
|
|
122
|
+
|
|
123
|
+
it( 'throws ValidationError for output schemas and invalid evaluator output', () => {
|
|
124
|
+
expect( () => EvaluatorValidator.validateDefinition( {
|
|
125
|
+
...evaluatorArgs,
|
|
126
|
+
outputSchema: z.string()
|
|
127
|
+
} ) ).toThrow( /Evaluator validation failed/ );
|
|
128
|
+
|
|
129
|
+
const validator = new EvaluatorValidator( evaluatorArgs );
|
|
130
|
+
|
|
131
|
+
expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Evaluator "valid_evaluator" input validation failed/ );
|
|
132
|
+
expect( () => validator.validateOutput( { value: 'pass', confidence: 1 } ) ).toThrow(
|
|
133
|
+
/Evaluator "valid_evaluator" output validation failed/
|
|
134
|
+
);
|
|
135
|
+
} );
|
|
136
|
+
} );
|
|
137
|
+
|
|
138
|
+
describe( 'validateRequestPayload()', () => {
|
|
139
|
+
it( 'accepts valid request payloads', () => {
|
|
140
|
+
expect( () => validateRequestPayload( {
|
|
141
|
+
url: 'https://example.com',
|
|
142
|
+
method: 'POST',
|
|
143
|
+
payload: { ok: true },
|
|
144
|
+
headers: { authorization: 'Bearer token' }
|
|
145
|
+
} ) ).not.toThrow();
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
it( 'throws ValidationError for invalid request payloads', () => {
|
|
149
|
+
expect( () => validateRequestPayload( {
|
|
150
|
+
url: 'ftp://example.com',
|
|
151
|
+
method: 'POST'
|
|
152
|
+
} ) ).toThrow( /Request payload validation failed/ );
|
|
153
|
+
|
|
154
|
+
expect( () => validateRequestPayload( {
|
|
155
|
+
url: 'https://example.com',
|
|
156
|
+
method: 'OPTIONS'
|
|
157
|
+
} ) ).toThrow( ValidationError );
|
|
158
|
+
} );
|
|
159
|
+
} );
|
|
160
|
+
|
|
161
|
+
describe( 'validateExecuteInParallel()', () => {
|
|
162
|
+
it( 'accepts valid parallel execution configs', () => {
|
|
163
|
+
expect( () => validateExecuteInParallel( {
|
|
164
|
+
jobs: [ () => 'ok' ],
|
|
165
|
+
concurrency: Infinity,
|
|
166
|
+
onJobCompleted: () => {}
|
|
167
|
+
} ) ).not.toThrow();
|
|
168
|
+
} );
|
|
169
|
+
|
|
170
|
+
it( 'throws ValidationError for invalid parallel execution configs', () => {
|
|
171
|
+
expect( () => validateExecuteInParallel( {
|
|
172
|
+
jobs: [ () => 'ok' ],
|
|
173
|
+
concurrency: 0
|
|
174
|
+
} ) ).toThrow( /ExecuteInParallel validation failed/ );
|
|
175
|
+
|
|
176
|
+
expect( () => validateExecuteInParallel( {
|
|
177
|
+
jobs: [ 'not-a-function' ],
|
|
178
|
+
concurrency: 1
|
|
179
|
+
} ) ).toThrow( ValidationError );
|
|
180
|
+
} );
|
|
181
|
+
} );
|
|
182
|
+
} );
|