vovk 3.7.0 → 4.0.0-beta.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/CHANGELOG.md +74 -0
- package/dist/client/create-rpc.d.ts +3 -3
- package/dist/client/create-rpc.js +6 -8
- package/dist/client/default-stream-handler.d.ts +1 -5
- package/dist/client/default-stream-handler.js +53 -45
- package/dist/client/fetcher.d.ts +1 -1
- package/dist/client/fetcher.js +53 -4
- package/dist/client/progressive.d.ts +1 -5
- package/dist/client/progressive.js +19 -9
- package/dist/client/serialize-query.d.ts +0 -11
- package/dist/client/serialize-query.js +4 -28
- package/dist/core/controllers-to-static-params.d.ts +1 -2
- package/dist/core/controllers-to-static-params.js +1 -2
- package/dist/core/create-decorator.d.ts +1 -1
- package/dist/core/decorate.d.ts +2 -6
- package/dist/core/decorators.js +3 -3
- package/dist/core/get-schema.d.ts +1 -1
- package/dist/core/get-schema.js +1 -3
- package/dist/core/http-exception.d.ts +1 -1
- package/dist/core/http-exception.js +1 -1
- package/dist/core/init-segment.js +1 -1
- package/dist/core/json-lines-responder.d.ts +8 -12
- package/dist/core/json-lines-responder.js +42 -20
- package/dist/core/vovk-app.d.ts +1 -1
- package/dist/core/vovk-app.js +62 -16
- package/dist/index.d.ts +16 -18
- package/dist/index.js +14 -15
- package/dist/internal.d.ts +14 -12
- package/dist/internal.js +8 -7
- package/dist/openapi/error.js +2 -2
- package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.d.ts +1 -12
- package/dist/openapi/openapi-to-vovk-schema/apply-components-schemas.js +4 -10
- package/dist/openapi/openapi-to-vovk-schema/index.d.ts +1 -1
- package/dist/openapi/openapi-to-vovk-schema/index.js +20 -6
- package/dist/openapi/openapi-to-vovk-schema/inline-refs.d.ts +0 -7
- package/dist/openapi/openapi-to-vovk-schema/inline-refs.js +2 -13
- package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.d.ts +0 -5
- package/dist/openapi/openapi-to-vovk-schema/prune-components-schemas.js +3 -8
- package/dist/openapi/vovk-schema-to-openapi.d.ts +1 -1
- package/dist/openapi/vovk-schema-to-openapi.js +9 -3
- package/dist/req/parse-query.d.ts +0 -23
- package/dist/req/parse-query.js +51 -43
- package/dist/req/validate-content-type.js +1 -1
- package/dist/samples/create-code-samples.d.ts +1 -1
- package/dist/samples/create-code-samples.js +4 -3
- package/dist/samples/schema-to-code.d.ts +1 -1
- package/dist/samples/schema-to-code.js +18 -15
- package/dist/samples/schema-to-object.d.ts +1 -1
- package/dist/samples/schema-to-object.js +18 -15
- package/dist/tools/create-tool-factory.d.ts +2 -2
- package/dist/tools/derive-tools.d.ts +9 -13
- package/dist/tools/derive-tools.js +48 -47
- package/dist/tools/to-model-output-default.d.ts +2 -2
- package/dist/tools/to-model-output-default.js +20 -6
- package/dist/tools/to-model-output-mcp.d.ts +3 -2
- package/dist/tools/to-model-output.d.ts +1 -1
- package/dist/types/client.d.ts +4 -4
- package/dist/types/config.d.ts +5 -6
- package/dist/types/core.d.ts +4 -1
- package/dist/types/enums.d.ts +1 -1
- package/dist/types/enums.js +1 -1
- package/dist/types/inference.d.ts +2 -3
- package/dist/types/request.d.ts +1 -5
- package/dist/types/standard-schema.js +0 -2
- package/dist/types/standard-tool.d.ts +8 -12
- package/dist/types/tools.d.ts +2 -40
- package/dist/types/validation.d.ts +3 -3
- package/dist/utils/camel-case.d.ts +0 -5
- package/dist/utils/camel-case.js +2 -10
- package/dist/utils/deep-extend.d.ts +1 -10
- package/dist/utils/deep-extend.js +16 -4
- package/dist/validation/create-standard-validation.d.ts +3 -126
- package/dist/validation/create-standard-validation.js +2 -3
- package/dist/validation/create-validate-on-client.d.ts +3 -3
- package/dist/validation/json-schema-only-spec.d.ts +14 -0
- package/dist/validation/json-schema-only-spec.js +57 -0
- package/dist/validation/procedure.d.ts +1 -124
- package/dist/validation/validation-schemas-object-to-single-validation-schema.d.ts +0 -9
- package/dist/validation/validation-schemas-object-to-single-validation-schema.js +11 -14
- package/dist/validation/with-validation-library.d.ts +2 -2
- package/dist/validation/with-validation-library.js +11 -5
- package/package.json +3 -10
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the transform emits: `#/components/schemas/X` (response slots, raw operation objects)
|
|
3
|
-
// and `#/$defs/X` (request slots embed components under their original names).
|
|
1
|
+
// collects the trailing name of every $ref in the tree, both `#/components/schemas/X` and `#/$defs/X` styles
|
|
4
2
|
function collectRefNames(node, into) {
|
|
5
3
|
if (!node || typeof node !== 'object')
|
|
6
4
|
return;
|
|
@@ -21,11 +19,8 @@ function collectRefNames(node, into) {
|
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* (BFS with a visited set — component graphs of large specs like Stripe are cyclic).
|
|
27
|
-
* Preserves the original key order for deterministic output.
|
|
28
|
-
*/
|
|
22
|
+
// shrinks components.schemas to the transitive $ref closure of roots, BFS with a visited set
|
|
23
|
+
// (big specs like Stripe are cyclic); preserves key order for deterministic output
|
|
29
24
|
export function pruneComponentsSchemas(roots, componentsSchemas) {
|
|
30
25
|
const required = new Set();
|
|
31
26
|
collectRefNames(roots, required);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
|
-
import type { VovkOutputConfig,
|
|
2
|
+
import type { VovkOutputConfig, VovkPackageJson, VovkReadmeConfig, VovkSamplesConfig, VovkStrictConfig } from '../types/config.js';
|
|
3
3
|
import type { VovkSchema } from '../types/core.js';
|
|
4
4
|
export declare function vovkSchemaToOpenAPI({ config, rootEntry, schema: fullSchema, outputConfigs, forceOutputConfigs, isBundle, segmentName: givenSegmentName, projectPackageJson, }: {
|
|
5
5
|
config: VovkStrictConfig | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { resolveGeneratorConfigValues } from '../core/resolve-generator-config-values.js';
|
|
1
2
|
import { createCodeSamples } from '../samples/create-code-samples.js';
|
|
2
3
|
import { schemaToObject } from '../samples/schema-to-object.js';
|
|
3
|
-
import { resolveGeneratorConfigValues } from '../core/resolve-generator-config-values.js';
|
|
4
4
|
import { HttpStatus } from '../types/enums.js';
|
|
5
5
|
function extractComponents(schema) {
|
|
6
6
|
if (!schema)
|
|
@@ -119,7 +119,11 @@ export function vovkSchemaToOpenAPI({ config, rootEntry = 'api', schema: fullSch
|
|
|
119
119
|
],
|
|
120
120
|
...(queryParameters || pathParameters
|
|
121
121
|
? {
|
|
122
|
-
|
|
122
|
+
// merge derived path/query parameters with user-declared ones, user wins on (name, in) collision
|
|
123
|
+
parameters: [
|
|
124
|
+
...[...(queryParameters || []), ...(pathParameters || [])].filter((p) => !h.operationObject?.parameters?.some((up) => 'name' in up && up.name === p.name && up.in === p.in)),
|
|
125
|
+
...(h.operationObject?.parameters ?? []),
|
|
126
|
+
],
|
|
123
127
|
}
|
|
124
128
|
: {}),
|
|
125
129
|
...(paths[path][httpMethod].parameters
|
|
@@ -209,7 +213,9 @@ export function vovkSchemaToOpenAPI({ config, rootEntry = 'api', schema: fullSch
|
|
|
209
213
|
components: {
|
|
210
214
|
...openAPIObject?.components,
|
|
211
215
|
schemas: {
|
|
212
|
-
|
|
216
|
+
// merge so user-declared schemas extend the derived ones instead of replacing them
|
|
217
|
+
...components,
|
|
218
|
+
...openAPIObject?.components?.schemas,
|
|
213
219
|
HttpStatus: {
|
|
214
220
|
type: 'integer',
|
|
215
221
|
description: 'HTTP status code',
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deserialize a bracket-based query string into an object.
|
|
3
|
-
*
|
|
4
|
-
* Supports:
|
|
5
|
-
* - Key/value pairs with nested brackets (e.g. "a[b][0]=value")
|
|
6
|
-
* - Arrays with empty bracket (e.g. "arr[]=1&arr[]=2")
|
|
7
|
-
* - Mixed arrays of objects, etc.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* parseQuery("x=xx&y[0]=yy&y[1]=uu&z[f]=x&z[u][0]=uu&z[u][1]=xx&z[d][x]=ee")
|
|
11
|
-
* => {
|
|
12
|
-
* x: "xx",
|
|
13
|
-
* y: ["yy", "uu"],
|
|
14
|
-
* z: {
|
|
15
|
-
* f: "x",
|
|
16
|
-
* u: ["uu", "xx"],
|
|
17
|
-
* d: { x: "ee" }
|
|
18
|
-
* }
|
|
19
|
-
* }
|
|
20
|
-
*
|
|
21
|
-
* @param queryString - The raw query string (e.g. location.search.slice(1))
|
|
22
|
-
* @returns - A nested object representing the query params
|
|
23
|
-
*/
|
|
24
1
|
export declare function parseQuery(queryString: string): Record<string, unknown>;
|
package/dist/req/parse-query.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Example: "z[d][0][x]" => ["z", "d", "0", "x"]
|
|
6
|
-
* Example: "arr[]" => ["arr", "" ] // "" indicates "push" onto array
|
|
7
|
-
*/
|
|
1
|
+
// segments that would let a query string reach Object.prototype, such pairs are dropped like qs does
|
|
2
|
+
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
3
|
+
// bracket key to path segments: "z[d][0][x]" => ["z", "d", "0", "x"], "arr[]" => ["arr", ""] ("" means push)
|
|
8
4
|
function parseKey(key) {
|
|
9
5
|
// The first segment is everything up to the first '[' (or the entire key if no '[')
|
|
10
6
|
const segments = [];
|
|
@@ -28,14 +24,34 @@ function parseKey(key) {
|
|
|
28
24
|
}
|
|
29
25
|
return segments;
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
// past this an index becomes an object key, like qs does, so a short query cannot size a huge array
|
|
28
|
+
const ARRAY_LIMIT = 100;
|
|
29
|
+
// digits only, Number() would take "-1" and "1e2" and then drop the value on an array
|
|
30
|
+
function isArrayIndex(segment) {
|
|
31
|
+
return /^\d+$/.test(segment) && Number(segment) <= ARRAY_LIMIT;
|
|
32
|
+
}
|
|
33
|
+
// which container the next segment needs, "" is a push and so wants an array too
|
|
34
|
+
function wantsArray(segment) {
|
|
35
|
+
return typeof segment === 'string' && (segment === '' || isArrayIndex(segment));
|
|
36
|
+
}
|
|
37
|
+
// sets a value at a segment path: numeric => array index, "" => array push, else object property
|
|
37
38
|
function setValue(obj, path, value) {
|
|
38
39
|
let current = obj;
|
|
40
|
+
let parent = null;
|
|
41
|
+
let parentKey = '';
|
|
42
|
+
// an array only keeps numeric indices, any other key is dropped on serialization,
|
|
43
|
+
// so replace the array with an object before setting one
|
|
44
|
+
const demoteArray = () => {
|
|
45
|
+
if (!Array.isArray(current))
|
|
46
|
+
return;
|
|
47
|
+
const replacement = {};
|
|
48
|
+
const source = current;
|
|
49
|
+
for (const key of Object.keys(source))
|
|
50
|
+
replacement[key] = source[key];
|
|
51
|
+
if (parent)
|
|
52
|
+
parent[parentKey] = replacement;
|
|
53
|
+
current = replacement;
|
|
54
|
+
};
|
|
39
55
|
for (let i = 0; i < path.length; i++) {
|
|
40
56
|
const segment = path[i];
|
|
41
57
|
// If we're at the last segment, set the value
|
|
@@ -47,7 +63,7 @@ function setValue(obj, path, value) {
|
|
|
47
63
|
}
|
|
48
64
|
current.push(value);
|
|
49
65
|
}
|
|
50
|
-
else if (
|
|
66
|
+
else if (isArrayIndex(segment)) {
|
|
51
67
|
// Numeric segment => array index
|
|
52
68
|
const idx = Number(segment);
|
|
53
69
|
if (!Array.isArray(current)) {
|
|
@@ -57,6 +73,7 @@ function setValue(obj, path, value) {
|
|
|
57
73
|
}
|
|
58
74
|
else {
|
|
59
75
|
// Object property
|
|
76
|
+
demoteArray();
|
|
60
77
|
current[segment] = value;
|
|
61
78
|
}
|
|
62
79
|
}
|
|
@@ -73,9 +90,9 @@ function setValue(obj, path, value) {
|
|
|
73
90
|
// for the next segment. We'll push something and move current to that.
|
|
74
91
|
if (current.length === 0) {
|
|
75
92
|
// nothing in array yet
|
|
76
|
-
current.push(
|
|
93
|
+
current.push(wantsArray(nextSegment) ? [] : {});
|
|
77
94
|
}
|
|
78
|
-
else if (
|
|
95
|
+
else if (wantsArray(nextSegment)) {
|
|
79
96
|
// next is numeric => we want an array
|
|
80
97
|
if (!Array.isArray(current[current.length - 1])) {
|
|
81
98
|
current[current.length - 1] = [];
|
|
@@ -87,9 +104,11 @@ function setValue(obj, path, value) {
|
|
|
87
104
|
current[current.length - 1] = {};
|
|
88
105
|
}
|
|
89
106
|
}
|
|
107
|
+
parent = current;
|
|
108
|
+
parentKey = current.length - 1;
|
|
90
109
|
current = current[current.length - 1];
|
|
91
110
|
}
|
|
92
|
-
else if (
|
|
111
|
+
else if (isArrayIndex(segment)) {
|
|
93
112
|
// segment is numeric => array index
|
|
94
113
|
const idx = Number(segment);
|
|
95
114
|
if (!Array.isArray(current)) {
|
|
@@ -97,44 +116,28 @@ function setValue(obj, path, value) {
|
|
|
97
116
|
}
|
|
98
117
|
if (current[idx] === undefined) {
|
|
99
118
|
// Create placeholder for next segment
|
|
100
|
-
current[idx] =
|
|
119
|
+
current[idx] = wantsArray(nextSegment) ? [] : {};
|
|
101
120
|
}
|
|
121
|
+
parent = current;
|
|
122
|
+
parentKey = idx;
|
|
102
123
|
current = current[idx];
|
|
103
124
|
}
|
|
104
125
|
else {
|
|
105
126
|
// segment is an object key
|
|
127
|
+
demoteArray();
|
|
106
128
|
if (current[segment] === undefined) {
|
|
107
129
|
// Create placeholder
|
|
108
|
-
current[segment] =
|
|
130
|
+
current[segment] = wantsArray(nextSegment) ? [] : {};
|
|
109
131
|
}
|
|
132
|
+
parent = current;
|
|
133
|
+
parentKey = segment;
|
|
110
134
|
current = current[segment];
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
137
|
}
|
|
114
138
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
*
|
|
118
|
-
* Supports:
|
|
119
|
-
* - Key/value pairs with nested brackets (e.g. "a[b][0]=value")
|
|
120
|
-
* - Arrays with empty bracket (e.g. "arr[]=1&arr[]=2")
|
|
121
|
-
* - Mixed arrays of objects, etc.
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* parseQuery("x=xx&y[0]=yy&y[1]=uu&z[f]=x&z[u][0]=uu&z[u][1]=xx&z[d][x]=ee")
|
|
125
|
-
* => {
|
|
126
|
-
* x: "xx",
|
|
127
|
-
* y: ["yy", "uu"],
|
|
128
|
-
* z: {
|
|
129
|
-
* f: "x",
|
|
130
|
-
* u: ["uu", "xx"],
|
|
131
|
-
* d: { x: "ee" }
|
|
132
|
-
* }
|
|
133
|
-
* }
|
|
134
|
-
*
|
|
135
|
-
* @param queryString - The raw query string (e.g. location.search.slice(1))
|
|
136
|
-
* @returns - A nested object representing the query params
|
|
137
|
-
*/
|
|
139
|
+
// bracket query string to a nested object, supports "a[b][0]=value", "arr[]=1&arr[]=2" etc,
|
|
140
|
+
// e.g. "x=xx&y[0]=yy&z[f]=x&z[d][x]=ee" => { x: "xx", y: ["yy"], z: { f: "x", d: { x: "ee" } } }
|
|
138
141
|
export function parseQuery(queryString) {
|
|
139
142
|
const result = {};
|
|
140
143
|
if (!queryString)
|
|
@@ -144,11 +147,16 @@ export function parseQuery(queryString) {
|
|
|
144
147
|
.replace(/^\?/, '') // Remove leading "?" if present
|
|
145
148
|
.split('&');
|
|
146
149
|
for (const pair of pairs) {
|
|
147
|
-
|
|
150
|
+
// split at the first "=" only, unencoded "=" is legal inside values (base64, JWTs, signatures)
|
|
151
|
+
const eqIndex = pair.indexOf('=');
|
|
152
|
+
const rawKey = eqIndex === -1 ? pair : pair.slice(0, eqIndex);
|
|
153
|
+
const rawVal = eqIndex === -1 ? '' : pair.slice(eqIndex + 1);
|
|
148
154
|
const decodedKey = decodeURIComponent(rawKey);
|
|
149
155
|
const decodedVal = decodeURIComponent(rawVal);
|
|
150
156
|
// Parse bracket notation
|
|
151
157
|
const pathSegments = parseKey(decodedKey);
|
|
158
|
+
if (pathSegments.some((segment) => FORBIDDEN_KEYS.has(segment)))
|
|
159
|
+
continue;
|
|
152
160
|
// Insert into the result object
|
|
153
161
|
setValue(result, pathSegments, decodedVal);
|
|
154
162
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { HttpException } from '../core/http-exception.js';
|
|
2
2
|
import { HttpStatus } from '../types/enums.js';
|
|
3
3
|
export function validateContentType(request, allowed) {
|
|
4
|
-
//
|
|
4
|
+
// wildcard, skip validation
|
|
5
5
|
if (!request?.headers || allowed.includes('*/*'))
|
|
6
6
|
return null;
|
|
7
7
|
const raw = request.headers.get('content-type');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { VovkControllerSchema, VovkHandlerSchema } from '../types/core.js';
|
|
2
1
|
import type { VovkSamplesConfig } from '../types/config.js';
|
|
2
|
+
import type { VovkControllerSchema, VovkHandlerSchema } from '../types/core.js';
|
|
3
3
|
export type CodeSamplePackageJson = {
|
|
4
4
|
name?: string;
|
|
5
5
|
version?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { schemaToCode, getSampleValue } from './schema-to-code.js';
|
|
2
1
|
import { objectToCode } from './object-to-code.js';
|
|
2
|
+
import { getSampleValue, schemaToCode } from './schema-to-code.js';
|
|
3
3
|
const toSnakeCase = (str) => str
|
|
4
4
|
.replace(/-/g, '_') // Replace hyphens with underscores
|
|
5
5
|
.replace(/([a-z0-9])([A-Z])/g, '$1_$2') // Add underscore between lowercase/digit and uppercase
|
|
@@ -270,8 +270,9 @@ export function createCodeSamples({ handlerName, handlerSchema, controllerSchema
|
|
|
270
270
|
const iterationValidation = handlerSchema?.validation?.iteration;
|
|
271
271
|
const hasArg = !!queryValidation || !!bodyValidation || !!paramsValidation || !!config?.apiRoot || !!config?.headers;
|
|
272
272
|
const rpcName = controllerSchema.rpcModuleName;
|
|
273
|
-
const packageName = packageJson?.name || '
|
|
274
|
-
|
|
273
|
+
const packageName = packageJson?.name || '@/client';
|
|
274
|
+
// snake fallback avoids invalid "@/client" in py/rs imports
|
|
275
|
+
const packageNameSnake = toSnakeCase(packageJson?.name || 'client');
|
|
275
276
|
const pyPackageName = packageJson?.py_name ?? packageNameSnake;
|
|
276
277
|
const rsPackageName = packageJson?.rs_name ?? packageNameSnake;
|
|
277
278
|
const commonParams = {
|
|
@@ -7,5 +7,5 @@ interface SamplerOptions {
|
|
|
7
7
|
ignoreBinary?: boolean;
|
|
8
8
|
}
|
|
9
9
|
export declare function schemaToCode(schema: VovkJSONSchemaBase, options: SamplerOptions, rootSchema?: VovkJSONSchemaBase): string;
|
|
10
|
-
export declare function getSampleValue(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase, ignoreBinary?: boolean): unknown;
|
|
10
|
+
export declare function getSampleValue(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase, ignoreBinary?: boolean, seen?: Set<string>): unknown;
|
|
11
11
|
export {};
|
|
@@ -10,7 +10,7 @@ export function schemaToCode(schema, options, rootSchema) {
|
|
|
10
10
|
return formatWithDescriptions(sampleValue, schema, rootSchema, comment, stripQuotes, indent, nestingIndent, ignoreBinary, true // isTopLevel
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
|
-
export function getSampleValue(schema, rootSchema, ignoreBinary) {
|
|
13
|
+
export function getSampleValue(schema, rootSchema, ignoreBinary, seen = new Set()) {
|
|
14
14
|
if (!schema || typeof schema !== 'object')
|
|
15
15
|
return null;
|
|
16
16
|
rootSchema = rootSchema || schema;
|
|
@@ -32,7 +32,7 @@ export function getSampleValue(schema, rootSchema, ignoreBinary) {
|
|
|
32
32
|
}
|
|
33
33
|
// Handle $ref if present
|
|
34
34
|
if (schema.$ref) {
|
|
35
|
-
return handleRef(schema.$ref, rootSchema, ignoreBinary);
|
|
35
|
+
return handleRef(schema.$ref, rootSchema, ignoreBinary, seen);
|
|
36
36
|
}
|
|
37
37
|
// Handle enum if present
|
|
38
38
|
if (schema.enum && schema.enum.length > 0) {
|
|
@@ -40,15 +40,15 @@ export function getSampleValue(schema, rootSchema, ignoreBinary) {
|
|
|
40
40
|
}
|
|
41
41
|
// Handle oneOf, anyOf, allOf
|
|
42
42
|
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
43
|
-
return getSampleValue(schema.oneOf[0], rootSchema, ignoreBinary);
|
|
43
|
+
return getSampleValue(schema.oneOf[0], rootSchema, ignoreBinary, seen);
|
|
44
44
|
}
|
|
45
45
|
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
46
|
-
return getSampleValue(schema.anyOf[0], rootSchema, ignoreBinary);
|
|
46
|
+
return getSampleValue(schema.anyOf[0], rootSchema, ignoreBinary, seen);
|
|
47
47
|
}
|
|
48
48
|
if (schema.allOf && schema.allOf.length > 0) {
|
|
49
49
|
// Merge all schemas in allOf
|
|
50
50
|
const mergedSchema = schema.allOf.reduce((acc, s) => Object.assign(acc, s), {});
|
|
51
|
-
return getSampleValue(mergedSchema, rootSchema, ignoreBinary);
|
|
51
|
+
return getSampleValue(mergedSchema, rootSchema, ignoreBinary, seen);
|
|
52
52
|
}
|
|
53
53
|
// Handle different types
|
|
54
54
|
if (schema.type) {
|
|
@@ -61,9 +61,9 @@ export function getSampleValue(schema, rootSchema, ignoreBinary) {
|
|
|
61
61
|
case 'boolean':
|
|
62
62
|
return handleBoolean();
|
|
63
63
|
case 'object':
|
|
64
|
-
return handleObject(schema, rootSchema, ignoreBinary);
|
|
64
|
+
return handleObject(schema, rootSchema, ignoreBinary, seen);
|
|
65
65
|
case 'array':
|
|
66
|
-
return handleArray(schema, rootSchema, ignoreBinary);
|
|
66
|
+
return handleArray(schema, rootSchema, ignoreBinary, seen);
|
|
67
67
|
case 'null':
|
|
68
68
|
return null;
|
|
69
69
|
default:
|
|
@@ -72,7 +72,7 @@ export function getSampleValue(schema, rootSchema, ignoreBinary) {
|
|
|
72
72
|
}
|
|
73
73
|
// If type is not specified but properties are, treat it as an object
|
|
74
74
|
if (schema.properties) {
|
|
75
|
-
return handleObject(schema, rootSchema, ignoreBinary);
|
|
75
|
+
return handleObject(schema, rootSchema, ignoreBinary, seen);
|
|
76
76
|
}
|
|
77
77
|
// Default fallback
|
|
78
78
|
return null;
|
|
@@ -154,9 +154,12 @@ function resolveRef(ref, rootSchema) {
|
|
|
154
154
|
}
|
|
155
155
|
return current;
|
|
156
156
|
}
|
|
157
|
-
function handleRef(ref, rootSchema, ignoreBinary) {
|
|
157
|
+
function handleRef(ref, rootSchema, ignoreBinary, seen) {
|
|
158
|
+
// a ref already being expanded means the schema is circular, stop instead of recursing forever
|
|
159
|
+
if (seen.has(ref))
|
|
160
|
+
return null;
|
|
158
161
|
const resolved = resolveRef(ref, rootSchema);
|
|
159
|
-
return getSampleValue(resolved, rootSchema, ignoreBinary);
|
|
162
|
+
return getSampleValue(resolved, rootSchema, ignoreBinary, new Set(seen).add(ref));
|
|
160
163
|
}
|
|
161
164
|
function handleString(schema) {
|
|
162
165
|
if (schema.format) {
|
|
@@ -218,13 +221,13 @@ function handleNumber(schema) {
|
|
|
218
221
|
function handleBoolean() {
|
|
219
222
|
return true;
|
|
220
223
|
}
|
|
221
|
-
function handleObject(schema, rootSchema, ignoreBinary) {
|
|
224
|
+
function handleObject(schema, rootSchema, ignoreBinary, seen) {
|
|
222
225
|
const result = {};
|
|
223
226
|
if (schema.properties) {
|
|
224
227
|
const required = schema.required || [];
|
|
225
228
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
226
229
|
if (required.includes(key) || required.length === 0) {
|
|
227
|
-
const value = getSampleValue(propSchema, rootSchema, ignoreBinary);
|
|
230
|
+
const value = getSampleValue(propSchema, rootSchema, ignoreBinary, seen);
|
|
228
231
|
// Only add the property if it's not undefined (which happens when ignoreBinary is true and it's a binary field)
|
|
229
232
|
if (value !== undefined) {
|
|
230
233
|
result[key] = value;
|
|
@@ -233,14 +236,14 @@ function handleObject(schema, rootSchema, ignoreBinary) {
|
|
|
233
236
|
}
|
|
234
237
|
}
|
|
235
238
|
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
236
|
-
const value = getSampleValue(schema.additionalProperties, rootSchema, ignoreBinary);
|
|
239
|
+
const value = getSampleValue(schema.additionalProperties, rootSchema, ignoreBinary, seen);
|
|
237
240
|
if (value !== undefined) {
|
|
238
241
|
result.additionalProp = value;
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
244
|
return result;
|
|
242
245
|
}
|
|
243
|
-
function handleArray(schema, rootSchema, ignoreBinary) {
|
|
246
|
+
function handleArray(schema, rootSchema, ignoreBinary, seen) {
|
|
244
247
|
if (schema.items) {
|
|
245
248
|
// If items is a boolean, return empty array (true means any items allowed, false means no items)
|
|
246
249
|
if (typeof schema.items === 'boolean') {
|
|
@@ -253,7 +256,7 @@ function handleArray(schema, rootSchema, ignoreBinary) {
|
|
|
253
256
|
}
|
|
254
257
|
const minItems = schema.minItems || 1;
|
|
255
258
|
const numItems = Math.min(minItems, 3);
|
|
256
|
-
const items = Array.from({ length: numItems }, () => getSampleValue(itemSchema, rootSchema, ignoreBinary)).filter((item) => item !== undefined); // Filter out undefined values from ignored binary items
|
|
259
|
+
const items = Array.from({ length: numItems }, () => getSampleValue(itemSchema, rootSchema, ignoreBinary, seen)).filter((item) => item !== undefined); // Filter out undefined values from ignored binary items
|
|
257
260
|
// If all items were filtered out (e.g., all were binary), return undefined instead of empty array
|
|
258
261
|
if (items.length === 0 && numItems > 0) {
|
|
259
262
|
return undefined;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { VovkJSONSchemaBase } from '../types/json-schema.js';
|
|
2
|
-
export declare function schemaToObject(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase): unknown;
|
|
2
|
+
export declare function schemaToObject(schema: VovkJSONSchemaBase, rootSchema?: VovkJSONSchemaBase, seen?: Set<string>): unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function schemaToObject(schema, rootSchema) {
|
|
1
|
+
export function schemaToObject(schema, rootSchema, seen = new Set()) {
|
|
2
2
|
if (!schema || typeof schema !== 'object')
|
|
3
3
|
return null;
|
|
4
4
|
// Use the input schema as the root if not provided
|
|
@@ -17,7 +17,7 @@ export function schemaToObject(schema, rootSchema) {
|
|
|
17
17
|
}
|
|
18
18
|
// Handle $ref if present
|
|
19
19
|
if (schema.$ref) {
|
|
20
|
-
return handleRef(schema.$ref, rootSchema);
|
|
20
|
+
return handleRef(schema.$ref, rootSchema, seen);
|
|
21
21
|
}
|
|
22
22
|
// Handle enum if present
|
|
23
23
|
if (schema.enum && schema.enum.length > 0) {
|
|
@@ -25,15 +25,15 @@ export function schemaToObject(schema, rootSchema) {
|
|
|
25
25
|
}
|
|
26
26
|
// Handle oneOf, anyOf, allOf
|
|
27
27
|
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
28
|
-
return schemaToObject(schema.oneOf[0], rootSchema);
|
|
28
|
+
return schemaToObject(schema.oneOf[0], rootSchema, seen);
|
|
29
29
|
}
|
|
30
30
|
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
31
|
-
return schemaToObject(schema.anyOf[0], rootSchema);
|
|
31
|
+
return schemaToObject(schema.anyOf[0], rootSchema, seen);
|
|
32
32
|
}
|
|
33
33
|
if (schema.allOf && schema.allOf.length > 0) {
|
|
34
34
|
// Merge all schemas in allOf
|
|
35
35
|
const mergedSchema = schema.allOf.reduce((acc, s) => Object.assign(acc, s), {});
|
|
36
|
-
return schemaToObject(mergedSchema, rootSchema);
|
|
36
|
+
return schemaToObject(mergedSchema, rootSchema, seen);
|
|
37
37
|
}
|
|
38
38
|
// Handle different types
|
|
39
39
|
if (schema.type) {
|
|
@@ -46,9 +46,9 @@ export function schemaToObject(schema, rootSchema) {
|
|
|
46
46
|
case 'boolean':
|
|
47
47
|
return handleBoolean();
|
|
48
48
|
case 'object':
|
|
49
|
-
return handleObject(schema, rootSchema);
|
|
49
|
+
return handleObject(schema, rootSchema, seen);
|
|
50
50
|
case 'array':
|
|
51
|
-
return handleArray(schema, rootSchema);
|
|
51
|
+
return handleArray(schema, rootSchema, seen);
|
|
52
52
|
case 'null':
|
|
53
53
|
return null;
|
|
54
54
|
default:
|
|
@@ -57,12 +57,15 @@ export function schemaToObject(schema, rootSchema) {
|
|
|
57
57
|
}
|
|
58
58
|
// If type is not specified but properties are, treat it as an object
|
|
59
59
|
if (schema.properties) {
|
|
60
|
-
return handleObject(schema, rootSchema);
|
|
60
|
+
return handleObject(schema, rootSchema, seen);
|
|
61
61
|
}
|
|
62
62
|
// Default fallback
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
|
-
function handleRef(ref, rootSchema) {
|
|
65
|
+
function handleRef(ref, rootSchema, seen) {
|
|
66
|
+
// a ref already being expanded means the schema is circular, stop instead of recursing forever
|
|
67
|
+
if (seen.has(ref))
|
|
68
|
+
return null;
|
|
66
69
|
// Parse the reference path
|
|
67
70
|
const path = ref.split('/').slice(1); // Remove the initial '#'
|
|
68
71
|
// Navigate through the schema to find the referenced definition
|
|
@@ -74,7 +77,7 @@ function handleRef(ref, rootSchema) {
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
// Process the referenced schema
|
|
77
|
-
return schemaToObject(current, rootSchema);
|
|
80
|
+
return schemaToObject(current, rootSchema, new Set(seen).add(ref));
|
|
78
81
|
}
|
|
79
82
|
function handleString(schema) {
|
|
80
83
|
if (schema.format) {
|
|
@@ -135,30 +138,30 @@ function handleNumber(schema) {
|
|
|
135
138
|
function handleBoolean() {
|
|
136
139
|
return true;
|
|
137
140
|
}
|
|
138
|
-
function handleObject(schema, rootSchema) {
|
|
141
|
+
function handleObject(schema, rootSchema, seen) {
|
|
139
142
|
const result = {};
|
|
140
143
|
if (schema.properties) {
|
|
141
144
|
const required = schema.required || [];
|
|
142
145
|
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
143
146
|
// Only include required properties or as a basic example
|
|
144
147
|
if (required.includes(key) || required.length === 0) {
|
|
145
|
-
result[key] = schemaToObject(propSchema, rootSchema);
|
|
148
|
+
result[key] = schemaToObject(propSchema, rootSchema, seen);
|
|
146
149
|
}
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
// Handle additionalProperties
|
|
150
153
|
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
151
|
-
result.additionalProp = schemaToObject(schema.additionalProperties, rootSchema);
|
|
154
|
+
result.additionalProp = schemaToObject(schema.additionalProperties, rootSchema, seen);
|
|
152
155
|
}
|
|
153
156
|
return result;
|
|
154
157
|
}
|
|
155
|
-
function handleArray(schema, rootSchema) {
|
|
158
|
+
function handleArray(schema, rootSchema, seen) {
|
|
156
159
|
if (schema.items && typeof schema.items === 'object') {
|
|
157
160
|
const itemSchema = schema.items;
|
|
158
161
|
const minItems = schema.minItems || 1;
|
|
159
162
|
// Create minimum number of items (capped at a reasonable max for examples)
|
|
160
163
|
const numItems = Math.min(minItems, 3);
|
|
161
|
-
return Array.from({ length: numItems }, () => schemaToObject(itemSchema, rootSchema));
|
|
164
|
+
return Array.from({ length: numItems }, () => schemaToObject(itemSchema, rootSchema, seen));
|
|
162
165
|
}
|
|
163
166
|
return [];
|
|
164
167
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { VovkValidationType } from '../types/core.js';
|
|
2
2
|
import type { VovkTool } from '../types/tools.js';
|
|
3
|
-
import type { DefaultModelOutput, ToModelOutputDefaultFn } from './to-model-output-default.js';
|
|
4
|
-
import type { CombinedSpec } from '../types/validation.js';
|
|
5
3
|
import type { KnownAny } from '../types/utils.js';
|
|
4
|
+
import type { CombinedSpec } from '../types/validation.js';
|
|
5
|
+
import type { DefaultModelOutput, ToModelOutputDefaultFn } from './to-model-output-default.js';
|
|
6
6
|
type InferFormattedOutput<TFn, TOutput> = TFn extends ToModelOutputDefaultFn ? DefaultModelOutput<TOutput> : TFn extends (...args: KnownAny[]) => infer R ? R extends Promise<infer U> ? U : R : unknown;
|
|
7
7
|
type AnyToModelOutputFn = (...args: KnownAny[]) => KnownAny;
|
|
8
8
|
export declare function createToolFactory({ toJSONSchema, }: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { DefaultModelOutput } from './to-model-output-default.js';
|
|
2
1
|
import type { VovkRequest } from '../types/request.js';
|
|
3
|
-
import type {
|
|
2
|
+
import type { StandardToolV0 } from '../types/standard-tool.js';
|
|
3
|
+
import type { ToModelOutputFn } from '../types/tools.js';
|
|
4
|
+
import type { DefaultModelOutput } from './to-model-output-default.js';
|
|
4
5
|
type DerivedToolInput = {
|
|
5
6
|
body?: unknown;
|
|
6
7
|
query?: unknown;
|
|
@@ -9,12 +10,8 @@ type DerivedToolInput = {
|
|
|
9
10
|
type DeriveToolsBaseOptions<TOutput = unknown, TFormattedOutput = unknown> = {
|
|
10
11
|
modules: Record<string, object>;
|
|
11
12
|
meta?: Record<string, unknown>;
|
|
12
|
-
onExecute?: (result: unknown, tool:
|
|
13
|
-
onError?: (error: Error, tool:
|
|
14
|
-
};
|
|
15
|
-
type DeriveToolsResult<TOutput, TFormattedOutput> = {
|
|
16
|
-
tools: VovkTool<DerivedToolInput, TOutput, TFormattedOutput>[];
|
|
17
|
-
toolsByName: Record<string, VovkTool<DerivedToolInput, TOutput, TFormattedOutput>>;
|
|
13
|
+
onExecute?: (result: unknown, tool: StandardToolV0<DerivedToolInput, TOutput, TFormattedOutput>, req: Pick<VovkRequest, 'vovk'> | null) => void;
|
|
14
|
+
onError?: (error: Error, tool: StandardToolV0<DerivedToolInput, TOutput, TFormattedOutput>, req: Pick<VovkRequest, 'vovk'> | null) => void;
|
|
18
15
|
};
|
|
19
16
|
/**
|
|
20
17
|
* Derives AI tools from controllers and RPC modules.
|
|
@@ -22,10 +19,9 @@ type DeriveToolsResult<TOutput, TFormattedOutput> = {
|
|
|
22
19
|
* @example
|
|
23
20
|
* ```ts
|
|
24
21
|
* import { deriveTools, ToModelOutput } from 'vovk';
|
|
25
|
-
* import { UserRPC } from '
|
|
22
|
+
* import { UserRPC } from '@/client';
|
|
26
23
|
*
|
|
27
|
-
*
|
|
28
|
-
* const { tools, toolsByName } = deriveTools({
|
|
24
|
+
* const tools = deriveTools({
|
|
29
25
|
* modules: { UserRPC },
|
|
30
26
|
* toModelOutput: ToModelOutput.MCP,
|
|
31
27
|
* onExecute: (result, tool) => {
|
|
@@ -39,8 +35,8 @@ type DeriveToolsResult<TOutput, TFormattedOutput> = {
|
|
|
39
35
|
*/
|
|
40
36
|
export declare function deriveTools<TOutput = unknown, TFormattedOutput = DefaultModelOutput<TOutput>>(options: DeriveToolsBaseOptions & {
|
|
41
37
|
toModelOutput?: never;
|
|
42
|
-
}):
|
|
38
|
+
}): StandardToolV0<DerivedToolInput, TOutput, TFormattedOutput>[];
|
|
43
39
|
export declare function deriveTools<TOutput = unknown, TFormattedOutput = unknown>(options: DeriveToolsBaseOptions & {
|
|
44
40
|
toModelOutput: ToModelOutputFn<unknown, TOutput, TFormattedOutput>;
|
|
45
|
-
}):
|
|
41
|
+
}): StandardToolV0<DerivedToolInput, TOutput, TFormattedOutput>[];
|
|
46
42
|
export {};
|