vovk-cli 0.0.1-draft.266 → 0.0.1-draft.268
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.
|
@@ -8,7 +8,7 @@ export namespace Mixins {
|
|
|
8
8
|
<% if(segment.meta?.components?.schemas) { %>
|
|
9
9
|
export namespace <%= t._.upperFirst(t._.camelCase(segment.segmentName)) %> {
|
|
10
10
|
<% for (const [componentName, componentSchema] of Object.entries(segment.meta.components.schemas)) { %>
|
|
11
|
-
<%-
|
|
11
|
+
<%- t.compileJSONSchemaToTypeScriptType(componentSchema, componentName, segment.meta.components, { dontCreateRefTypes: true }) %>
|
|
12
12
|
<% } %>
|
|
13
13
|
}
|
|
14
14
|
<% } %>
|
|
@@ -22,11 +22,11 @@ export namespace Types {
|
|
|
22
22
|
export namespace <%= controllerSchema.rpcModuleName %> {
|
|
23
23
|
<% for (const [handlerName, handlerSchema] of Object.entries(controllerSchema.handlers)) { %>
|
|
24
24
|
export namespace <%= t._.upperFirst(handlerName) %> {
|
|
25
|
-
<%-
|
|
26
|
-
<%-
|
|
27
|
-
<%-
|
|
28
|
-
<%-
|
|
29
|
-
<%-
|
|
25
|
+
<%- t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.body, 'Body') %>
|
|
26
|
+
<%- t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.query, 'Query') %>
|
|
27
|
+
<%- t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.params, 'Params') %>
|
|
28
|
+
<%- t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.output, 'Output') %>
|
|
29
|
+
<%- t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.iteration, 'Iteration') %>
|
|
30
30
|
}
|
|
31
31
|
<% } %>
|
|
32
32
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare function compileJSONSchemaToTypeScriptType(schema:
|
|
1
|
+
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
3
|
+
export declare function compileJSONSchemaToTypeScriptType(schema: JSONSchema7, typeName: string, components?: NonNullable<OpenAPIObject['components']>, options?: {
|
|
4
|
+
dontCreateRefTypes?: boolean;
|
|
5
|
+
}): string;
|
|
@@ -1,39 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function
|
|
3
|
-
if (schema === null || typeof schema !== 'object') {
|
|
4
|
-
return schema;
|
|
5
|
-
}
|
|
6
|
-
if (typeof schema.$ref === 'string') {
|
|
7
|
-
const refPath = schema.$ref.split('/');
|
|
8
|
-
const componentName = refPath[refPath.length - 1];
|
|
9
|
-
if (!(componentName in (components.schemas ?? schema.$defs ?? schema.definitions ?? {}))) {
|
|
10
|
-
return {
|
|
11
|
-
tsType: `unknown`,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
if (Array.isArray(schema)) {
|
|
16
|
-
return schema.map((item) => replaceUnresolvedRefs(item, components));
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
const result = {};
|
|
20
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
21
|
-
result[key] = replaceUnresolvedRefs(value, components);
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export async function compileJSONSchemaToTypeScriptType(schema, typeName, components = {}) {
|
|
1
|
+
import { compileTs } from './compileTs.mjs';
|
|
2
|
+
export function compileJSONSchemaToTypeScriptType(schema, typeName, components = {}, options = {}) {
|
|
27
3
|
if (!schema)
|
|
28
4
|
return '';
|
|
29
5
|
if ('tsType' in schema && typeof schema.tsType === 'string')
|
|
30
6
|
return `export type ${typeName} = ${schema.tsType};\n`;
|
|
31
|
-
|
|
32
|
-
components = replaceUnresolvedRefs(components, components);
|
|
33
|
-
// tsType attribute isn't working with objects that use $ref, so we need to handle it separately
|
|
34
|
-
const tsType = await compile({ ...schema, components }, typeName, {
|
|
35
|
-
bannerComment: schema.description ? `/**\n * ${schema.description}\n */` : '',
|
|
36
|
-
ignoreMinAndMaxItems: true,
|
|
37
|
-
});
|
|
7
|
+
const tsType = compileTs({ schema: { ...schema, components }, name: typeName, ...options });
|
|
38
8
|
return tsType;
|
|
39
9
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { JSONSchema7 } from 'json-schema';
|
|
2
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
3
|
+
interface CompileOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
schema: JSONSchema7 & {
|
|
6
|
+
components?: OpenAPIObject['components'];
|
|
7
|
+
};
|
|
8
|
+
refs?: Map<string, JSONSchema7>;
|
|
9
|
+
dontCreateRefTypes?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function compileTs(options: CompileOptions): string;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
export function compileTs(options) {
|
|
2
|
+
const context = {
|
|
3
|
+
refs: options.refs || new Map(),
|
|
4
|
+
compiledRefs: new Map(),
|
|
5
|
+
refsInProgress: new Set(),
|
|
6
|
+
};
|
|
7
|
+
// Collect all definitions from the schema
|
|
8
|
+
collectDefinitions(options.schema, context.refs);
|
|
9
|
+
const mainType = compileSchema(options.schema, options.name, context);
|
|
10
|
+
// Compile all referenced types, unless dontCreateRefTypes is set
|
|
11
|
+
const compiledRefs = options.dontCreateRefTypes
|
|
12
|
+
? ''
|
|
13
|
+
: Array.from(context.compiledRefs.entries())
|
|
14
|
+
.map(([, typeDecl]) => typeDecl)
|
|
15
|
+
.join('\n\n');
|
|
16
|
+
return compiledRefs
|
|
17
|
+
? `${compiledRefs}\n\n${options.schema.description ? `/** ${options.schema.description} */\n` : ''}export type ${options.name} = ${mainType};`
|
|
18
|
+
: `${options.schema.description ? `/** ${options.schema.description} */\n` : ''}export type ${options.name} = ${mainType};`;
|
|
19
|
+
}
|
|
20
|
+
function collectDefinitions(schema, refs) {
|
|
21
|
+
// Collect from $defs
|
|
22
|
+
if (schema.$defs) {
|
|
23
|
+
Object.entries(schema.$defs).forEach(([key, def]) => {
|
|
24
|
+
if (typeof def === 'object') {
|
|
25
|
+
refs.set(`#/$defs/${key}`, def);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Collect from definitions (older spec)
|
|
30
|
+
if (schema.definitions) {
|
|
31
|
+
Object.entries(schema.definitions).forEach(([key, def]) => {
|
|
32
|
+
if (typeof def === 'object') {
|
|
33
|
+
refs.set(`#/definitions/${key}`, def);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Collect from components/schemas (OpenAPI spec)
|
|
38
|
+
if (schema?.components?.schemas) {
|
|
39
|
+
Object.entries(schema?.components?.schemas ?? {}).forEach(([key, def]) => {
|
|
40
|
+
if (typeof def === 'object') {
|
|
41
|
+
refs.set(`#/components/schemas/${key}`, def);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
// Recursively collect from nested schemas
|
|
46
|
+
const schemasToProcess = [];
|
|
47
|
+
if (schema.properties) {
|
|
48
|
+
schemasToProcess.push(...Object.values(schema.properties).filter(isSchema));
|
|
49
|
+
}
|
|
50
|
+
if (schema.items) {
|
|
51
|
+
if (Array.isArray(schema.items)) {
|
|
52
|
+
schemasToProcess.push(...schema.items.filter(isSchema));
|
|
53
|
+
}
|
|
54
|
+
else if (isSchema(schema.items)) {
|
|
55
|
+
schemasToProcess.push(schema.items);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (schema.additionalProperties && isSchema(schema.additionalProperties)) {
|
|
59
|
+
schemasToProcess.push(schema.additionalProperties);
|
|
60
|
+
}
|
|
61
|
+
if (schema.allOf)
|
|
62
|
+
schemasToProcess.push(...schema.allOf.filter(isSchema));
|
|
63
|
+
if (schema.anyOf)
|
|
64
|
+
schemasToProcess.push(...schema.anyOf.filter(isSchema));
|
|
65
|
+
if (schema.oneOf)
|
|
66
|
+
schemasToProcess.push(...schema.oneOf.filter(isSchema));
|
|
67
|
+
schemasToProcess.forEach((s) => collectDefinitions(s, refs));
|
|
68
|
+
}
|
|
69
|
+
function isSchema(value) {
|
|
70
|
+
return typeof value === 'object' && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
function compileSchema(schema, name, context) {
|
|
73
|
+
if (typeof schema === 'boolean') {
|
|
74
|
+
return schema ? 'any' : 'never';
|
|
75
|
+
}
|
|
76
|
+
// Handle x-tsType extension
|
|
77
|
+
if ('x-tsType' in schema && typeof schema['x-tsType'] === 'string') {
|
|
78
|
+
return schema['x-tsType'];
|
|
79
|
+
}
|
|
80
|
+
// Handle $ref
|
|
81
|
+
if (schema.$ref) {
|
|
82
|
+
return handleRef(schema.$ref, context);
|
|
83
|
+
}
|
|
84
|
+
// Handle combinators
|
|
85
|
+
if (schema.allOf) {
|
|
86
|
+
return handleAllOf(schema.allOf, name, context);
|
|
87
|
+
}
|
|
88
|
+
if (schema.anyOf) {
|
|
89
|
+
return handleAnyOf(schema.anyOf, name, context);
|
|
90
|
+
}
|
|
91
|
+
if (schema.oneOf) {
|
|
92
|
+
return handleOneOf(schema.oneOf, name, context);
|
|
93
|
+
}
|
|
94
|
+
// Handle type-specific compilation
|
|
95
|
+
if (schema.enum) {
|
|
96
|
+
return handleEnum(schema.enum);
|
|
97
|
+
}
|
|
98
|
+
if (schema.const !== undefined) {
|
|
99
|
+
return handleConst(schema.const);
|
|
100
|
+
}
|
|
101
|
+
if (!schema.type) {
|
|
102
|
+
// No type specified, could be object
|
|
103
|
+
if (schema.properties || schema.additionalProperties) {
|
|
104
|
+
return handleObject(schema, name, context);
|
|
105
|
+
}
|
|
106
|
+
return 'any';
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(schema.type)) {
|
|
109
|
+
return schema.type.map((t) => compileSchemaWithType({ ...schema, type: t }, name, context)).join(' | ');
|
|
110
|
+
}
|
|
111
|
+
return compileSchemaWithType(schema, name, context);
|
|
112
|
+
}
|
|
113
|
+
function compileSchemaWithType(schema, name, context) {
|
|
114
|
+
switch (schema.type) {
|
|
115
|
+
case 'null':
|
|
116
|
+
return 'null';
|
|
117
|
+
case 'boolean':
|
|
118
|
+
return 'boolean';
|
|
119
|
+
case 'string':
|
|
120
|
+
return 'string';
|
|
121
|
+
case 'number':
|
|
122
|
+
return 'number';
|
|
123
|
+
case 'integer':
|
|
124
|
+
return 'number';
|
|
125
|
+
case 'array':
|
|
126
|
+
return handleArray(schema, name, context);
|
|
127
|
+
case 'object':
|
|
128
|
+
return handleObject(schema, name, context);
|
|
129
|
+
default:
|
|
130
|
+
return 'any';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function handleRef(ref, context) {
|
|
134
|
+
const typeName = refToTypeName(ref);
|
|
135
|
+
// Check if we're already compiling this ref (circular reference)
|
|
136
|
+
if (context.refsInProgress.has(ref)) {
|
|
137
|
+
return typeName;
|
|
138
|
+
}
|
|
139
|
+
// Check if already compiled
|
|
140
|
+
if (context.compiledRefs.has(ref)) {
|
|
141
|
+
return typeName;
|
|
142
|
+
}
|
|
143
|
+
// Find the referenced schema
|
|
144
|
+
const referencedSchema = context.refs.get(ref);
|
|
145
|
+
if (!referencedSchema) {
|
|
146
|
+
return 'any'; // Reference not found
|
|
147
|
+
}
|
|
148
|
+
// Mark as in progress
|
|
149
|
+
context.refsInProgress.add(ref);
|
|
150
|
+
// Compile the referenced schema
|
|
151
|
+
const compiledType = compileSchema(referencedSchema, typeName, context);
|
|
152
|
+
const description = referencedSchema.description ? `/** ${referencedSchema.description} */\n` : '';
|
|
153
|
+
context.compiledRefs.set(ref, `${description}export type ${typeName} = ${compiledType};`);
|
|
154
|
+
// Mark as completed
|
|
155
|
+
context.refsInProgress.delete(ref);
|
|
156
|
+
return typeName;
|
|
157
|
+
}
|
|
158
|
+
function handleAllOf(schemas, name, context) {
|
|
159
|
+
const types = schemas.map((s, i) => compileSchema(s, `${name}AllOf${i}`, context));
|
|
160
|
+
// For allOf, we need to intersect types
|
|
161
|
+
// If they're all objects, we can merge them properly
|
|
162
|
+
const objectTypes = types.filter((t) => t.startsWith('{') && t.endsWith('}'));
|
|
163
|
+
if (objectTypes.length === types.length) {
|
|
164
|
+
// Merge object types
|
|
165
|
+
const merged = objectTypes.map((t) => t.slice(1, -1).trim()).filter((t) => t.length > 0);
|
|
166
|
+
return merged.length > 0 ? `{ ${merged.join('; ')} }` : '{}';
|
|
167
|
+
}
|
|
168
|
+
return types.join(' & ');
|
|
169
|
+
}
|
|
170
|
+
function handleAnyOf(schemas, name, context) {
|
|
171
|
+
const types = schemas.map((s, i) => compileSchema(s, `${name}AnyOf${i}`, context));
|
|
172
|
+
return types.join(' | ');
|
|
173
|
+
}
|
|
174
|
+
function handleOneOf(schemas, name, context) {
|
|
175
|
+
// For TypeScript, oneOf behaves like anyOf
|
|
176
|
+
const types = schemas.map((s, i) => compileSchema(s, `${name}OneOf${i}`, context));
|
|
177
|
+
return types.join(' | ');
|
|
178
|
+
}
|
|
179
|
+
function handleEnum(enumValues) {
|
|
180
|
+
return enumValues.map((v) => JSON.stringify(v)).join(' | ');
|
|
181
|
+
}
|
|
182
|
+
function handleConst(value) {
|
|
183
|
+
return JSON.stringify(value);
|
|
184
|
+
}
|
|
185
|
+
function handleArray(schema, name, context) {
|
|
186
|
+
if (!schema.items) {
|
|
187
|
+
return 'any[]';
|
|
188
|
+
}
|
|
189
|
+
if (Array.isArray(schema.items)) {
|
|
190
|
+
// Tuple (ignoring min/max as requested)
|
|
191
|
+
const types = schema.items.map((item, i) => compileSchema(item, `${name}Item${i}`, context));
|
|
192
|
+
return `[${types.join(', ')}]`;
|
|
193
|
+
}
|
|
194
|
+
const itemType = compileSchema(schema.items, `${name}Item`, context);
|
|
195
|
+
return `${wrapUnionType(itemType)}[]`;
|
|
196
|
+
}
|
|
197
|
+
function handleObject(schema, name, context) {
|
|
198
|
+
const props = [];
|
|
199
|
+
// Handle known properties
|
|
200
|
+
if (schema.properties) {
|
|
201
|
+
const required = new Set(schema.required || []);
|
|
202
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
203
|
+
if (!isSchema(propSchema))
|
|
204
|
+
continue;
|
|
205
|
+
const isRequired = required.has(propName);
|
|
206
|
+
const propType = compileSchema(propSchema, `${name}${capitalize(propName)}`, context);
|
|
207
|
+
const safePropName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName) ? propName : `"${propName}"`;
|
|
208
|
+
// Add JSDoc comment if description is present
|
|
209
|
+
const comment = propSchema.description ? `\n/** ${propSchema.description} */\n` : '';
|
|
210
|
+
props.push(`${comment}${safePropName}${isRequired ? '' : '?'}: ${propType}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Handle additional properties
|
|
214
|
+
if (schema.additionalProperties === true) {
|
|
215
|
+
props.push('[key: string]: any');
|
|
216
|
+
}
|
|
217
|
+
else if (schema.additionalProperties && isSchema(schema.additionalProperties)) {
|
|
218
|
+
const additionalType = compileSchema(schema.additionalProperties, `${name}Additional`, context);
|
|
219
|
+
props.push(`[key: string]: ${additionalType}`);
|
|
220
|
+
}
|
|
221
|
+
// Handle pattern properties
|
|
222
|
+
if (schema.patternProperties) {
|
|
223
|
+
// For simplicity, treat pattern properties as string index signature
|
|
224
|
+
const patternTypes = Object.values(schema.patternProperties)
|
|
225
|
+
.filter(isSchema)
|
|
226
|
+
.map((s, i) => compileSchema(s, `${name}Pattern${i}`, context));
|
|
227
|
+
if (patternTypes.length > 0) {
|
|
228
|
+
props.push(`[key: string]: ${patternTypes.join(' | ')}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return props.length > 0 ? `{ ${props.join('; ')} }` : '{}';
|
|
232
|
+
}
|
|
233
|
+
function refToTypeName(ref) {
|
|
234
|
+
// Extract the last part of the reference as the type name
|
|
235
|
+
const parts = ref.split('/');
|
|
236
|
+
const name = parts[parts.length - 1];
|
|
237
|
+
return capitalize(name);
|
|
238
|
+
}
|
|
239
|
+
function capitalize(str) {
|
|
240
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
241
|
+
}
|
|
242
|
+
function wrapUnionType(type) {
|
|
243
|
+
// Wrap union types in parentheses for array types
|
|
244
|
+
return type.includes('|') ? `(${type})` : type;
|
|
245
|
+
}
|