@outputai/core 0.10.1-next.c717e35.0 → 0.10.1-next.cbef793.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.10.1-next.c717e35.0",
3
+ "version": "0.10.1-next.cbef793.0",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "engines": {
@@ -16,7 +16,7 @@ export type EvaluatorOptions = {
16
16
  /**
17
17
  * The handler function of an evaluator.
18
18
  *
19
- * @param input - The evaluator input; it matches the schema defined by `inputSchema`.
19
+ * @param input - Parsed evaluator input (`z.infer<inputSchema>`).
20
20
  *
21
21
  * @returns The result of the evaluation.
22
22
  */
@@ -30,14 +30,15 @@ export type EvaluatorFunction<
30
30
  /**
31
31
  * A wrapper around the user defined `fn` handler function.
32
32
  *
33
- * It has the same signature and returns the same value, calling the user function inside.
34
- *
35
- * It adds input validation based on the `inputSchema`.
33
+ * Callers pass values accepted by `inputSchema` (`z.input`). The wrapper parses input
34
+ * and invokes `fn`.
36
35
  */
37
- export type EvaluatorFunctionWrapper<EvaluatorFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
38
- Parameters<EvaluatorFunction> extends [infer Input] ?
39
- ( input: Input ) => ReturnType<EvaluatorFunction> :
40
- () => ReturnType<EvaluatorFunction>;
36
+ export type EvaluatorFunctionWrapper<
37
+ InputSchema extends AnyZodSchema | undefined = undefined,
38
+ Result extends EvaluationResult = EvaluationResult
39
+ > = InputSchema extends AnyZodSchema ?
40
+ ( input: z.input<InputSchema> ) => Promise<Result> :
41
+ () => Promise<Result>;
41
42
 
42
43
  /**
43
44
  * Creates an evaluation function. It is similar to a step, but must return an EvaluationResult.
@@ -67,4 +68,4 @@ export declare function evaluator<
67
68
  inputSchema: InputSchema;
68
69
  fn: EvaluatorFunction<InputSchema, Result>;
69
70
  options?: EvaluatorOptions;
70
- } ): EvaluatorFunctionWrapper<EvaluatorFunction<InputSchema, Result>>;
71
+ } ): EvaluatorFunctionWrapper<InputSchema, Result>;
@@ -13,11 +13,6 @@ export function evaluator( { name, description, inputSchema, fn, options } ) {
13
13
  description,
14
14
  inputSchema,
15
15
  options,
16
- handler: async input => {
17
- validator.validateInput( input );
18
- const output = await fn( input );
19
- validator.validateOutput( output );
20
- return output;
21
- }
16
+ handler: async input => validator.parseOutput( await fn( validator.parseInput( input ) ) )
22
17
  } );
23
18
  }
@@ -9,8 +9,8 @@ import {
9
9
  import { ValidationError } from '#errors';
10
10
 
11
11
  const validateDefinitionMock = vi.hoisted( () => vi.fn() );
12
- const validateInputMock = vi.hoisted( () => vi.fn() );
13
- const validateOutputMock = vi.hoisted( () => vi.fn() );
12
+ const parseInputMock = vi.hoisted( () => vi.fn( input => input ) );
13
+ const parseOutputMock = vi.hoisted( () => vi.fn( output => output ) );
14
14
  const validatorConstructorMock = vi.hoisted( () => vi.fn() );
15
15
  const createEvaluatorMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
16
16
 
@@ -22,8 +22,8 @@ vi.mock( './validations/index.js', () => {
22
22
 
23
23
  constructor( ...args ) {
24
24
  validatorConstructorMock( ...args );
25
- this.validateInput = validateInputMock;
26
- this.validateOutput = validateOutputMock;
25
+ this.parseInput = parseInputMock;
26
+ this.parseOutput = parseOutputMock;
27
27
  }
28
28
  }
29
29
 
@@ -87,15 +87,15 @@ describe( 'evaluator()', () => {
87
87
 
88
88
  await expect( wrapper( { value: 'input' } ) ).resolves.toBe( output );
89
89
 
90
- expect( validateInputMock ).toHaveBeenCalledWith( { value: 'input' } );
90
+ expect( parseInputMock ).toHaveBeenCalledWith( { value: 'input' } );
91
91
  expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
92
- expect( validateOutputMock ).toHaveBeenCalledWith( output );
92
+ expect( parseOutputMock ).toHaveBeenCalledWith( output );
93
93
  } );
94
94
 
95
95
  it( 'does not call the evaluator function when input validation throws', async () => {
96
96
  const { evaluator } = await import( './evaluator.js' );
97
97
  const error = new ValidationError( 'invalid input' );
98
- validateInputMock.mockImplementationOnce( () => {
98
+ parseInputMock.mockImplementationOnce( () => {
99
99
  throw error;
100
100
  } );
101
101
  const fn = vi.fn();
@@ -106,13 +106,13 @@ describe( 'evaluator()', () => {
106
106
 
107
107
  await expect( wrapper( { value: 'bad' } ) ).rejects.toBe( error );
108
108
  expect( fn ).not.toHaveBeenCalled();
109
- expect( validateOutputMock ).not.toHaveBeenCalled();
109
+ expect( parseOutputMock ).not.toHaveBeenCalled();
110
110
  } );
111
111
 
112
112
  it( 'propagates output validation errors after the evaluator function runs', async () => {
113
113
  const { evaluator } = await import( './evaluator.js' );
114
114
  const error = new ValidationError( 'invalid output' );
115
- validateOutputMock.mockImplementationOnce( () => {
115
+ parseOutputMock.mockImplementationOnce( () => {
116
116
  throw error;
117
117
  } );
118
118
  const output = { value: 'not-an-evaluation-result' };
@@ -124,7 +124,7 @@ describe( 'evaluator()', () => {
124
124
 
125
125
  await expect( wrapper( { value: 'input' } ) ).rejects.toBe( error );
126
126
  expect( fn ).toHaveBeenCalledOnce();
127
- expect( validateOutputMock ).toHaveBeenCalledWith( output );
127
+ expect( parseOutputMock ).toHaveBeenCalledWith( output );
128
128
  } );
129
129
  } );
130
130
 
@@ -15,31 +15,32 @@ export type StepOptions = {
15
15
  /**
16
16
  * The handler function of a step.
17
17
  *
18
- * @param input - The step input; it matches the schema defined by `inputSchema`.
18
+ * @param input - Parsed step input (`z.infer<inputSchema>`).
19
19
  *
20
- * @returns A value matching the schema defined by `outputSchema`.
20
+ * @returns A value accepted by `outputSchema` before parse (`z.input<outputSchema>`).
21
21
  */
22
22
  export type StepFunction<
23
23
  InputSchema extends AnyZodSchema | undefined = undefined,
24
24
  OutputSchema extends AnyZodSchema | undefined = undefined
25
25
  > = InputSchema extends AnyZodSchema ?
26
- ( input: z.infer<InputSchema> ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
27
- () => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
26
+ ( input: z.infer<InputSchema> ) => Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void> :
27
+ () => Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void>;
28
28
 
29
29
  /**
30
30
  * A wrapper around the user defined `fn` handler function.
31
31
  *
32
- * It accepts the same input and returns the same value, calling the user function inside.
33
- *
34
- * It adds input and output validation based on the `inputSchema`, `outputSchema`.
32
+ * Callers pass values accepted by `inputSchema` (`z.input`). The wrapper parses input,
33
+ * invokes `fn`, parses output, and returns `z.infer<outputSchema>`.
35
34
  *
36
- * @param input - The Step input; it matches the schema defined by `inputSchema`.
37
- * @returns A value matching the schema defined by `outputSchema`.
35
+ * @param input - The step input before `inputSchema` parse.
36
+ * @returns The step output after `outputSchema` parse.
38
37
  */
39
- export type StepFunctionWrapper<StepFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
40
- Parameters<StepFunction> extends [infer Input] ?
41
- ( input: Input ) => ReturnType<StepFunction> :
42
- () => ReturnType<StepFunction>;
38
+ export type StepFunctionWrapper<
39
+ InputSchema extends AnyZodSchema | undefined = undefined,
40
+ OutputSchema extends AnyZodSchema | undefined = undefined
41
+ > = InputSchema extends AnyZodSchema ?
42
+ ( input: z.input<InputSchema> ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
43
+ () => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
43
44
 
44
45
  /**
45
46
  * Creates a step.
@@ -123,7 +124,7 @@ export type StepFunctionWrapper<StepFunction extends ( ...args: any ) => any> =
123
124
  * @param params.outputSchema - Zod schema for the `fn` output
124
125
  * @param params.fn - A handler function containing the step code
125
126
  * @param params.options - Optional step options.
126
- * @returns The same handler function set at `fn`
127
+ * @returns A wrapper that parses input/output around `fn`
127
128
  */
128
129
  export declare function step<
129
130
  InputSchema extends AnyZodSchema | undefined = undefined,
@@ -135,4 +136,4 @@ export declare function step<
135
136
  outputSchema?: OutputSchema;
136
137
  fn: StepFunction<InputSchema, OutputSchema>;
137
138
  options?: StepOptions;
138
- } ): StepFunctionWrapper<StepFunction<InputSchema, OutputSchema>>;
139
+ } ): StepFunctionWrapper<InputSchema, OutputSchema>;
@@ -14,11 +14,6 @@ export function step( { name, description, inputSchema, outputSchema, fn, option
14
14
  inputSchema,
15
15
  outputSchema,
16
16
  options,
17
- handler: async input => {
18
- validator.validateInput( input );
19
- const output = await fn( input );
20
- validator.validateOutput( output );
21
- return output;
22
- }
17
+ handler: async input => validator.parseOutput( await fn( validator.parseInput( input ) ) )
23
18
  } );
24
19
  }
@@ -2,8 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { ValidationError } from '#errors';
3
3
 
4
4
  const validateDefinitionMock = vi.hoisted( () => vi.fn() );
5
- const validateInputMock = vi.hoisted( () => vi.fn() );
6
- const validateOutputMock = vi.hoisted( () => vi.fn() );
5
+ const parseInputMock = vi.hoisted( () => vi.fn( input => input ) );
6
+ const parseOutputMock = vi.hoisted( () => vi.fn( output => output ) );
7
7
  const validatorConstructorMock = vi.hoisted( () => vi.fn() );
8
8
  const createStepMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
9
9
 
@@ -15,8 +15,8 @@ vi.mock( './validations/index.js', () => {
15
15
 
16
16
  constructor( ...args ) {
17
17
  validatorConstructorMock( ...args );
18
- this.validateInput = validateInputMock;
19
- this.validateOutput = validateOutputMock;
18
+ this.parseInput = parseInputMock;
19
+ this.parseOutput = parseOutputMock;
20
20
  }
21
21
  }
22
22
 
@@ -86,15 +86,15 @@ describe( 'step()', () => {
86
86
 
87
87
  await expect( wrapper( { value: 'input' } ) ).resolves.toBe( output );
88
88
 
89
- expect( validateInputMock ).toHaveBeenCalledWith( { value: 'input' } );
89
+ expect( parseInputMock ).toHaveBeenCalledWith( { value: 'input' } );
90
90
  expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
91
- expect( validateOutputMock ).toHaveBeenCalledWith( output );
91
+ expect( parseOutputMock ).toHaveBeenCalledWith( output );
92
92
  } );
93
93
 
94
94
  it( 'does not call the step function when input validation throws', async () => {
95
95
  const { step } = await import( './step.js' );
96
96
  const error = new ValidationError( 'invalid input' );
97
- validateInputMock.mockImplementationOnce( () => {
97
+ parseInputMock.mockImplementationOnce( () => {
98
98
  throw error;
99
99
  } );
100
100
  const fn = vi.fn();
@@ -105,13 +105,13 @@ describe( 'step()', () => {
105
105
 
106
106
  await expect( wrapper( { value: 'bad' } ) ).rejects.toBe( error );
107
107
  expect( fn ).not.toHaveBeenCalled();
108
- expect( validateOutputMock ).not.toHaveBeenCalled();
108
+ expect( parseOutputMock ).not.toHaveBeenCalled();
109
109
  } );
110
110
 
111
111
  it( 'propagates output validation errors after the step function runs', async () => {
112
112
  const { step } = await import( './step.js' );
113
113
  const error = new ValidationError( 'invalid output' );
114
- validateOutputMock.mockImplementationOnce( () => {
114
+ parseOutputMock.mockImplementationOnce( () => {
115
115
  throw error;
116
116
  } );
117
117
  const output = { ok: false };
@@ -123,6 +123,6 @@ describe( 'step()', () => {
123
123
 
124
124
  await expect( wrapper( { value: 'input' } ) ).rejects.toBe( error );
125
125
  expect( fn ).toHaveBeenCalledOnce();
126
- expect( validateOutputMock ).toHaveBeenCalledWith( output );
126
+ expect( parseOutputMock ).toHaveBeenCalledWith( output );
127
127
  } );
128
128
  } );
@@ -1,4 +1,5 @@
1
1
  import { ValidationError } from '#errors';
2
+ import { ComponentType } from '#consts';
2
3
  import { prettifyError } from 'zod';
3
4
  import {
4
5
  evaluatorOutputSchema,
@@ -10,84 +11,63 @@ import {
10
11
  workflowSchema
11
12
  } from './schemas.js';
12
13
 
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 = '' ) => {
14
+ const capitalize = word => word.at( 0 ).toUpperCase() + word.slice( 1 );
15
+
16
+ const validate = ( schema, data, prefix ) => {
22
17
  if ( !schema ) {
23
- return;
18
+ return data;
24
19
  }
25
-
26
20
  const result = schema.safeParse( data );
27
- if ( !result.success ) {
21
+ if ( result.success === false ) {
28
22
  throw new ValidationError( `${prefix} validation failed: ${prettifyError( result.error ) }` );
29
23
  }
24
+ return result.data;
30
25
  };
31
26
 
32
- export class WorkflowValidator {
27
+ export class Validator {
28
+ static label;
29
+ static definitionSchema;
30
+
33
31
  static validateDefinition( definition ) {
34
- validate( definition, workflowSchema, 'Workflow' );
32
+ validate( this.definitionSchema, definition, this.label );
35
33
  }
36
34
 
37
35
  constructor( { name, inputSchema, outputSchema } ) {
38
36
  this.name = name;
39
37
  this.inputSchema = inputSchema;
40
38
  this.outputSchema = outputSchema;
39
+ this.prefix = `${this.constructor.label} "${this.name}"`;
41
40
  }
42
41
 
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` );
42
+ parseInput( input ) {
43
+ return validate( this.inputSchema, input, `${this.prefix} input` );
49
44
  }
50
45
 
51
- validateInvocationOptions( options ) {
52
- validate( options, workflowInvocationOptionsSchema, `Workflow "${this.name}" invocation options` );
46
+ parseOutput( output ) {
47
+ return validate( this.outputSchema, output, `${this.prefix} output` );
53
48
  }
54
49
  }
55
50
 
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
- }
51
+ export class WorkflowValidator extends Validator {
52
+ static label = capitalize( ComponentType.WORKFLOW );
53
+ static definitionSchema = workflowSchema;
70
54
 
71
- validateOutput( output ) {
72
- validate( output, this.outputSchema, `Step "${this.name}" output` );
55
+ parseInvocationOptions( options ) {
56
+ return validate( workflowInvocationOptionsSchema, options, `${this.prefix} invocation options` );
73
57
  }
74
58
  }
75
59
 
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
- }
60
+ export class StepValidator extends Validator {
61
+ static label = capitalize( ComponentType.STEP );
62
+ static definitionSchema = stepSchema;
63
+ }
84
64
 
85
- validateInput( input ) {
86
- validate( input, this.inputSchema, `Evaluator "${this.name}" input` );
87
- }
65
+ export class EvaluatorValidator extends Validator {
66
+ static label = capitalize( ComponentType.EVALUATOR );
67
+ static definitionSchema = evaluatorSchema;
88
68
 
89
- validateOutput( output ) {
90
- validate( output, evaluatorOutputSchema, `Evaluator "${this.name}" output` );
69
+ constructor( { name, inputSchema } ) {
70
+ super( { name, inputSchema, outputSchema: evaluatorOutputSchema } );
91
71
  }
92
72
  }
93
73
 
@@ -96,7 +76,7 @@ export class EvaluatorValidator {
96
76
  * @param {object} args - The request arguments
97
77
  */
98
78
  export function validateRequestPayload( args ) {
99
- validate( args, httpRequestSchema, 'Request payload' );
79
+ validate( httpRequestSchema, args, 'Request payload' );
100
80
  };
101
81
 
102
82
  /**
@@ -104,5 +84,5 @@ export function validateRequestPayload( args ) {
104
84
  * @param {object} args - The request arguments
105
85
  */
106
86
  export function validateExecuteInParallel( args ) {
107
- validate( args, executeInParallelSchema, 'ExecuteInParallel' );
87
+ validate( executeInParallelSchema, args, 'ExecuteInParallel' );
108
88
  };
@@ -53,10 +53,12 @@ describe( 'interface validators', () => {
53
53
  it( 'validates workflow definitions, input, output, and invocation options', () => {
54
54
  expect( () => WorkflowValidator.validateDefinition( workflowArgs ) ).not.toThrow();
55
55
  const validator = new WorkflowValidator( workflowArgs );
56
+ const input = { value: 'ok' };
57
+ const output = { result: 'ok' };
56
58
 
57
- expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
58
- expect( () => validator.validateOutput( { result: 'ok' } ) ).not.toThrow();
59
- expect( () => validator.validateInvocationOptions( {
59
+ expect( validator.parseInput( input ) ).toEqual( input );
60
+ expect( validator.parseOutput( output ) ).toEqual( output );
61
+ expect( () => validator.parseInvocationOptions( {
60
62
  detached: true,
61
63
  activityOptions: { retry: { maximumAttempts: 1 } },
62
64
  context: { info: { workflowId: 'test-workflow' } }
@@ -71,10 +73,10 @@ describe( 'interface validators', () => {
71
73
 
72
74
  const validator = new WorkflowValidator( workflowArgs );
73
75
 
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( {
76
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( ValidationError );
77
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( /Workflow "valid_workflow" input validation failed/ );
78
+ expect( () => validator.parseOutput( { result: 1 } ) ).toThrow( /Workflow "valid_workflow" output validation failed/ );
79
+ expect( () => validator.parseInvocationOptions( {
78
80
  options: { activityOptions: { retry: { maximumAttempts: 1 } } }
79
81
  } ) ).toThrow( /Workflow "valid_workflow" invocation options validation failed/ );
80
82
  } );
@@ -84,9 +86,11 @@ describe( 'interface validators', () => {
84
86
  name: 'schema_less_workflow',
85
87
  fn
86
88
  } );
89
+ const input = { anything: true };
90
+ const output = { anything: true };
87
91
 
88
- expect( () => validator.validateInput( { anything: true } ) ).not.toThrow();
89
- expect( () => validator.validateOutput( { anything: true } ) ).not.toThrow();
92
+ expect( validator.parseInput( input ) ).toBe( input );
93
+ expect( validator.parseOutput( output ) ).toBe( output );
90
94
  } );
91
95
  } );
92
96
 
@@ -94,9 +98,11 @@ describe( 'interface validators', () => {
94
98
  it( 'validates step definitions, input, and output', () => {
95
99
  expect( () => StepValidator.validateDefinition( stepArgs ) ).not.toThrow();
96
100
  const validator = new StepValidator( stepArgs );
101
+ const input = { value: 'ok' };
102
+ const output = { result: 'ok' };
97
103
 
98
- expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
99
- expect( () => validator.validateOutput( { result: 'ok' } ) ).not.toThrow();
104
+ expect( validator.parseInput( input ) ).toEqual( input );
105
+ expect( validator.parseOutput( output ) ).toEqual( output );
100
106
  } );
101
107
 
102
108
  it( 'throws ValidationError with useful prefixes for invalid step data', () => {
@@ -106,8 +112,8 @@ describe( 'interface validators', () => {
106
112
 
107
113
  const validator = new StepValidator( stepArgs );
108
114
 
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/ );
115
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( /Step "valid_step" input validation failed/ );
116
+ expect( () => validator.parseOutput( { result: 1 } ) ).toThrow( /Step "valid_step" output validation failed/ );
111
117
  } );
112
118
  } );
113
119
 
@@ -115,9 +121,11 @@ describe( 'interface validators', () => {
115
121
  it( 'validates evaluator definitions, input, and EvaluationResult output', () => {
116
122
  expect( () => EvaluatorValidator.validateDefinition( evaluatorArgs ) ).not.toThrow();
117
123
  const validator = new EvaluatorValidator( evaluatorArgs );
124
+ const input = { value: 'ok' };
125
+ const output = new EvaluationResult( { value: 'pass', confidence: 1 } );
118
126
 
119
- expect( () => validator.validateInput( { value: 'ok' } ) ).not.toThrow();
120
- expect( () => validator.validateOutput( new EvaluationResult( { value: 'pass', confidence: 1 } ) ) ).not.toThrow();
127
+ expect( validator.parseInput( input ) ).toEqual( input );
128
+ expect( validator.parseOutput( output ) ).toEqual( output );
121
129
  } );
122
130
 
123
131
  it( 'throws ValidationError for output schemas and invalid evaluator output', () => {
@@ -128,8 +136,8 @@ describe( 'interface validators', () => {
128
136
 
129
137
  const validator = new EvaluatorValidator( evaluatorArgs );
130
138
 
131
- expect( () => validator.validateInput( { value: 1 } ) ).toThrow( /Evaluator "valid_evaluator" input validation failed/ );
132
- expect( () => validator.validateOutput( { value: 'pass', confidence: 1 } ) ).toThrow(
139
+ expect( () => validator.parseInput( { value: 1 } ) ).toThrow( /Evaluator "valid_evaluator" input validation failed/ );
140
+ expect( () => validator.parseOutput( { value: 'pass', confidence: 1 } ) ).toThrow(
133
141
  /Evaluator "valid_evaluator" output validation failed/
134
142
  );
135
143
  } );
@@ -21,6 +21,8 @@ export type WorkflowContext<
21
21
  * It acts as a checkpoint when the workflow gets too long or approaches certain scaling limits.
22
22
  *
23
23
  * It accepts input with the same schema as the parent workflow function (`inputSchema`).
24
+ * The next run parses that input like any other workflow start; pass wire-format values if
25
+ * `inputSchema` transforms are not safe to apply twice.
24
26
  *
25
27
  * Calling this function must be the last statement in the workflow, accompanied by a `return`:
26
28
  *
@@ -38,7 +40,7 @@ export type WorkflowContext<
38
40
  * @returns The workflow output type for type-checking; never returns at runtime.
39
41
  */
40
42
  continueAsNew: InputSchema extends AnyZodSchema ?
41
- ( input: z.infer<InputSchema> ) => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ) :
43
+ ( input: z.input<InputSchema> ) => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ) :
42
44
  () => ( OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void ),
43
45
 
44
46
  /**
@@ -122,39 +124,44 @@ export type WorkflowOptions = {
122
124
  /**
123
125
  * The handler function of a workflow.
124
126
  *
125
- * @param input - The workflow input; it matches the schema defined by `inputSchema`.
127
+ * @param input - Parsed workflow input (`z.infer<inputSchema>`).
126
128
  * @param context - A context object with tools and information.
127
129
  *
128
- * @returns A value matching the schema defined by `outputSchema`.
130
+ * @returns A value accepted by `outputSchema` before parse (`z.input<outputSchema>`).
129
131
  */
130
132
  export type WorkflowFunction<
131
133
  InputSchema extends AnyZodSchema | undefined = undefined,
132
134
  OutputSchema extends AnyZodSchema | undefined = undefined
133
135
  > = InputSchema extends AnyZodSchema ?
134
136
  ( input: z.infer<InputSchema>, context: WorkflowContext<InputSchema, OutputSchema> ) =>
135
- Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
137
+ Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void> :
136
138
  ( input: undefined | null, context: WorkflowContext<InputSchema, OutputSchema> ) =>
137
- Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
139
+ Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void>;
138
140
 
139
141
  /**
140
142
  * A wrapper around the user defined `fn` handler function.
141
143
  *
142
- * It accepts the same input and returns the same value, calling the user function inside.
144
+ * Callers pass values accepted by `inputSchema` (`z.input`). The wrapper parses input,
145
+ * invokes `fn`, parses output, and returns `z.infer<outputSchema>`.
143
146
  *
144
147
  * The second argument is a WorkflowInvocationOptions object, allowing workflows configuration overwrite.
145
148
  *
146
- * It adds input and output validation based on the `inputSchema`, `outputSchema`.
147
- *
148
- * @param input - The workflow input; it matches the schema defined by `inputSchema`.
149
+ * @param input - The workflow input before `inputSchema` parse.
149
150
  * @param options - Additional options for the invocation.
150
- * @returns A value matching the schema defined by `outputSchema`.
151
+ * @returns The workflow output after `outputSchema` parse.
151
152
  */
152
- export type WorkflowFunctionWrapper<WorkflowFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
153
- [Parameters<WorkflowFunction>[0]] extends [undefined | null] ?
154
- ( input?: undefined | null, options?: WorkflowInvocationOptions ) =>
155
- ReturnType<WorkflowFunction> :
156
- ( input: Parameters<WorkflowFunction>[0], options?: WorkflowInvocationOptions ) =>
157
- ReturnType<WorkflowFunction>;
153
+ export type WorkflowFunctionWrapper<
154
+ InputSchema extends AnyZodSchema | undefined = undefined,
155
+ OutputSchema extends AnyZodSchema | undefined = undefined
156
+ > = InputSchema extends AnyZodSchema ?
157
+ (
158
+ input: z.input<InputSchema>,
159
+ options?: WorkflowInvocationOptions
160
+ ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
161
+ (
162
+ input?: undefined | null,
163
+ options?: WorkflowInvocationOptions
164
+ ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
158
165
 
159
166
  /**
160
167
  * Creates a workflow.
@@ -270,7 +277,7 @@ export type WorkflowFunctionWrapper<WorkflowFunction extends ( ...args: any ) =>
270
277
  * @param params.outputSchema - Zod schema for workflow output
271
278
  * @param params.fn - A function containing the workflow code
272
279
  * @param params.options - Optional workflow options.
273
- * @returns The same handler function set at `fn` with a different signature
280
+ * @returns A wrapper that parses input/output around `fn`
274
281
  */
275
282
  export declare function workflow<
276
283
  InputSchema extends AnyZodSchema | undefined = undefined,
@@ -288,4 +295,4 @@ export declare function workflow<
288
295
  * while maintaining backward compatibility with existing callers.
289
296
  */
290
297
  aliases?: string[];
291
- } ): WorkflowFunctionWrapper<WorkflowFunction<InputSchema, OutputSchema>>;
298
+ } ): WorkflowFunctionWrapper<InputSchema, OutputSchema>;
@@ -10,17 +10,6 @@ import { createWorkflow } from '#helpers/component';
10
10
  import { FatalError } from '#errors';
11
11
  import * as C from '#consts';
12
12
 
13
- /**
14
- * Execute the workflow without temporal, using the fn handler function.
15
- * This is important to allow for workflows to have unit tests
16
- */
17
- const executeWithoutTemporal = async ( { input, validator, handler, contextOverrides = {} } ) => {
18
- validator.validateInput( input );
19
- const output = await handler( input, deepMerge( WorkflowContext.build(), contextOverrides ) );
20
- validator.validateOutput( output );
21
- return output;
22
- };
23
-
24
13
  /**
25
14
  * Add a global dispatcher function to be used to invoke activities.
26
15
  * This will replace direct activity invocation in the user code by the webpack loader.
@@ -51,12 +40,14 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
51
40
  const disableTrace = options.disableTrace ?? defaultOptions.disableTrace;
52
41
  const validator = new WorkflowValidator( { name, inputSchema, outputSchema } );
53
42
 
54
- const handler = async ( input, invocationOptions = {} ) => {
55
- validator.validateInvocationOptions( invocationOptions );
43
+ const handler = async ( input, rawInvocationOptions = {} ) => {
44
+ const invocationOptions = validator.parseInvocationOptions( rawInvocationOptions );
56
45
 
57
46
  // If called outside Temporal workflow context, just execute the handler function
58
47
  if ( !inWorkflowContext() ) {
59
- return executeWithoutTemporal( { input, validator, handler: fn, contextOverrides: invocationOptions?.context } );
48
+ return validator.parseOutput(
49
+ await fn( validator.parseInput( input ), deepMerge( WorkflowContext.build(), invocationOptions?.context ) )
50
+ );
60
51
  }
61
52
 
62
53
  const { workflowId, runId, memo, root } = workflowInfo();
@@ -99,9 +90,7 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
99
90
  };
100
91
 
101
92
  try {
102
- validator.validateInput( input );
103
- const output = await fn( input, WorkflowContext.build() );
104
- validator.validateOutput( output );
93
+ const output = validator.parseOutput( await fn( validator.parseInput( input ), WorkflowContext.build() ) );
105
94
 
106
95
  return { [C.WORKFLOW_WRAPPER_VERSION_FIELD]: 1, output, ...traceDestinations };
107
96
  } catch ( error ) {
@@ -15,9 +15,9 @@ const executeChildMock = vi.hoisted( () => vi.fn() );
15
15
  const workflowInfoMock = vi.hoisted( () => vi.fn() );
16
16
  const continueAsNewMock = vi.hoisted( () => vi.fn() );
17
17
  const validateDefinitionMock = vi.hoisted( () => vi.fn() );
18
- const validateInputMock = vi.hoisted( () => vi.fn() );
19
- const validateOutputMock = vi.hoisted( () => vi.fn() );
20
- const validateInvocationOptionsMock = vi.hoisted( () => vi.fn() );
18
+ const parseInputMock = vi.hoisted( () => vi.fn( input => input ) );
19
+ const parseOutputMock = vi.hoisted( () => vi.fn( output => output ) );
20
+ const parseInvocationOptionsMock = vi.hoisted( () => vi.fn( options => options ) );
21
21
  const validatorConstructorMock = vi.hoisted( () => vi.fn() );
22
22
  const createWorkflowMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
23
23
 
@@ -29,9 +29,9 @@ vi.mock( './validations/index.js', () => {
29
29
 
30
30
  constructor( ...args ) {
31
31
  validatorConstructorMock( ...args );
32
- this.validateInput = validateInputMock;
33
- this.validateOutput = validateOutputMock;
34
- this.validateInvocationOptions = validateInvocationOptionsMock;
32
+ this.parseInput = parseInputMock;
33
+ this.parseOutput = parseOutputMock;
34
+ this.parseInvocationOptions = parseInvocationOptionsMock;
35
35
  }
36
36
  }
37
37
 
@@ -210,9 +210,9 @@ describe( 'workflow()', () => {
210
210
  value: 'test-workflow-ok',
211
211
  extra: 'custom'
212
212
  } );
213
- expect( validateInvocationOptionsMock ).toHaveBeenCalledWith( { context: { extra: 'custom' } } );
214
- expect( validateInputMock ).toHaveBeenCalledWith( { suffix: '-ok' } );
215
- expect( validateOutputMock ).toHaveBeenCalledWith( { value: 'test-workflow-ok', extra: 'custom' } );
213
+ expect( parseInvocationOptionsMock ).toHaveBeenCalledWith( { context: { extra: 'custom' } } );
214
+ expect( parseInputMock ).toHaveBeenCalledWith( { suffix: '-ok' } );
215
+ expect( parseOutputMock ).toHaveBeenCalledWith( { value: 'test-workflow-ok', extra: 'custom' } );
216
216
  expect( workflowInfoMock ).not.toHaveBeenCalled();
217
217
  expect( proxyActivitiesMock ).not.toHaveBeenCalled();
218
218
  } );
@@ -220,7 +220,7 @@ describe( 'workflow()', () => {
220
220
  it( 'does not run fn when plain function input validation fails', async () => {
221
221
  const { workflow } = await import( './workflow.js' );
222
222
  const error = new ValidationError( 'invalid input' );
223
- validateInputMock.mockImplementationOnce( () => {
223
+ parseInputMock.mockImplementationOnce( () => {
224
224
  throw error;
225
225
  } );
226
226
  const fn = vi.fn();
@@ -234,13 +234,13 @@ describe( 'workflow()', () => {
234
234
 
235
235
  await expect( wf( { value: 1 } ) ).rejects.toBe( error );
236
236
  expect( fn ).not.toHaveBeenCalled();
237
- expect( validateOutputMock ).not.toHaveBeenCalled();
237
+ expect( parseOutputMock ).not.toHaveBeenCalled();
238
238
  } );
239
239
 
240
240
  it( 'propagates plain function output validation errors after fn runs', async () => {
241
241
  const { workflow } = await import( './workflow.js' );
242
242
  const error = new ValidationError( 'invalid output' );
243
- validateOutputMock.mockImplementationOnce( () => {
243
+ parseOutputMock.mockImplementationOnce( () => {
244
244
  throw error;
245
245
  } );
246
246
  const output = { result: 1 };
@@ -257,7 +257,7 @@ describe( 'workflow()', () => {
257
257
  expect( fn ).toHaveBeenCalledWith( { value: 'ok' }, expect.objectContaining( {
258
258
  info: { workflowId: 'test-workflow', runId: 'test-run' }
259
259
  } ) );
260
- expect( validateOutputMock ).toHaveBeenCalledWith( output );
260
+ expect( parseOutputMock ).toHaveBeenCalledWith( output );
261
261
  } );
262
262
  } );
263
263
 
@@ -294,7 +294,7 @@ describe( 'workflow()', () => {
294
294
  retry: { maximumAttempts: 7 }
295
295
  }
296
296
  } ) ).resolves.toEqual( { child: 'ok' } );
297
- expect( validateInvocationOptionsMock ).toHaveBeenCalledWith( {
297
+ expect( parseInvocationOptionsMock ).toHaveBeenCalledWith( {
298
298
  detached: true,
299
299
  context: { testOnly: true },
300
300
  activityOptions: {
@@ -543,7 +543,7 @@ describe( 'workflow()', () => {
543
543
  fn: async () => ( { result: 1 } )
544
544
  } ) );
545
545
 
546
- validateInputMock.mockImplementationOnce( () => {
546
+ parseInputMock.mockImplementationOnce( () => {
547
547
  throw inputError;
548
548
  } );
549
549
  await expect( wf( { value: 1 } ) ).rejects.toBe( inputError );
@@ -551,7 +551,7 @@ describe( 'workflow()', () => {
551
551
 
552
552
  delete globalThis[INVOKE_ACTIVITY_SYMBOL];
553
553
  setWorkflowInfo( { workflowType: 'runtime_validation_wf', memo: {} } );
554
- validateOutputMock.mockImplementationOnce( () => {
554
+ parseOutputMock.mockImplementationOnce( () => {
555
555
  throw outputError;
556
556
  } );
557
557
  await expect( wf( { value: 'ok' } ) ).rejects.toBe( outputError );