@outputai/core 0.7.1-next.5a29fff.0 → 0.7.1-next.930738c.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 (46) hide show
  1. package/package.json +1 -1
  2. package/src/consts.js +0 -4
  3. package/src/errors.js +6 -2
  4. package/src/hooks/index.d.ts +10 -0
  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/worker/index.js +1 -1
  26. package/src/worker/index.spec.js +1 -1
  27. package/src/worker/interceptors/activity.js +7 -24
  28. package/src/worker/interceptors/activity.spec.js +97 -66
  29. package/src/worker/interceptors/index.js +2 -2
  30. package/src/worker/interceptors/workflow.js +4 -7
  31. package/src/worker/interceptors/workflow.spec.js +49 -42
  32. package/src/worker/loader_tools.js +1 -1
  33. package/src/worker/loader_tools.spec.js +36 -0
  34. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
  35. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
  36. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
  37. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
  38. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
  39. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
  40. package/src/interface/validations/runtime.js +0 -20
  41. package/src/interface/validations/runtime.spec.js +0 -29
  42. package/src/interface/validations/schema_utils.js +0 -8
  43. package/src/interface/validations/schema_utils.spec.js +0 -67
  44. package/src/interface/validations/static.js +0 -137
  45. package/src/interface/validations/static.spec.js +0 -397
  46. package/src/interface/workflow.replay_compatibility.spec.js +0 -254
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.7.1-next.5a29fff.0",
3
+ "version": "0.7.1-next.930738c.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
+ }
@@ -85,6 +85,8 @@ export interface WorkflowDetails {
85
85
  export interface ErrorHookPayload {
86
86
  /** UUID v4 stamped per emit. Stable per-emit idempotency key. */
87
87
  eventId: string;
88
+ /** Timestamp of the event */
89
+ eventDate: number;
88
90
  /** Origin of the error: workflow execution, activity execution, or runtime. */
89
91
  source: 'workflow' | 'activity' | 'runtime';
90
92
  /** Information about the current workflow execution */
@@ -103,6 +105,8 @@ export interface ErrorHookPayload {
103
105
  export interface WorkflowStartHookPayload {
104
106
  /** UUID v4 stamped per emit. Stable per-emit idempotency key. */
105
107
  eventId: string;
108
+ /** Timestamp of the event */
109
+ eventDate: number;
106
110
  /** Information about the current workflow execution */
107
111
  workflowDetails: WorkflowDetails;
108
112
  }
@@ -113,6 +117,8 @@ export interface WorkflowStartHookPayload {
113
117
  export interface WorkflowEndHookPayload {
114
118
  /** UUID v4 stamped per emit. Stable per-emit idempotency key. */
115
119
  eventId: string;
120
+ /** Timestamp of the event */
121
+ eventDate: number;
116
122
  /** Information about the current workflow execution */
117
123
  workflowDetails: WorkflowDetails;
118
124
  }
@@ -123,6 +129,8 @@ export interface WorkflowEndHookPayload {
123
129
  export interface WorkflowErrorHookPayload {
124
130
  /** UUID v4 stamped per emit. Stable per-emit idempotency key. */
125
131
  eventId: string;
132
+ /** Timestamp of the event */
133
+ eventDate: number;
126
134
  /** Information about the current workflow execution */
127
135
  workflowDetails: WorkflowDetails;
128
136
  /** The error thrown. */
@@ -177,6 +185,8 @@ export declare function onWorkflowError( handler: ( payload: WorkflowErrorHookPa
177
185
  export interface OnHookEnvelope {
178
186
  /** UUID v4 stamped per emit. Stable per-emit idempotency key. */
179
187
  eventId: string;
188
+ /** Timestamp of the event */
189
+ eventDate: number;
180
190
  /** Information about the current workflow execution */
181
191
  workflowDetails: WorkflowDetails;
182
192
  /** Temporal's activityInfo(). */
@@ -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
+ } );