vovk 3.0.0-draft.21 → 3.0.0-draft.211
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/LICENSE +1 -1
- package/README.md +8 -96
- package/bin/index.mjs +8 -0
- package/{HttpException.d.ts → cjs/HttpException.d.ts} +2 -2
- package/{HttpException.js → cjs/HttpException.js} +3 -3
- package/cjs/JSONLinesResponse.d.ts +14 -0
- package/{StreamJSONResponse.js → cjs/JSONLinesResponse.js} +15 -10
- package/{Segment.d.ts → cjs/VovkApp.d.ts} +11 -10
- package/cjs/VovkApp.js +189 -0
- package/cjs/client/createRPC.d.ts +3 -0
- package/cjs/client/createRPC.js +87 -0
- package/{client → cjs/client}/defaultHandler.d.ts +1 -1
- package/cjs/client/defaultHandler.js +22 -0
- package/cjs/client/defaultStreamHandler.d.ts +4 -0
- package/{client → cjs/client}/defaultStreamHandler.js +11 -12
- package/cjs/client/fetcher.d.ts +13 -0
- package/cjs/client/fetcher.js +90 -0
- package/cjs/client/index.d.ts +3 -0
- package/cjs/client/index.js +8 -0
- package/cjs/client/types.d.ts +111 -0
- package/{createSegment.d.ts → cjs/createVovkApp.d.ts} +11 -10
- package/cjs/createVovkApp.js +132 -0
- package/cjs/index.d.ts +65 -0
- package/cjs/index.js +37 -0
- package/cjs/openapi/error.d.ts +2 -0
- package/cjs/openapi/error.js +100 -0
- package/cjs/openapi/generateFnName.d.ts +23 -0
- package/cjs/openapi/generateFnName.js +81 -0
- package/cjs/openapi/index.d.ts +12 -0
- package/cjs/openapi/index.js +21 -0
- package/cjs/openapi/openAPIToVovkSchema.d.ts +2 -0
- package/cjs/openapi/openAPIToVovkSchema.js +197 -0
- package/cjs/openapi/vovkSchemaToOpenAPI.d.ts +9 -0
- package/cjs/openapi/vovkSchemaToOpenAPI.js +237 -0
- package/cjs/types.d.ts +364 -0
- package/cjs/types.js +74 -0
- package/cjs/utils/camelCase.d.ts +6 -0
- package/cjs/utils/camelCase.js +37 -0
- package/cjs/utils/createCodeExamples.d.ts +19 -0
- package/cjs/utils/createCodeExamples.js +114 -0
- package/cjs/utils/createDecorator.d.ts +6 -0
- package/{createDecorator.js → cjs/utils/createDecorator.js} +24 -16
- package/cjs/utils/createLLMFunctions.d.ts +20 -0
- package/cjs/utils/createLLMFunctions.js +100 -0
- package/cjs/utils/generateStaticAPI.d.ts +4 -0
- package/cjs/utils/generateStaticAPI.js +30 -0
- package/cjs/utils/getSchema.d.ts +21 -0
- package/cjs/utils/getSchema.js +43 -0
- package/cjs/utils/multitenant.d.ts +24 -0
- package/cjs/utils/multitenant.js +170 -0
- package/cjs/utils/parseQuery.d.ts +25 -0
- package/cjs/utils/parseQuery.js +156 -0
- package/cjs/utils/reqForm.d.ts +2 -0
- package/cjs/utils/reqForm.js +33 -0
- package/{utils → cjs/utils}/reqMeta.d.ts +1 -2
- package/cjs/utils/reqQuery.d.ts +2 -0
- package/cjs/utils/reqQuery.js +10 -0
- package/cjs/utils/serializeQuery.d.ts +13 -0
- package/cjs/utils/serializeQuery.js +65 -0
- package/cjs/utils/setHandlerSchema.d.ts +4 -0
- package/cjs/utils/setHandlerSchema.js +15 -0
- package/cjs/utils/withStandard.d.ts +51 -0
- package/cjs/utils/withStandard.js +30 -0
- package/cjs/utils/withValidationLibrary.d.ts +49 -0
- package/cjs/utils/withValidationLibrary.js +123 -0
- package/mjs/HttpException.d.ts +7 -0
- package/mjs/HttpException.js +15 -0
- package/mjs/JSONLinesResponse.d.ts +14 -0
- package/mjs/JSONLinesResponse.js +59 -0
- package/mjs/VovkApp.d.ts +29 -0
- package/mjs/VovkApp.js +189 -0
- package/mjs/client/createRPC.d.ts +3 -0
- package/mjs/client/createRPC.js +87 -0
- package/mjs/client/defaultHandler.d.ts +2 -0
- package/mjs/client/defaultHandler.js +22 -0
- package/mjs/client/defaultStreamHandler.d.ts +4 -0
- package/mjs/client/defaultStreamHandler.js +81 -0
- package/mjs/client/fetcher.d.ts +13 -0
- package/mjs/client/fetcher.js +90 -0
- package/mjs/client/index.d.ts +3 -0
- package/mjs/client/index.js +8 -0
- package/mjs/client/types.d.ts +111 -0
- package/mjs/createVovkApp.d.ts +63 -0
- package/mjs/createVovkApp.js +132 -0
- package/mjs/index.d.ts +65 -0
- package/mjs/index.js +37 -0
- package/mjs/openapi/error.d.ts +2 -0
- package/mjs/openapi/error.js +100 -0
- package/mjs/openapi/generateFnName.d.ts +23 -0
- package/mjs/openapi/generateFnName.js +81 -0
- package/mjs/openapi/index.d.ts +12 -0
- package/mjs/openapi/index.js +21 -0
- package/mjs/openapi/openAPIToVovkSchema.d.ts +2 -0
- package/mjs/openapi/openAPIToVovkSchema.js +197 -0
- package/mjs/openapi/vovkSchemaToOpenAPI.d.ts +9 -0
- package/mjs/openapi/vovkSchemaToOpenAPI.js +237 -0
- package/mjs/types.d.ts +364 -0
- package/mjs/types.js +74 -0
- package/mjs/utils/camelCase.d.ts +6 -0
- package/mjs/utils/camelCase.js +37 -0
- package/mjs/utils/createCodeExamples.d.ts +19 -0
- package/mjs/utils/createCodeExamples.js +114 -0
- package/mjs/utils/createDecorator.d.ts +6 -0
- package/mjs/utils/createDecorator.js +46 -0
- package/mjs/utils/createLLMFunctions.d.ts +20 -0
- package/mjs/utils/createLLMFunctions.js +100 -0
- package/mjs/utils/generateStaticAPI.d.ts +4 -0
- package/mjs/utils/generateStaticAPI.js +30 -0
- package/mjs/utils/getSchema.d.ts +21 -0
- package/mjs/utils/getSchema.js +43 -0
- package/mjs/utils/multitenant.d.ts +24 -0
- package/mjs/utils/multitenant.js +170 -0
- package/mjs/utils/parseQuery.d.ts +25 -0
- package/mjs/utils/parseQuery.js +156 -0
- package/mjs/utils/reqForm.d.ts +2 -0
- package/mjs/utils/reqForm.js +33 -0
- package/mjs/utils/reqMeta.d.ts +2 -0
- package/mjs/utils/reqMeta.js +13 -0
- package/mjs/utils/reqQuery.d.ts +2 -0
- package/mjs/utils/reqQuery.js +10 -0
- package/mjs/utils/serializeQuery.d.ts +13 -0
- package/mjs/utils/serializeQuery.js +65 -0
- package/mjs/utils/setHandlerSchema.d.ts +4 -0
- package/mjs/utils/setHandlerSchema.js +15 -0
- package/mjs/utils/shim.d.ts +1 -0
- package/mjs/utils/shim.js +18 -0
- package/mjs/utils/withStandard.d.ts +51 -0
- package/mjs/utils/withStandard.js +30 -0
- package/mjs/utils/withValidationLibrary.d.ts +49 -0
- package/mjs/utils/withValidationLibrary.js +123 -0
- package/package.json +30 -5
- package/.npmignore +0 -2
- package/Segment.js +0 -182
- package/StreamJSONResponse.d.ts +0 -17
- package/client/clientizeController.d.ts +0 -4
- package/client/clientizeController.js +0 -92
- package/client/defaultFetcher.d.ts +0 -4
- package/client/defaultFetcher.js +0 -49
- package/client/defaultHandler.js +0 -21
- package/client/defaultStreamHandler.d.ts +0 -4
- package/client/index.d.ts +0 -4
- package/client/index.js +0 -5
- package/client/types.d.ts +0 -100
- package/createDecorator.d.ts +0 -4
- package/createSegment.js +0 -118
- package/generateStaticAPI.d.ts +0 -4
- package/generateStaticAPI.js +0 -18
- package/index.d.ts +0 -60
- package/index.js +0 -20
- package/types.d.ts +0 -155
- package/types.js +0 -65
- package/utils/getSchema.d.ts +0 -8
- package/utils/getSchema.js +0 -38
- package/utils/reqQuery.d.ts +0 -3
- package/utils/reqQuery.js +0 -25
- package/utils/setClientValidatorsForHandler.d.ts +0 -5
- package/utils/setClientValidatorsForHandler.js +0 -28
- package/worker/index.d.ts +0 -3
- package/worker/index.js +0 -7
- package/worker/promisifyWorker.d.ts +0 -2
- package/worker/promisifyWorker.js +0 -141
- package/worker/types.d.ts +0 -31
- package/worker/worker.d.ts +0 -1
- package/worker/worker.js +0 -43
- /package/{client → cjs/client}/types.js +0 -0
- /package/{utils → cjs/utils}/reqMeta.js +0 -0
- /package/{utils → cjs/utils}/shim.d.ts +0 -0
- /package/{utils → cjs/utils}/shim.js +0 -0
- /package/{worker → mjs/client}/types.js +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openAPIToVovkSchema = openAPIToVovkSchema;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const generateFnName_1 = require("./generateFnName");
|
|
6
|
+
const camelCase_1 = require("../utils/camelCase");
|
|
7
|
+
// fast clone JSON object while ignoring Date, RegExp, and Function types
|
|
8
|
+
function cloneJSON(obj) {
|
|
9
|
+
if (obj === null || typeof obj !== 'object')
|
|
10
|
+
return obj;
|
|
11
|
+
if (Array.isArray(obj))
|
|
12
|
+
return obj.map(cloneJSON);
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
15
|
+
if (value instanceof Date || value instanceof RegExp || typeof value === 'function')
|
|
16
|
+
continue;
|
|
17
|
+
result[key] = cloneJSON(value);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function applyComponents(schema, components) {
|
|
22
|
+
if (!components || !Object.keys(components).length)
|
|
23
|
+
return schema;
|
|
24
|
+
// Create a deep copy of the schema
|
|
25
|
+
const result = cloneJSON(schema);
|
|
26
|
+
// Initialize $defs if it doesn't exist
|
|
27
|
+
result.$defs = result.$defs || {};
|
|
28
|
+
// Set to track components we've added to $defs
|
|
29
|
+
const addedComponents = new Set();
|
|
30
|
+
// Process a schema object and replace $refs
|
|
31
|
+
function processSchema(obj) {
|
|
32
|
+
if (!obj || typeof obj !== 'object')
|
|
33
|
+
return obj;
|
|
34
|
+
// Create a new object/array to avoid modifying the input
|
|
35
|
+
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
36
|
+
// Check for $ref
|
|
37
|
+
if (newObj.$ref && typeof newObj.$ref === 'string' && newObj.$ref.startsWith('#/components/schemas/')) {
|
|
38
|
+
const componentName = newObj.$ref.replace('#/components/schemas/', '');
|
|
39
|
+
newObj.$ref = `#/$defs/${componentName}`;
|
|
40
|
+
// Add the component to $defs if not already added
|
|
41
|
+
if (!addedComponents.has(componentName) && components[componentName]) {
|
|
42
|
+
addedComponents.add(componentName);
|
|
43
|
+
// TODO: IMPORTANT! Deep copy to avoid mutation issues
|
|
44
|
+
result.$defs[componentName] = processSchema(cloneJSON(components[componentName]));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Process properties/items recursively
|
|
48
|
+
if (Array.isArray(newObj)) {
|
|
49
|
+
for (let i = 0; i < newObj.length; i++) {
|
|
50
|
+
newObj[i] = processSchema(newObj[i]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
for (const key in newObj) {
|
|
55
|
+
if (Object.prototype.hasOwnProperty.call(newObj, key)) {
|
|
56
|
+
newObj[key] = processSchema(newObj[key]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return newObj;
|
|
61
|
+
}
|
|
62
|
+
// Process the main schema
|
|
63
|
+
return processSchema(result);
|
|
64
|
+
}
|
|
65
|
+
const getNamesNestJS = (operationObject) => {
|
|
66
|
+
const operationId = operationObject.operationId;
|
|
67
|
+
if (!operationId) {
|
|
68
|
+
throw new Error('Operation ID is required for NestJS module name generation');
|
|
69
|
+
}
|
|
70
|
+
const controllerHandlerMatch = operationId?.match(/^([A-Z][a-zA-Z0-9]*)_([a-zA-Z0-9_]+)/);
|
|
71
|
+
if (!controllerHandlerMatch) {
|
|
72
|
+
throw new Error(`Invalid operationId format for NestJS: ${operationId}`);
|
|
73
|
+
}
|
|
74
|
+
return controllerHandlerMatch.slice(1, 3);
|
|
75
|
+
};
|
|
76
|
+
const normalizeGetModuleName = (getModuleName) => {
|
|
77
|
+
if (getModuleName === 'nestjs-operation-id') {
|
|
78
|
+
getModuleName = (operationObject) => getNamesNestJS(operationObject)[0];
|
|
79
|
+
}
|
|
80
|
+
else if (typeof getModuleName === 'string') {
|
|
81
|
+
const moduleName = getModuleName;
|
|
82
|
+
getModuleName = () => moduleName;
|
|
83
|
+
}
|
|
84
|
+
else if (typeof getModuleName !== 'function') {
|
|
85
|
+
throw new Error('getModuleName must be a function or one of the predefined strings');
|
|
86
|
+
}
|
|
87
|
+
return getModuleName;
|
|
88
|
+
};
|
|
89
|
+
const normalizeGetMethodName = (getMethodName) => {
|
|
90
|
+
if (getMethodName === 'nestjs-operation-id') {
|
|
91
|
+
getMethodName = (operationObject) => getNamesNestJS(operationObject)[1];
|
|
92
|
+
}
|
|
93
|
+
else if (getMethodName === 'camel-case-operation-id') {
|
|
94
|
+
getMethodName = ({ operationObject }) => {
|
|
95
|
+
const operationId = operationObject.operationId;
|
|
96
|
+
if (!operationId) {
|
|
97
|
+
throw new Error('Operation ID is required for camel-case method name generation');
|
|
98
|
+
}
|
|
99
|
+
return (0, camelCase_1.camelCase)(operationId);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
else if (getMethodName === 'auto') {
|
|
103
|
+
getMethodName = ({ operationObject, method, path }) => {
|
|
104
|
+
const operationId = operationObject.operationId;
|
|
105
|
+
const isSnakeCase = operationId && /^[a-z][a-z0-9_]+$/.test(operationId);
|
|
106
|
+
return isSnakeCase ? (0, camelCase_1.camelCase)(operationId) : (0, generateFnName_1.generateFnName)(method, path);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
else if (typeof getMethodName !== 'function') {
|
|
110
|
+
throw new Error('getMethodName must be a function or one of the predefined strings');
|
|
111
|
+
}
|
|
112
|
+
return getMethodName;
|
|
113
|
+
};
|
|
114
|
+
function openAPIToVovkSchema({ apiRoot, source: { object: openAPIObject }, getModuleName = 'api', getMethodName = 'auto', }) {
|
|
115
|
+
const forceApiRoot = apiRoot ?? openAPIObject.servers?.[0]?.url;
|
|
116
|
+
if (!forceApiRoot) {
|
|
117
|
+
throw new Error('API root URL is required in OpenAPI configuration');
|
|
118
|
+
}
|
|
119
|
+
const schema = {
|
|
120
|
+
$schema: types_1.VovkSchemaIdEnum.SCHEMA,
|
|
121
|
+
segments: {
|
|
122
|
+
'': {
|
|
123
|
+
$schema: types_1.VovkSchemaIdEnum.SEGMENT,
|
|
124
|
+
emitSchema: true,
|
|
125
|
+
segmentName: '',
|
|
126
|
+
forceApiRoot,
|
|
127
|
+
controllers: {},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
meta: {
|
|
131
|
+
$schema: types_1.VovkSchemaIdEnum.META,
|
|
132
|
+
config: {
|
|
133
|
+
$schema: types_1.VovkSchemaIdEnum.CONFIG,
|
|
134
|
+
},
|
|
135
|
+
openapi: openAPIObject,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
const segment = schema.segments[''];
|
|
139
|
+
getModuleName = normalizeGetModuleName(getModuleName);
|
|
140
|
+
getMethodName = normalizeGetMethodName(getMethodName);
|
|
141
|
+
return Object.entries(openAPIObject.paths ?? {}).reduce((acc, [path, operations]) => {
|
|
142
|
+
Object.entries(operations ?? {}).forEach(([method, operation]) => {
|
|
143
|
+
const rpcModuleName = getModuleName({
|
|
144
|
+
method: method.toUpperCase(),
|
|
145
|
+
path,
|
|
146
|
+
openAPIObject,
|
|
147
|
+
operationObject: operation,
|
|
148
|
+
});
|
|
149
|
+
const handlerName = getMethodName({
|
|
150
|
+
method: method.toUpperCase(),
|
|
151
|
+
path,
|
|
152
|
+
openAPIObject,
|
|
153
|
+
operationObject: operation,
|
|
154
|
+
});
|
|
155
|
+
segment.controllers[rpcModuleName] ??= {
|
|
156
|
+
rpcModuleName,
|
|
157
|
+
handlers: {},
|
|
158
|
+
};
|
|
159
|
+
// TODO: how to utilize ReferenceObject?
|
|
160
|
+
const queryProperties = operation.parameters?.filter((p) => p.in === 'query') ?? null;
|
|
161
|
+
const pathProperties = operation.parameters?.filter((p) => p.in === 'path') ?? null;
|
|
162
|
+
const query = queryProperties?.length
|
|
163
|
+
? {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: Object.fromEntries(queryProperties.map((p) => [p.name, p.schema])),
|
|
166
|
+
required: queryProperties.filter((p) => p.required).map((p) => p.name),
|
|
167
|
+
}
|
|
168
|
+
: null;
|
|
169
|
+
const params = pathProperties?.length
|
|
170
|
+
? {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: Object.fromEntries(pathProperties.map((p) => [p.name, p.schema])),
|
|
173
|
+
required: pathProperties.filter((p) => p.required).map((p) => p.name),
|
|
174
|
+
}
|
|
175
|
+
: null;
|
|
176
|
+
// TODO: how to utilize ReferenceObject?
|
|
177
|
+
const body = operation.requestBody?.content['application/json']?.schema ?? null;
|
|
178
|
+
const output = operation.responses?.['200']?.content?.['application/json']?.schema ??
|
|
179
|
+
operation.responses?.['201']?.content?.['application/json']?.schema ??
|
|
180
|
+
null;
|
|
181
|
+
// TODO: "iteration" validation
|
|
182
|
+
// TODO: FormData
|
|
183
|
+
segment.controllers[rpcModuleName].handlers[handlerName] = {
|
|
184
|
+
httpMethod: method.toUpperCase(),
|
|
185
|
+
path,
|
|
186
|
+
openapi: operation,
|
|
187
|
+
validation: {
|
|
188
|
+
...(query && { query: applyComponents(query, openAPIObject.components?.schemas) }),
|
|
189
|
+
...(params && { params: applyComponents(params, openAPIObject.components?.schemas) }),
|
|
190
|
+
...(body && { body: applyComponents(body, openAPIObject.components?.schemas) }),
|
|
191
|
+
...(output && { output: applyComponents(output, openAPIObject.components?.schemas) }),
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
return acc;
|
|
196
|
+
}, schema);
|
|
197
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
|
+
import { type CodeSamplePackageJson } from '../utils/createCodeExamples';
|
|
3
|
+
import { type VovkSchema } from '../types';
|
|
4
|
+
export declare function vovkSchemaToOpenAPI({ rootEntry, schema: fullSchema, openAPIObject, package: packageJson, }: {
|
|
5
|
+
rootEntry: string;
|
|
6
|
+
schema: VovkSchema;
|
|
7
|
+
openAPIObject?: Partial<OpenAPIObject>;
|
|
8
|
+
package?: CodeSamplePackageJson;
|
|
9
|
+
}): OpenAPIObject;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.vovkSchemaToOpenAPI = vovkSchemaToOpenAPI;
|
|
4
|
+
const json_schema_sampler_1 = require("@stoplight/json-schema-sampler");
|
|
5
|
+
const createCodeExamples_1 = require("../utils/createCodeExamples");
|
|
6
|
+
const types_1 = require("../types");
|
|
7
|
+
function extractComponents(schema) {
|
|
8
|
+
if (!schema)
|
|
9
|
+
return [undefined, {}];
|
|
10
|
+
const components = {};
|
|
11
|
+
// Function to collect components and replace $refs recursively
|
|
12
|
+
const process = (obj, path = []) => {
|
|
13
|
+
if (!obj || typeof obj !== 'object')
|
|
14
|
+
return obj;
|
|
15
|
+
// Handle arrays
|
|
16
|
+
if (Array.isArray(obj)) {
|
|
17
|
+
return obj.map((item) => process(item, path));
|
|
18
|
+
}
|
|
19
|
+
// Create a copy to modify
|
|
20
|
+
const result = {};
|
|
21
|
+
Object.entries({ ...obj.definitions, ...obj.$defs }).forEach(([key, value]) => {
|
|
22
|
+
components[key] = process(value, [...path, key]);
|
|
23
|
+
});
|
|
24
|
+
// Process all properties
|
|
25
|
+
for (const [key, value] of Object.entries(obj ?? {})) {
|
|
26
|
+
// Skip already processed special properties
|
|
27
|
+
if (key === '$defs' || key === 'definitions')
|
|
28
|
+
continue;
|
|
29
|
+
if (key === '$ref' && typeof value === 'string') {
|
|
30
|
+
// Extract the component name from the reference
|
|
31
|
+
const refParts = value.split('/');
|
|
32
|
+
const refName = refParts[refParts.length - 1];
|
|
33
|
+
// Replace with component reference
|
|
34
|
+
result[key] = `#/components/schemas/${refName}`;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Recursively process other properties
|
|
38
|
+
result[key] = process(value, [...path, key]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
const processedSchema = process(schema);
|
|
44
|
+
return [processedSchema, components];
|
|
45
|
+
}
|
|
46
|
+
function vovkSchemaToOpenAPI({ rootEntry, schema: fullSchema, openAPIObject = {}, package: packageJson = { name: 'vovk-client' }, }) {
|
|
47
|
+
const paths = {};
|
|
48
|
+
const components = {};
|
|
49
|
+
for (const [segmentName, segmentSchema] of Object.entries(fullSchema.segments ?? {})) {
|
|
50
|
+
for (const c of Object.values(segmentSchema.controllers)) {
|
|
51
|
+
for (const [handlerName, h] of Object.entries(c.handlers ?? {})) {
|
|
52
|
+
if (h.openapi) {
|
|
53
|
+
const [queryValidation, queryComponents] = extractComponents(h?.validation?.query);
|
|
54
|
+
const [bodyValidation, bodyComponents] = extractComponents(h?.validation?.body);
|
|
55
|
+
const [paramsValidation, paramsComponents] = extractComponents(h?.validation?.params);
|
|
56
|
+
const [outputValidation, outputComponents] = extractComponents(h?.validation?.output);
|
|
57
|
+
const [iterationValidation, iterationComponents] = extractComponents(h?.validation?.iteration);
|
|
58
|
+
// TODO: Handle name conflicts?
|
|
59
|
+
Object.assign(components, queryComponents, bodyComponents, paramsComponents, outputComponents, iterationComponents);
|
|
60
|
+
const { ts, rs, py } = (0, createCodeExamples_1.createCodeExamples)({
|
|
61
|
+
package: packageJson,
|
|
62
|
+
handlerName,
|
|
63
|
+
handlerSchema: h,
|
|
64
|
+
controllerSchema: c,
|
|
65
|
+
});
|
|
66
|
+
const queryParameters = queryValidation && 'type' in queryValidation && 'properties' in queryValidation
|
|
67
|
+
? Object.entries(queryValidation.properties ?? {}).map(([propName, propSchema]) => ({
|
|
68
|
+
name: propName,
|
|
69
|
+
in: 'query',
|
|
70
|
+
required: queryValidation.required ? queryValidation.required.includes(propName) : false,
|
|
71
|
+
schema: propSchema,
|
|
72
|
+
}))
|
|
73
|
+
: null;
|
|
74
|
+
const pathParameters = paramsValidation && 'type' in paramsValidation && 'properties' in paramsValidation
|
|
75
|
+
? Object.entries(paramsValidation.properties ?? {}).map(([propName, propSchema]) => ({
|
|
76
|
+
name: propName,
|
|
77
|
+
in: 'path',
|
|
78
|
+
required: paramsValidation.required ? paramsValidation.required.includes(propName) : false,
|
|
79
|
+
schema: propSchema,
|
|
80
|
+
}))
|
|
81
|
+
: null;
|
|
82
|
+
const path = '/' +
|
|
83
|
+
[rootEntry.replace(/^\/+|\/+$/g, ''), segmentName, c.prefix, h.path]
|
|
84
|
+
.filter(Boolean)
|
|
85
|
+
.join('/')
|
|
86
|
+
.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');
|
|
87
|
+
paths[path] = paths[path] ?? {};
|
|
88
|
+
const httpMethod = h.httpMethod.toLowerCase();
|
|
89
|
+
paths[path][httpMethod] ??= {};
|
|
90
|
+
paths[path][httpMethod] = {
|
|
91
|
+
...h.openapi,
|
|
92
|
+
...paths[path][httpMethod],
|
|
93
|
+
'x-codeSamples': [
|
|
94
|
+
...(paths[path][httpMethod]['x-codeSamples'] ?? []),
|
|
95
|
+
...(h.openapi['x-codeSamples'] ?? []),
|
|
96
|
+
{
|
|
97
|
+
label: 'TypeScript RPC',
|
|
98
|
+
lang: 'typescript',
|
|
99
|
+
source: ts,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
label: 'Python RPC',
|
|
103
|
+
lang: 'python',
|
|
104
|
+
source: py,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: 'Rust RPC',
|
|
108
|
+
lang: 'rust',
|
|
109
|
+
source: rs,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
...(queryParameters || pathParameters
|
|
113
|
+
? {
|
|
114
|
+
parameters: h.openapi.parameters ?? [...(queryParameters || []), ...(pathParameters || [])],
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
117
|
+
...(paths[path][httpMethod].parameters
|
|
118
|
+
? {
|
|
119
|
+
parameters: paths[path][httpMethod].parameters,
|
|
120
|
+
}
|
|
121
|
+
: {}),
|
|
122
|
+
...(outputValidation && 'type' in outputValidation && 'properties' in outputValidation
|
|
123
|
+
? {
|
|
124
|
+
responses: {
|
|
125
|
+
200: {
|
|
126
|
+
description: 'description' in outputValidation ? outputValidation.description : 'Success',
|
|
127
|
+
content: {
|
|
128
|
+
'application/json': {
|
|
129
|
+
schema: outputValidation,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
...h.openapi?.responses,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
137
|
+
...(iterationValidation && 'type' in iterationValidation && 'properties' in iterationValidation
|
|
138
|
+
? {
|
|
139
|
+
responses: {
|
|
140
|
+
200: {
|
|
141
|
+
description: 'description' in iterationValidation ? iterationValidation.description : 'JSON Lines response',
|
|
142
|
+
content: {
|
|
143
|
+
'application/jsonl': {
|
|
144
|
+
schema: {
|
|
145
|
+
...iterationValidation,
|
|
146
|
+
examples: iterationValidation.examples ?? [
|
|
147
|
+
[
|
|
148
|
+
JSON.stringify((0, json_schema_sampler_1.sample)(iterationValidation)),
|
|
149
|
+
JSON.stringify((0, json_schema_sampler_1.sample)(iterationValidation)),
|
|
150
|
+
JSON.stringify((0, json_schema_sampler_1.sample)(iterationValidation)),
|
|
151
|
+
].join('\n'),
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
...h.openapi?.responses,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
: {}),
|
|
161
|
+
...(paths[path][httpMethod].responses
|
|
162
|
+
? {
|
|
163
|
+
responses: paths[path][httpMethod].responses,
|
|
164
|
+
}
|
|
165
|
+
: {}),
|
|
166
|
+
...(bodyValidation && 'type' in bodyValidation && 'properties' in bodyValidation
|
|
167
|
+
? {
|
|
168
|
+
requestBody: h.openapi?.requestBody ?? {
|
|
169
|
+
description: 'description' in bodyValidation ? bodyValidation.description : 'Request body',
|
|
170
|
+
required: true,
|
|
171
|
+
content: {
|
|
172
|
+
'application/json': {
|
|
173
|
+
schema: bodyValidation,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
: {}),
|
|
179
|
+
...(paths[path][httpMethod].requestBody
|
|
180
|
+
? {
|
|
181
|
+
requestBody: paths[path][httpMethod].requestBody,
|
|
182
|
+
}
|
|
183
|
+
: {}),
|
|
184
|
+
tags: paths[path][httpMethod].tags ?? h.openapi?.tags,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...openAPIObject,
|
|
192
|
+
openapi: '3.1.0',
|
|
193
|
+
info: {
|
|
194
|
+
title: packageJson?.description ?? 'API',
|
|
195
|
+
version: packageJson?.version ?? '0.0.1',
|
|
196
|
+
...openAPIObject?.info,
|
|
197
|
+
},
|
|
198
|
+
components: {
|
|
199
|
+
schemas: {
|
|
200
|
+
...(openAPIObject?.components?.schemas ?? components),
|
|
201
|
+
HttpStatus: {
|
|
202
|
+
type: 'integer',
|
|
203
|
+
description: 'HTTP status code',
|
|
204
|
+
enum: Object.keys(types_1.HttpStatus)
|
|
205
|
+
.map((k) => types_1.HttpStatus[k])
|
|
206
|
+
.filter(Boolean)
|
|
207
|
+
.filter((v) => typeof v === 'number'),
|
|
208
|
+
},
|
|
209
|
+
VovkErrorResponse: {
|
|
210
|
+
type: 'object',
|
|
211
|
+
description: 'Vovk error response',
|
|
212
|
+
properties: {
|
|
213
|
+
cause: {
|
|
214
|
+
description: 'Error cause of any shape',
|
|
215
|
+
},
|
|
216
|
+
statusCode: {
|
|
217
|
+
$ref: '#/components/schemas/HttpStatus',
|
|
218
|
+
},
|
|
219
|
+
message: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: 'Error message',
|
|
222
|
+
},
|
|
223
|
+
isError: {
|
|
224
|
+
type: 'boolean',
|
|
225
|
+
const: true,
|
|
226
|
+
description: 'Indicates that this object represents an error',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
required: ['statusCode', 'message', 'isError'],
|
|
230
|
+
additionalProperties: false,
|
|
231
|
+
},
|
|
232
|
+
...openAPIObject?.components?.schemas,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
paths,
|
|
236
|
+
};
|
|
237
|
+
}
|