@outputai/core 0.7.1-next.ad03627.0 → 0.7.1-next.ba2fb0b.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/bin/worker.sh +6 -0
  2. package/package.json +1 -1
  3. package/src/consts.js +0 -4
  4. package/src/errors.js +6 -2
  5. package/src/interface/evaluator.js +7 -20
  6. package/src/interface/evaluator.spec.js +117 -1
  7. package/src/interface/step.js +8 -9
  8. package/src/interface/step.spec.js +124 -0
  9. package/src/interface/validations/index.js +108 -0
  10. package/src/interface/validations/index.spec.js +182 -0
  11. package/src/interface/validations/schemas.js +113 -0
  12. package/src/interface/validations/schemas.spec.js +209 -0
  13. package/src/interface/webhook.js +1 -1
  14. package/src/interface/webhook.spec.js +1 -1
  15. package/src/interface/workflow.d.ts +10 -9
  16. package/src/interface/workflow.js +76 -164
  17. package/src/interface/workflow.spec.js +637 -521
  18. package/src/interface/workflow_activity_options.js +16 -0
  19. package/src/interface/workflow_utils.js +1 -1
  20. package/src/interface/zod_integration.spec.js +2 -2
  21. package/src/internal_utils/aggregations.js +0 -10
  22. package/src/internal_utils/aggregations.spec.js +1 -48
  23. package/src/internal_utils/errors.js +14 -8
  24. package/src/internal_utils/errors.spec.js +73 -27
  25. package/src/utils/index.d.ts +19 -0
  26. package/src/utils/utils.js +53 -0
  27. package/src/utils/utils.spec.js +105 -1
  28. package/src/worker/bundle.js +26 -0
  29. package/src/worker/bundle.spec.js +53 -0
  30. package/src/worker/bundler_options.js +1 -1
  31. package/src/worker/bundler_options.spec.js +1 -1
  32. package/src/worker/catalog_workflow/catalog_job.js +148 -0
  33. package/src/worker/catalog_workflow/catalog_job.spec.js +232 -0
  34. package/src/worker/check.js +24 -0
  35. package/src/worker/connection_monitor.js +112 -0
  36. package/src/worker/connection_monitor.spec.js +199 -0
  37. package/src/worker/index.js +146 -41
  38. package/src/worker/index.spec.js +281 -109
  39. package/src/worker/interceptors/activity.js +7 -24
  40. package/src/worker/interceptors/activity.spec.js +97 -66
  41. package/src/worker/interceptors/index.js +4 -7
  42. package/src/worker/interceptors/modules.js +15 -0
  43. package/src/worker/interceptors/workflow.js +6 -8
  44. package/src/worker/interceptors/workflow.spec.js +49 -42
  45. package/src/worker/interruption.js +33 -0
  46. package/src/worker/interruption.spec.js +98 -0
  47. package/src/worker/loader/activities.js +75 -0
  48. package/src/worker/loader/activities.spec.js +213 -0
  49. package/src/worker/loader/hooks.js +28 -0
  50. package/src/worker/loader/hooks.spec.js +64 -0
  51. package/src/worker/loader/matchers.js +46 -0
  52. package/src/worker/loader/matchers.spec.js +140 -0
  53. package/src/worker/{loader_tools.js → loader/tools.js} +18 -66
  54. package/src/worker/{loader_tools.spec.js → loader/tools.spec.js} +24 -92
  55. package/src/worker/loader/workflows.js +82 -0
  56. package/src/worker/loader/workflows.spec.js +256 -0
  57. package/src/worker/{setup_telemetry.js → telemetry.js} +9 -4
  58. package/src/worker/{setup_telemetry.spec.js → telemetry.spec.js} +3 -3
  59. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
  60. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
  61. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
  62. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
  63. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
  64. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
  65. package/src/interface/validations/runtime.js +0 -20
  66. package/src/interface/validations/runtime.spec.js +0 -29
  67. package/src/interface/validations/schema_utils.js +0 -8
  68. package/src/interface/validations/schema_utils.spec.js +0 -67
  69. package/src/interface/validations/static.js +0 -137
  70. package/src/interface/validations/static.spec.js +0 -397
  71. package/src/interface/workflow.replay_compatibility.spec.js +0 -254
  72. package/src/worker/loader.js +0 -202
  73. package/src/worker/loader.spec.js +0 -498
  74. package/src/worker/shutdown.js +0 -26
  75. package/src/worker/shutdown.spec.js +0 -82
  76. package/src/worker/start_catalog.js +0 -96
  77. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.7.1-next.ad03627.0",
3
+ "version": "0.7.1-next.ba2fb0b.0",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
package/src/consts.js CHANGED
@@ -35,10 +35,6 @@ export const BusEventType = {
35
35
  RUNTIME_ERROR: 'runtime_error'
36
36
  };
37
37
 
38
- export const Signal = {
39
- SEND_AGGREGATIONS: 'send_aggregations'
40
- };
41
-
42
38
  export const WorkflowSpecialOutput = {
43
39
  CONTINUED_AS_NEW: '<<continued_as_new>>'
44
40
  };
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 { validateEvaluator } from './validations/static.js';
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
- import { EvaluationResult } from './evaluation_result.js';
4
+
7
5
  /**
8
- * Expose the function to create a new evaluator
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
- validateEvaluator( { name, description, inputSchema, fn, options } );
9
+ EvaluatorValidator.validateDefinition( { name, description, inputSchema, fn, options } );
10
+ const validator = new EvaluatorValidator( { name, inputSchema } );
19
11
 
20
12
  const wrapper = async input => {
21
- validateWithSchema( inputSchema, input, `Evaluator ${name} input` );
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', () => {
@@ -1,19 +1,18 @@
1
- // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
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
- validateStep( { name, description, inputSchema, outputSchema, fn, options } );
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
- validateWithSchema( inputSchema, input, `Step ${name} input` );
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
+ } );