@output.ai/core 0.0.13 → 0.0.15
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 +1 -2
- package/src/index.d.ts +74 -171
- package/src/index.js +2 -1
- package/src/interface/schema_utils.js +34 -0
- package/src/interface/schema_utils.spec.js +67 -0
- package/src/interface/step.js +14 -8
- package/src/interface/validations/static.js +11 -10
- package/src/interface/validations/static.spec.js +14 -15
- package/src/interface/workflow.js +12 -12
- package/src/interface/zod_integration.spec.js +646 -0
- package/src/worker/catalog_workflow/index.js +30 -1
- package/src/worker/catalog_workflow/index.spec.js +83 -22
- package/src/interface/validations/ajv_provider.js +0 -3
- package/src/interface/validations/runtime.js +0 -69
- package/src/interface/validations/runtime.spec.js +0 -50
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { METADATA_ACCESS_SYMBOL } from '#consts';
|
|
2
3
|
import { Catalog, CatalogActivity, CatalogWorkflow } from './catalog.js';
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Zod schema to JSON Schema format.
|
|
7
|
+
*
|
|
8
|
+
* @param {any} schema - A zod schema
|
|
9
|
+
* @returns {object|null} JSON Schema object, or null if schema is invalid
|
|
10
|
+
*/
|
|
11
|
+
const convertToJsonSchema = schema => {
|
|
12
|
+
if ( !schema ) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return z.toJSONSchema( schema );
|
|
18
|
+
} catch ( error ) {
|
|
19
|
+
console.warn( 'Invalid schema provided (expected Zod schema):', schema, error );
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* Converts the list of workflows and the activities into the catalog information.
|
|
6
26
|
*
|
|
@@ -14,8 +34,17 @@ export const createCatalog = ( { workflows, activities } ) =>
|
|
|
14
34
|
workflows.reduce( ( catalog, workflow ) =>
|
|
15
35
|
catalog.addWorkflow( new CatalogWorkflow( {
|
|
16
36
|
...workflow,
|
|
37
|
+
inputSchema: convertToJsonSchema( workflow.inputSchema ),
|
|
38
|
+
outputSchema: convertToJsonSchema( workflow.outputSchema ),
|
|
17
39
|
activities: Object.entries( activities )
|
|
18
40
|
.filter( ( [ k ] ) => k.startsWith( `${workflow.path}#` ) )
|
|
19
|
-
.map( ( [ _, v ] ) =>
|
|
41
|
+
.map( ( [ _, v ] ) => {
|
|
42
|
+
const metadata = v[METADATA_ACCESS_SYMBOL];
|
|
43
|
+
return new CatalogActivity( {
|
|
44
|
+
...metadata,
|
|
45
|
+
inputSchema: convertToJsonSchema( metadata.inputSchema ),
|
|
46
|
+
outputSchema: convertToJsonSchema( metadata.outputSchema )
|
|
47
|
+
} );
|
|
48
|
+
} )
|
|
20
49
|
} ) )
|
|
21
50
|
, new Catalog() );
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
|
|
3
4
|
// Provide the same symbol to the module under test and to the test
|
|
4
5
|
const METADATA_ACCESS_SYMBOL = Symbol( '__metadata' );
|
|
@@ -19,16 +20,16 @@ describe( 'createCatalog', () => {
|
|
|
19
20
|
path: '/flows/flow1',
|
|
20
21
|
pathname: '/flows/flow1/workflow.js',
|
|
21
22
|
description: 'desc-flow1',
|
|
22
|
-
inputSchema: { in: 'f1' },
|
|
23
|
-
outputSchema: { out: 'f1' }
|
|
23
|
+
inputSchema: z.object( { in: z.literal( 'f1' ) } ),
|
|
24
|
+
outputSchema: z.object( { out: z.literal( 'f1' ) } )
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
27
|
name: 'flow2',
|
|
27
28
|
path: '/flows/flow2',
|
|
28
29
|
pathname: '/flows/flow2/workflow.js',
|
|
29
30
|
description: 'desc-flow2',
|
|
30
|
-
inputSchema: { in: 'f2' },
|
|
31
|
-
outputSchema: { out: 'f2' }
|
|
31
|
+
inputSchema: z.object( { in: z.literal( 'f2' ) } ),
|
|
32
|
+
outputSchema: z.object( { out: z.literal( 'f2' ) } )
|
|
32
33
|
}
|
|
33
34
|
];
|
|
34
35
|
|
|
@@ -37,8 +38,8 @@ describe( 'createCatalog', () => {
|
|
|
37
38
|
name: 'A1',
|
|
38
39
|
path: '/flows/flow1#A1',
|
|
39
40
|
description: 'desc-a1',
|
|
40
|
-
inputSchema: { in: 'a1' },
|
|
41
|
-
outputSchema: { out: 'a1' }
|
|
41
|
+
inputSchema: z.object( { in: z.literal( 'a1' ) } ),
|
|
42
|
+
outputSchema: z.object( { out: z.literal( 'a1' ) } )
|
|
42
43
|
} );
|
|
43
44
|
|
|
44
45
|
const activity2 = () => {};
|
|
@@ -46,8 +47,8 @@ describe( 'createCatalog', () => {
|
|
|
46
47
|
name: 'A2',
|
|
47
48
|
path: '/flows/flow1#A2',
|
|
48
49
|
description: 'desc-a2',
|
|
49
|
-
inputSchema: { in: 'a2' },
|
|
50
|
-
outputSchema: { out: 'a2' }
|
|
50
|
+
inputSchema: z.object( { in: z.literal( 'a2' ) } ),
|
|
51
|
+
outputSchema: z.object( { out: z.literal( 'a2' ) } )
|
|
51
52
|
} );
|
|
52
53
|
|
|
53
54
|
const activity3 = () => {};
|
|
@@ -55,8 +56,8 @@ describe( 'createCatalog', () => {
|
|
|
55
56
|
name: 'B1',
|
|
56
57
|
path: '/flows/flow2#B1',
|
|
57
58
|
description: 'desc-b1',
|
|
58
|
-
inputSchema: { in: 'b1' },
|
|
59
|
-
outputSchema: { out: 'b1' }
|
|
59
|
+
inputSchema: z.object( { in: z.literal( 'b1' ) } ),
|
|
60
|
+
outputSchema: z.object( { out: z.literal( 'b1' ) } )
|
|
60
61
|
} );
|
|
61
62
|
|
|
62
63
|
const activity4 = () => {};
|
|
@@ -64,8 +65,8 @@ describe( 'createCatalog', () => {
|
|
|
64
65
|
name: 'X',
|
|
65
66
|
path: '/other#X',
|
|
66
67
|
description: 'desc-x',
|
|
67
|
-
inputSchema: { in: 'x' },
|
|
68
|
-
outputSchema: { out: 'x' }
|
|
68
|
+
inputSchema: z.object( { in: z.literal( 'x' ) } ),
|
|
69
|
+
outputSchema: z.object( { out: z.literal( 'x' ) } )
|
|
69
70
|
} );
|
|
70
71
|
|
|
71
72
|
const activities = {
|
|
@@ -96,20 +97,56 @@ describe( 'createCatalog', () => {
|
|
|
96
97
|
name: 'flow1',
|
|
97
98
|
path: '/flows/flow1',
|
|
98
99
|
description: 'desc-flow1',
|
|
99
|
-
inputSchema: {
|
|
100
|
-
|
|
100
|
+
inputSchema: {
|
|
101
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: { in: { type: 'string', const: 'f1' } },
|
|
104
|
+
required: [ 'in' ],
|
|
105
|
+
additionalProperties: false
|
|
106
|
+
},
|
|
107
|
+
outputSchema: {
|
|
108
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: { out: { type: 'string', const: 'f1' } },
|
|
111
|
+
required: [ 'out' ],
|
|
112
|
+
additionalProperties: false
|
|
113
|
+
},
|
|
101
114
|
activities: [
|
|
102
115
|
{
|
|
103
116
|
name: 'A1',
|
|
104
117
|
description: 'desc-a1',
|
|
105
|
-
inputSchema: {
|
|
106
|
-
|
|
118
|
+
inputSchema: {
|
|
119
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: { in: { type: 'string', const: 'a1' } },
|
|
122
|
+
required: [ 'in' ],
|
|
123
|
+
additionalProperties: false
|
|
124
|
+
},
|
|
125
|
+
outputSchema: {
|
|
126
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: { out: { type: 'string', const: 'a1' } },
|
|
129
|
+
required: [ 'out' ],
|
|
130
|
+
additionalProperties: false
|
|
131
|
+
}
|
|
107
132
|
},
|
|
108
133
|
{
|
|
109
134
|
name: 'A2',
|
|
110
135
|
description: 'desc-a2',
|
|
111
|
-
inputSchema: {
|
|
112
|
-
|
|
136
|
+
inputSchema: {
|
|
137
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: { in: { type: 'string', const: 'a2' } },
|
|
140
|
+
required: [ 'in' ],
|
|
141
|
+
additionalProperties: false
|
|
142
|
+
},
|
|
143
|
+
outputSchema: {
|
|
144
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: { out: { type: 'string', const: 'a2' } },
|
|
147
|
+
required: [ 'out' ],
|
|
148
|
+
additionalProperties: false
|
|
149
|
+
}
|
|
113
150
|
}
|
|
114
151
|
]
|
|
115
152
|
},
|
|
@@ -117,14 +154,38 @@ describe( 'createCatalog', () => {
|
|
|
117
154
|
name: 'flow2',
|
|
118
155
|
path: '/flows/flow2',
|
|
119
156
|
description: 'desc-flow2',
|
|
120
|
-
inputSchema: {
|
|
121
|
-
|
|
157
|
+
inputSchema: {
|
|
158
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: { in: { type: 'string', const: 'f2' } },
|
|
161
|
+
required: [ 'in' ],
|
|
162
|
+
additionalProperties: false
|
|
163
|
+
},
|
|
164
|
+
outputSchema: {
|
|
165
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: { out: { type: 'string', const: 'f2' } },
|
|
168
|
+
required: [ 'out' ],
|
|
169
|
+
additionalProperties: false
|
|
170
|
+
},
|
|
122
171
|
activities: [
|
|
123
172
|
{
|
|
124
173
|
name: 'B1',
|
|
125
174
|
description: 'desc-b1',
|
|
126
|
-
inputSchema: {
|
|
127
|
-
|
|
175
|
+
inputSchema: {
|
|
176
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: { in: { type: 'string', const: 'b1' } },
|
|
179
|
+
required: [ 'in' ],
|
|
180
|
+
additionalProperties: false
|
|
181
|
+
},
|
|
182
|
+
outputSchema: {
|
|
183
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: { out: { type: 'string', const: 'b1' } },
|
|
186
|
+
required: [ 'out' ],
|
|
187
|
+
additionalProperties: false
|
|
188
|
+
}
|
|
128
189
|
}
|
|
129
190
|
]
|
|
130
191
|
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { FatalError } from '#errors';
|
|
2
|
-
import { ajv } from './ajv_provider.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Error type for when the input/output of a step/workflow doesn't match its input/output schema, respectively
|
|
6
|
-
*/
|
|
7
|
-
export class MismatchSchemaError extends FatalError {}
|
|
8
|
-
/**
|
|
9
|
-
* Error type for when the input of a step/workflow doesn't match its input schema
|
|
10
|
-
* @extends MismatchSchemaError
|
|
11
|
-
*/
|
|
12
|
-
export class InvalidInputError extends MismatchSchemaError {}
|
|
13
|
-
/**
|
|
14
|
-
* Error type for when the output of a step/workflow doesn't match its output schema
|
|
15
|
-
* @extends MismatchSchemaError
|
|
16
|
-
*/
|
|
17
|
-
export class InvalidOutputError extends MismatchSchemaError {}
|
|
18
|
-
|
|
19
|
-
const validate = ( ErrorClass, type, name, schema, payload ) => {
|
|
20
|
-
const validate = ajv.compile( schema );
|
|
21
|
-
const valid = validate( payload );
|
|
22
|
-
|
|
23
|
-
if ( !valid ) {
|
|
24
|
-
throw new ErrorClass( `Invalid input at ${type} "${name}": ${ajv.errorsText( validate.errors )}` );
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const validateInput = validate.bind( null, InvalidInputError );
|
|
29
|
-
const validateOutput = validate.bind( null, InvalidOutputError );
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Validates step input
|
|
33
|
-
*
|
|
34
|
-
* @param {name} name - step's name
|
|
35
|
-
* @param {object} schema - step's input schema
|
|
36
|
-
* @param {any} - the input to validate
|
|
37
|
-
* @throws InvalidInputError
|
|
38
|
-
*/
|
|
39
|
-
export const validateStepInput = validateInput.bind( null, 'step' );
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Validates step output
|
|
43
|
-
*
|
|
44
|
-
* @param {name} name - step's name
|
|
45
|
-
* @param {object} schema - step's output schema
|
|
46
|
-
* @param {any} - the output to validate
|
|
47
|
-
* @throws InvalidOutputError
|
|
48
|
-
*/
|
|
49
|
-
export const validateStepOutput = validateOutput.bind( null, 'step' );
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Validates workflow input
|
|
53
|
-
*
|
|
54
|
-
* @param {name} name - workflow's name
|
|
55
|
-
* @param {object} schema - workflow's input schema
|
|
56
|
-
* @param {any} - the input to validate
|
|
57
|
-
* @throws InvalidInputError
|
|
58
|
-
*/
|
|
59
|
-
export const validateWorkflowInput = validateInput.bind( null, 'workflow' );
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Validates workflow output
|
|
63
|
-
*
|
|
64
|
-
* @param {name} name - workflow's name
|
|
65
|
-
* @param {object} schema - workflow's output schema
|
|
66
|
-
* @param {any} - the output to validate
|
|
67
|
-
* @throws InvalidOutputError
|
|
68
|
-
*/
|
|
69
|
-
export const validateWorkflowOutput = validateOutput.bind( null, 'workflow' );
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { validateStepInput, validateWorkflowInput, InvalidInputError } from './runtime.js';
|
|
3
|
-
|
|
4
|
-
describe( 'Runtime validations spec', () => {
|
|
5
|
-
describe( 'validateStepInput', () => {
|
|
6
|
-
it( 'passes with matching payload', () => {
|
|
7
|
-
const schema = {
|
|
8
|
-
type: 'object',
|
|
9
|
-
properties: { id: { type: 'string' }, count: { type: 'integer', minimum: 0 } },
|
|
10
|
-
required: [ 'id' ],
|
|
11
|
-
additionalProperties: false
|
|
12
|
-
};
|
|
13
|
-
expect( () => validateStepInput( 'myStep', schema, { id: 'x', count: 2 } ) ).not.toThrow();
|
|
14
|
-
} );
|
|
15
|
-
|
|
16
|
-
it( 'rejects invalid payload', () => {
|
|
17
|
-
const schema = {
|
|
18
|
-
type: 'object',
|
|
19
|
-
properties: { id: { type: 'string' }, count: { type: 'integer', minimum: 0 } },
|
|
20
|
-
required: [ 'id' ],
|
|
21
|
-
additionalProperties: false
|
|
22
|
-
};
|
|
23
|
-
const error = new InvalidInputError( 'Invalid input at step "myStep": data must have required property \'id\'' );
|
|
24
|
-
expect( () => validateStepInput( 'myStep', schema, { count: -1 } ) ).toThrow( error );
|
|
25
|
-
} );
|
|
26
|
-
} );
|
|
27
|
-
|
|
28
|
-
describe( 'validateWorkflowInput', () => {
|
|
29
|
-
it( 'passes with matching payload', () => {
|
|
30
|
-
const schema = {
|
|
31
|
-
type: 'object',
|
|
32
|
-
properties: { name: { type: 'string' }, enabled: { type: 'boolean' } },
|
|
33
|
-
required: [ 'name' ],
|
|
34
|
-
additionalProperties: false
|
|
35
|
-
};
|
|
36
|
-
expect( () => validateWorkflowInput( 'myWorkflow', schema, { name: 'wf', enabled: true } ) ).not.toThrow();
|
|
37
|
-
} );
|
|
38
|
-
|
|
39
|
-
it( 'rejects invalid payload', () => {
|
|
40
|
-
const schema = {
|
|
41
|
-
type: 'object',
|
|
42
|
-
properties: { name: { type: 'string' }, enabled: { type: 'boolean' } },
|
|
43
|
-
required: [ 'name' ],
|
|
44
|
-
additionalProperties: false
|
|
45
|
-
};
|
|
46
|
-
const error = new InvalidInputError( 'Invalid input at workflow "myWorkflow": data must have required property \'name\'' );
|
|
47
|
-
expect( () => validateWorkflowInput( 'myWorkflow', schema, { enabled: 'yes' } ) ).toThrow( error );
|
|
48
|
-
} );
|
|
49
|
-
} );
|
|
50
|
-
} );
|