vovk-cli 0.0.1-draft.255 → 0.0.1-draft.256

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.
@@ -7,7 +7,7 @@ import type { createRPC } from '<%= t.imports.module.createRPC %>';
7
7
  import type { Controllers as Controllers<%= i %> } from "<%= t.segmentMeta[segment.segmentName].segmentImportPath %>";
8
8
  <% }}) %>
9
9
  <% if (t.hasMixins) { %>
10
- import type { Controllers as MixinControllers } from "./mixins";
10
+ import type { Controllers as MixinControllers, Mixins } from "./mixins";
11
11
  <% } %>
12
12
 
13
13
  type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
@@ -16,4 +16,7 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
16
16
  export const <%= rpcModuleName %>: ReturnType<typeof createRPC<<%= segment.segmentType === 'mixin' ? `MixinControllers` : `Controllers${i}` %>["<%= rpcModuleName %>"], Options>>;
17
17
  <% })
18
18
  }) %>
19
- export { schema } from './schema.cjs';
19
+ export { schema } from './schema.cjs';
20
+ <% if (t.hasMixins) { %>
21
+ export { Mixins };
22
+ <% } %>
@@ -1,27 +1,64 @@
1
1
  <%- `// auto-generated by Vovk.ts ${new Date().toISOString()}` %>
2
2
  import type { VovkRequest, VovkStreamAsyncIterable, KnownAny } from 'vovk';
3
3
 
4
- <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType === 'mixin').forEach((segment, i) => {
5
- Object.values(segment.controllers).forEach((controllerSchema) => {
6
- Object.entries(controllerSchema.handlers).forEach(([handlerName, handlerSchema]) => {
7
- ['body', 'query', 'params', 'output', 'iteration'].forEach((type) => {
8
- if (handlerSchema.validation?.[type]?.$defs || handlerSchema.validation?.[type]?.definitions) { %>
9
- <%- t.convertJSONSchemaToTypeScriptDef(handlerSchema.validation?.[type], `$defs__${controllerSchema.rpcModuleName}__${handlerName}__${type}`).$defs %>
10
- <% }
11
- })
12
- })
13
- })
14
- }); %>
4
+ <% const mixins = Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType === 'mixin'); %>
5
+
6
+ export namespace Mixins {
7
+ <% for (const segment of mixins) { %>
8
+ <% console.log(segment); if(segment.meta?.components?.schemas) { %>
9
+ export namespace <%= t._.upperFirst(t._.camelCase(segment.segmentName)) %> {
10
+ <% for (const [componentName, componentSchema] of Object.entries(segment.meta.components.schemas)) { %>
11
+ <%- await t.compileJSONSchemaToTypeScriptType(componentSchema, componentName, segment.meta.components) %>
12
+ <% } %>
13
+ }
14
+ <% } %>
15
+ <% } %>
16
+ }
17
+
18
+ export namespace Types {
19
+ <% for (const segment of mixins) { %>
20
+ export namespace <%= t._.upperFirst(t._.camelCase(segment.segmentName)) %> {
21
+ <% for (const [controllerName, controllerSchema] of Object.entries(segment.controllers)) { %>
22
+ export namespace <%= controllerSchema.rpcModuleName %> {
23
+ <% for (const [handlerName, handlerSchema] of Object.entries(controllerSchema.handlers)) { %>
24
+ export namespace <%= t._.upperFirst(handlerName) %> {
25
+ <%- await t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.body, 'Body') %>
26
+ <%- await t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.query, 'Query') %>
27
+ <%- await t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.params, 'Params') %>
28
+ <%- await t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.output, 'Output') %>
29
+ <%- await t.compileJSONSchemaToTypeScriptType(handlerSchema.validation?.iteration, 'Iteration') %>
30
+ }
31
+ <% } %>
32
+ }
33
+ <% } %>
34
+ }
35
+ <% } %>
36
+ }
37
+
38
+ <% const getType = (segment, controllerSchema, handlerName, validationType) => {
39
+ const segmentNs = t._.upperFirst(t._.camelCase(segment.segmentName));
40
+ const controllerNs = controllerSchema.rpcModuleName;
41
+ const handlerNs = t._.upperFirst(handlerName);
42
+ const typeName = t._.upperFirst(validationType);
43
+ const handlerSchema = controllerSchema.handlers[handlerName];
44
+ if(handlerSchema.validation?.[validationType] ) {
45
+ return 'Types.' + segmentNs + '.' + controllerNs + '.' + handlerNs + '.' + typeName;
46
+ }
47
+ return 'null';
48
+ } %>
15
49
 
16
50
  export type Controllers = {
17
- <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType === 'mixin').forEach((segment, i) => {%>
18
- <% Object.values(segment.controllers).forEach((controllerSchema) => { %>
51
+ <% for (const segment of Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType === 'mixin')) { %>
52
+ <% for (const controllerSchema of Object.values(segment.controllers)) { %>
19
53
  <%= controllerSchema.rpcModuleName %>: {
20
- <% Object.entries(controllerSchema.handlers).forEach(([handlerName, handlerSchema]) => { %>
21
- <%= handlerName %>: (req: VovkRequest<<%- t.convertJSONSchemaToTypeScriptDef(handlerSchema.validation?.body, `$defs__${controllerSchema.rpcModuleName}__${handlerName}__body`).$type %>, <%- t.convertJSONSchemaToTypeScriptDef(handlerSchema.validation?.query, `$defs__${controllerSchema.rpcModuleName}__${handlerName}__query`).$type %>, <%- t.convertJSONSchemaToTypeScriptDef(handlerSchema.validation?.params, `$defs__${controllerSchema.rpcModuleName}__${handlerName}__params`).$type %>>) => <%- handlerSchema.validation?.output ? `Promise<${t.convertJSONSchemaToTypeScriptDef(handlerSchema.validation?.output, `$defs__${controllerSchema.rpcModuleName}__${handlerName}__output`).$type}>` : handlerSchema.validation?.iteration ? `Promise<VovkStreamAsyncIterable<${t.convertJSONSchemaToTypeScriptDef(handlerSchema.validation?.iteration, `$defs__${controllerSchema.rpcModuleName}__${handlerName}__iteration`).$type}>>` : 'Promise<KnownAny>' %>,
22
- <% }) %>
54
+ <% for (const [handlerName, handlerSchema] of Object.entries(controllerSchema.handlers)) { %>
55
+ <%= handlerName %>: (req: VovkRequest<
56
+ <%- getType(segment, controllerSchema, handlerName, 'body') %>,
57
+ <%- getType(segment, controllerSchema, handlerName, 'query') %>,
58
+ <%- getType(segment, controllerSchema, handlerName, 'params') %>
59
+ >) => <%- handlerSchema.validation?.output ? `Promise<${getType(segment, controllerSchema, handlerName, 'output')}>` : handlerSchema.validation?.iteration ? `Promise<VovkStreamAsyncIterable<${getType(segment, controllerSchema, handlerName, 'iteration')}>` : 'Promise<KnownAny>' %>,
60
+ <% } %>
23
61
  };
24
- <% }) %>
25
- <% }); %>
26
- };
27
-
62
+ <% } %>
63
+ <% } %>
64
+ };
@@ -7,7 +7,7 @@ import type { createRPC } from '<%= t.imports.module.createRPC %>';
7
7
  import type { Controllers as Controllers<%= i %> } from "<%= t.segmentMeta[segment.segmentName].segmentImportPath %>";
8
8
  <% }}) %>
9
9
  <% if (t.hasMixins) { %>
10
- import type { Controllers as MixinControllers } from "./mixins";
10
+ import type { Controllers as MixinControllers, Mixins } from "./mixins";
11
11
  <% } %>
12
12
 
13
13
  type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
@@ -16,4 +16,7 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
16
16
  export const <%= rpcModuleName %>: ReturnType<typeof createRPC<<%= segment.segmentType === 'mixin' ? `MixinControllers` : `Controllers${i}` %>["<%= rpcModuleName %>"], Options>>;
17
17
  <% })
18
18
  }) %>
19
- export { schema } from './schema.cjs';
19
+ export { schema } from './schema.cjs';
20
+ <% if (t.hasMixins) { %>
21
+ export { Mixins };
22
+ <% } %>
@@ -4,6 +4,7 @@ import meta from './<%= t.schemaOutDir %>/_meta.json' with { type: "json" };
4
4
  <% } %>
5
5
  <% if(t.hasMixins) { %>
6
6
  import mixins from './mixins.json' with { type: "json" };
7
+ import type { Mixins } from './mixins.d.ts';
7
8
  <% } %>
8
9
  <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { if(segment.segmentType !== 'mixin') { %>
9
10
  import segment<%= i %> from './<%= t.schemaOutDir %>/<%= segment.segmentName || t.ROOT_SEGMENT_FILE_NAME %>.json' with { type: "json" };
@@ -28,3 +29,7 @@ export const schema = {
28
29
  <% } %>
29
30
  }
30
31
  };
32
+
33
+ <% if (t.hasMixins) { %>
34
+ export { Mixins };
35
+ <% } %>
@@ -8,7 +8,7 @@ import type { Controllers as Controllers<%= i %> } from "<%= t.segmentMeta[segme
8
8
  <% }
9
9
  });
10
10
  if (t.hasMixins) { %>
11
- import type { Controllers as MixinControllers } from "./mixins.d.ts";
11
+ import type { Controllers as MixinControllers, Mixins } from "./mixins.d.ts";
12
12
  <% }
13
13
  if (t.imports.validateOnClient) { %>
14
14
  import { validateOnClient } from '<%= t.imports.validateOnClient %>';
@@ -24,4 +24,7 @@ export const <%= rpcModuleName %> = createRPC<<%= segment.segmentType === 'mixin
24
24
  );
25
25
  <% })
26
26
  }) %>
27
- export { schema };
27
+ export { schema };
28
+ <% if (t.hasMixins) { %>
29
+ export { Mixins };
30
+ <% } %>
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import fs from 'node:fs/promises';
3
3
  import matter from 'gray-matter';
4
4
  import _ from 'lodash';
5
- import { openAPIToVovkSchema, VovkSchemaIdEnum } from 'vovk';
5
+ import { openAPIToVovkSchema } from 'vovk';
6
6
  import getClientTemplateFiles from './getClientTemplateFiles.mjs';
7
7
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
8
8
  import pickSegmentFullSchema from '../utils/pickSegmentFullSchema.mjs';
@@ -86,38 +86,19 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
86
86
  ...config.openApiMixins,
87
87
  ...cliOptionsToOpenAPIMixins(cliGenerateOptions ?? {}),
88
88
  };
89
+ /** @deprecated */
89
90
  let hasMixins = false;
90
91
  if (Object.keys(allOpenAPIMixins).length) {
91
- const mixins = Object.fromEntries(Object.entries(await normalizeOpenAPIMixins({ mixinModules: allOpenAPIMixins })).map(([mixinName, conf]) => {
92
- return [
93
- mixinName,
94
- openAPIToVovkSchema({
95
- ...conf,
96
- mixinName,
97
- }).segments[mixinName],
98
- ];
99
- }));
92
+ const mixins = Object.fromEntries(Object.entries(await normalizeOpenAPIMixins({ mixinModules: allOpenAPIMixins })).map(([mixinName, conf]) => [
93
+ mixinName,
94
+ openAPIToVovkSchema({ ...conf, mixinName }).segments[mixinName],
95
+ ]));
100
96
  hasMixins = true;
101
97
  fullSchema = {
102
98
  ...fullSchema,
103
99
  segments: {
104
100
  ...fullSchema.segments,
105
- ...Object.fromEntries(Object.entries(mixins).map(([segmentName, mixin]) => {
106
- return [
107
- segmentName,
108
- {
109
- $schema: VovkSchemaIdEnum.SEGMENT,
110
- emitSchema: true,
111
- segmentType: 'mixin',
112
- segmentName,
113
- forceApiRoot: mixin?.forceApiRoot, // TODO: Merging with existing segments and using apiRoot doesn't make a lot of sense
114
- controllers: {
115
- ...fullSchema.segments[segmentName]?.controllers,
116
- ...mixin?.controllers,
117
- },
118
- },
119
- ];
120
- })),
101
+ ...mixins,
121
102
  },
122
103
  };
123
104
  }
@@ -14,6 +14,7 @@ export default function getClientTemplateFiles({ config, cwd, log, configKey, cl
14
14
  log: ProjectInfo['log'];
15
15
  configKey: 'composedClient' | 'segmentedClient';
16
16
  cliGenerateOptions?: GenerateOptions;
17
+ /** @deprecated */
17
18
  hasMixins: boolean;
18
19
  }): Promise<{
19
20
  fromTemplates: string[];
@@ -31,6 +31,7 @@ export default async function getClientTemplateFiles({ config, cwd, log, configK
31
31
  }
32
32
  usedTemplateDefs[templateName] = usedDef;
33
33
  }
34
+ // $openapi['github']['components']['schemas']['User'];
34
35
  const templateFiles = [];
35
36
  const entries = Object.entries(usedTemplateDefs);
36
37
  for (let i = 0; i < entries.length; i++) {
@@ -7,7 +7,7 @@ import * as YAML from 'yaml';
7
7
  import TOML from '@iarna/toml';
8
8
  import prettify from '../utils/prettify.mjs';
9
9
  import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
10
- import { convertJSONSchemaToTypeScriptDef } from '../utils/convertJSONSchemaToTypeScriptDef.mjs';
10
+ import { compileJSONSchemaToTypeScriptType } from '../utils/compileJSONSchemaToTypeScriptType.mjs';
11
11
  export default async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, isEnsuringClient, outCwdRelativeDir, origin, templateDef, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, }) {
12
12
  const { config, apiRoot } = projectInfo;
13
13
  const { templateFilePath, relativeDir } = clientTemplateFile;
@@ -29,7 +29,7 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
29
29
  schema: fullSchema,
30
30
  VovkSchemaIdEnum,
31
31
  createCodeExamples,
32
- convertJSONSchemaToTypeScriptDef,
32
+ compileJSONSchemaToTypeScriptType,
33
33
  YAML,
34
34
  TOML,
35
35
  nodeNextResolutionExt: {
@@ -74,8 +74,9 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
74
74
  }
75
75
  // Render the template
76
76
  let rendered = templateFilePath.endsWith('.ejs')
77
- ? ejs.render(content, { t }, {
77
+ ? await ejs.render(content, { t }, {
78
78
  filename: templateFilePath,
79
+ async: true,
79
80
  })
80
81
  : templateContent;
81
82
  // Optionally prettify
@@ -0,0 +1,3 @@
1
+ import { JSONSchema } from 'json-schema-to-typescript';
2
+ import { OpenAPIObject } from 'openapi3-ts/oas31';
3
+ export declare function compileJSONSchemaToTypeScriptType(schema: JSONSchema, typeName: string, components?: NonNullable<OpenAPIObject['components']>): Promise<string>;
@@ -0,0 +1,225 @@
1
+ import { compile } from 'json-schema-to-typescript';
2
+ export async function compileJSONSchemaToTypeScriptType(schema, typeName, components = {}) {
3
+ if (!schema)
4
+ return '';
5
+ const tsType = await compile({ ...schema, components }, typeName, {
6
+ bannerComment: schema.description ? `/**\n * ${schema.description}\n */` : '',
7
+ style: {
8
+ bracketSpacing: true,
9
+ printWidth: 80,
10
+ semi: true,
11
+ singleQuote: true,
12
+ tabWidth: 2,
13
+ useTabs: false,
14
+ trailingComma: 'all',
15
+ },
16
+ // Don't generate separate interfaces for additionalProperties
17
+ additionalProperties: false,
18
+ // Enable strict null checks
19
+ strictIndexSignatures: true,
20
+ // Don't add schema as comment
21
+ });
22
+ return tsType;
23
+ }
24
+ /*
25
+ // Extend JSONSchema to include custom x-formData property
26
+ interface ExtendedJSONSchema extends JSONSchema {
27
+ 'x-formData'?: boolean;
28
+ [key: string]: any;
29
+ }
30
+
31
+ interface OpenAPIDocument {
32
+ openapi: string;
33
+ components?: {
34
+ schemas?: Record<string, ExtendedJSONSchema>;
35
+ };
36
+ [key: string]: any;
37
+ }
38
+
39
+ /**
40
+ * Converts OpenAPI 3.1 component schemas to TypeScript type definitions
41
+ * @param oasDoc - The OpenAPI 3.1 document
42
+ * @param options - Optional configuration for the TypeScript generation
43
+ * @returns A string containing all TypeScript type definitions
44
+ * /
45
+ export async function oasToTypeScript(
46
+ oasDoc: OpenAPIDocument,
47
+ options?: {
48
+ bannerComment?: string;
49
+ style?: {
50
+ bracketSpacing?: boolean;
51
+ printWidth?: number;
52
+ semi?: boolean;
53
+ singleQuote?: boolean;
54
+ tabWidth?: number;
55
+ useTabs?: boolean;
56
+ trailingComma?: 'all' | 'es5' | 'none';
57
+ };
58
+ }
59
+ ): Promise<string> {
60
+ // Validate that this is an OAS 3.1 document
61
+ if (!oasDoc.openapi || !oasDoc.openapi.startsWith('3.1')) {
62
+ throw new Error('Document must be an OpenAPI 3.1 specification');
63
+ }
64
+
65
+ // Extract schemas from components
66
+ const schemas = oasDoc.components?.schemas || {};
67
+
68
+ if (Object.keys(schemas).length === 0) {
69
+ return '// No schemas found in components';
70
+ }
71
+
72
+ const typeDefinitions: string[] = [];
73
+
74
+ // Process each schema
75
+ for (const [schemaName, schema] of Object.entries(schemas)) {
76
+ try {
77
+ // Check if schema has x-formData: true
78
+ if (schema['x-formData'] === true) {
79
+ // Generate a simple type alias to FormData
80
+ typeDefinitions.push(`export type ${schemaName} = FormData;\n`);
81
+ continue;
82
+ }
83
+
84
+ // Handle schema references
85
+ const resolvedSchema = resolveSchemaReferences(schema, schemas);
86
+
87
+ // Compile to TypeScript
88
+ const tsType = await compile(resolvedSchema as JSONSchema, schemaName, {
89
+ bannerComment: options?.bannerComment || '',
90
+ style: options?.style || {
91
+ bracketSpacing: true,
92
+ printWidth: 80,
93
+ semi: true,
94
+ singleQuote: true,
95
+ tabWidth: 2,
96
+ useTabs: false,
97
+ trailingComma: 'all',
98
+ },
99
+ // Don't generate separate interfaces for additionalProperties
100
+ additionalProperties: false,
101
+ // Enable strict null checks
102
+ strictIndexSignatures: true,
103
+ // Don't add schema as comment
104
+ $refOptions: {
105
+ resolve: {
106
+ // Resolve internal references
107
+ internal: true,
108
+ // Add our custom resolver
109
+ custom: {
110
+ order: 1,
111
+ canRead: (ref: string) => ref.startsWith('#/components/schemas/'),
112
+ },
113
+ },
114
+ },
115
+ });
116
+
117
+ typeDefinitions.push(tsType);
118
+ } catch (error) {
119
+ console.error(`Error processing schema "${schemaName}":`, error);
120
+ typeDefinitions.push(`// Error processing schema "${schemaName}": ${error.message}`);
121
+ }
122
+ }
123
+
124
+ return typeDefinitions.join('\n');
125
+ }
126
+
127
+ /**
128
+ * Resolves $ref references within a schema
129
+ * /
130
+ function resolveSchemaReferences(
131
+ schema: any,
132
+ allSchemas: Record<string, ExtendedJSONSchema>,
133
+ visited = new Set<string>()
134
+ ): any {
135
+ if (!schema || typeof schema !== 'object') {
136
+ return schema;
137
+ }
138
+
139
+ // Handle $ref
140
+ if (schema.$ref && typeof schema.$ref === 'string') {
141
+ const refPath = schema.$ref.replace('#/components/schemas/', '');
142
+
143
+ // Prevent circular references
144
+ if (visited.has(refPath)) {
145
+ return { type: 'object', additionalProperties: true };
146
+ }
147
+
148
+ visited.add(refPath);
149
+ const referencedSchema = allSchemas[refPath];
150
+
151
+ if (referencedSchema) {
152
+ return resolveSchemaReferences(referencedSchema, allSchemas, visited);
153
+ }
154
+
155
+ return schema;
156
+ }
157
+
158
+ // Handle arrays
159
+ if (Array.isArray(schema)) {
160
+ return schema.map((item) => resolveSchemaReferences(item, allSchemas, visited));
161
+ }
162
+
163
+ // Handle objects recursively
164
+ const resolved: any = {};
165
+ for (const [key, value] of Object.entries(schema)) {
166
+ resolved[key] = resolveSchemaReferences(value, allSchemas, visited);
167
+ }
168
+
169
+ return resolved;
170
+ }
171
+
172
+ // Example usage:
173
+ /*
174
+ const oasDoc = {
175
+ openapi: '3.1.0',
176
+ info: {
177
+ title: 'My API',
178
+ version: '1.0.0'
179
+ },
180
+ components: {
181
+ schemas: {
182
+ User: {
183
+ type: 'object',
184
+ properties: {
185
+ id: { type: 'integer' },
186
+ name: { type: 'string' },
187
+ email: { type: 'string', format: 'email' },
188
+ roles: {
189
+ type: 'array',
190
+ items: { $ref: '#/components/schemas/Role' }
191
+ }
192
+ },
193
+ required: ['id', 'name', 'email']
194
+ },
195
+ Role: {
196
+ type: 'object',
197
+ properties: {
198
+ id: { type: 'integer' },
199
+ name: { type: 'string' },
200
+ permissions: {
201
+ type: 'array',
202
+ items: { type: 'string' }
203
+ }
204
+ },
205
+ required: ['id', 'name']
206
+ },
207
+ UploadFileRequest: {
208
+ 'x-formData': true,
209
+ type: 'object',
210
+ properties: {
211
+ file: { type: 'string', format: 'binary' },
212
+ description: { type: 'string' }
213
+ }
214
+ }
215
+ }
216
+ }
217
+ };
218
+
219
+ const types = await oasToTypeScript(oasDoc);
220
+ console.log(types);
221
+ // Output will include:
222
+ // export interface User { ... }
223
+ // export interface Role { ... }
224
+ // export type UploadFileRequest = FormData;
225
+ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.0.1-draft.255",
3
+ "version": "0.0.1-draft.256",
4
4
  "bin": {
5
5
  "vovk": "./dist/index.mjs"
6
6
  },
@@ -60,6 +60,7 @@
60
60
  "glob": "^11.0.2",
61
61
  "gray-matter": "^4.0.3",
62
62
  "inflection": "^3.0.2",
63
+ "json-schema-to-typescript": "^15.0.4",
63
64
  "jsonc-parser": "^3.3.1",
64
65
  "lodash": "^4.17.21",
65
66
  "loglevel": "^1.9.2",
@@ -1,5 +0,0 @@
1
- import type { JSONSchema7 } from 'json-schema';
2
- export declare function convertJSONSchemaToTypeScriptDef(schema: JSONSchema7, defsPrefix: string): {
3
- $type: string;
4
- $defs: string;
5
- };
@@ -1,271 +0,0 @@
1
- export function convertJSONSchemaToTypeScriptDef(schema, defsPrefix) {
2
- if (!schema)
3
- return { $type: 'null', $defs: 'null' };
4
- // Helper function to escape single quotes in string literals
5
- const escapeStringLiteral = (str) => {
6
- return str.replace(/'/g, "\\'");
7
- };
8
- // Helper function to escape JSDoc comment closing sequences
9
- const escapeJSDocComment = (str) => {
10
- return str.replace(/\*\//g, '*\\/');
11
- };
12
- // Helper function to check if a property name is a valid JavaScript identifier
13
- const isValidIdentifier = (name) => {
14
- // Check if it matches valid JavaScript identifier pattern and is not a reserved word
15
- return (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) &&
16
- ![
17
- 'break',
18
- 'case',
19
- 'catch',
20
- 'class',
21
- 'const',
22
- 'continue',
23
- 'debugger',
24
- 'default',
25
- 'delete',
26
- 'do',
27
- 'else',
28
- 'export',
29
- 'extends',
30
- 'false',
31
- 'finally',
32
- 'for',
33
- 'function',
34
- 'if',
35
- 'import',
36
- 'in',
37
- 'instanceof',
38
- 'new',
39
- 'null',
40
- 'return',
41
- 'super',
42
- 'switch',
43
- 'this',
44
- 'throw',
45
- 'true',
46
- 'try',
47
- 'typeof',
48
- 'var',
49
- 'void',
50
- 'while',
51
- 'with',
52
- 'let',
53
- 'static',
54
- 'yield',
55
- 'enum',
56
- 'await',
57
- 'implements',
58
- 'interface',
59
- 'package',
60
- 'private',
61
- 'protected',
62
- 'public',
63
- ].includes(name));
64
- };
65
- // Helper function to format property name (with quotes if needed)
66
- const formatPropertyName = (name) => {
67
- if (isValidIdentifier(name)) {
68
- return name;
69
- }
70
- else {
71
- return `'${escapeStringLiteral(name)}'`;
72
- }
73
- };
74
- // Helper function to extract type name from $ref
75
- const getRefTypeName = (ref) => {
76
- if (ref.startsWith('#/definitions/') || ref.startsWith('#/$defs/')) {
77
- const path = ref.split('/');
78
- return `${defsPrefix}__${path[path.length - 1].replace(/[^a-zA-Z0-9_$]/g, '_')}`;
79
- }
80
- return 'KnownAny'; // Fallback for external references
81
- };
82
- // Helper function to get JSDoc from schema
83
- const getJSDoc = (schema, indentation = '') => {
84
- if (typeof schema === 'boolean') {
85
- return '';
86
- }
87
- const description = schema.description || schema.title;
88
- if (!description) {
89
- return '';
90
- }
91
- const safeDescription = escapeJSDocComment(description);
92
- return `${indentation}/**\n${indentation} * ${safeDescription}\n${indentation} */`;
93
- };
94
- // Helper function to convert schema to TypeScript type
95
- const schemaToType = (schema, indentation = ' ') => {
96
- if (!schema) {
97
- return 'null';
98
- }
99
- if (typeof schema === 'boolean') {
100
- return schema ? 'KnownAny' : 'never';
101
- }
102
- // Handle $ref references - check this first before other properties
103
- if (schema.$ref) {
104
- return getRefTypeName(schema.$ref);
105
- }
106
- if ('x-formData' in schema) {
107
- return `FormData`; // Special case for form data
108
- }
109
- if (schema.enum) {
110
- return schema.enum
111
- .map((value) => {
112
- if (typeof value === 'string') {
113
- return `'${escapeStringLiteral(value)}'`;
114
- }
115
- else if (value === null) {
116
- return 'null';
117
- }
118
- else {
119
- return String(value);
120
- }
121
- })
122
- .join(' | ');
123
- }
124
- if (schema.const !== undefined) {
125
- if (typeof schema.const === 'string') {
126
- return `'${escapeStringLiteral(schema.const)}'`;
127
- }
128
- else if (schema.const === null) {
129
- return 'null';
130
- }
131
- else {
132
- return String(schema.const);
133
- }
134
- }
135
- if (schema.oneOf) {
136
- return schema.oneOf.map((s) => schemaToType(s, indentation)).join(' | ');
137
- }
138
- if (schema.anyOf) {
139
- return schema.anyOf.map((s) => schemaToType(s, indentation)).join(' | ');
140
- }
141
- if (schema.allOf) {
142
- return schema.allOf.map((s) => schemaToType(s, indentation)).join(' & ');
143
- }
144
- if (schema.type === 'object' || schema.properties) {
145
- const properties = schema.properties || {};
146
- const required = schema.required || [];
147
- const propertyEntries = Object.entries(properties);
148
- if (propertyEntries.length === 0) {
149
- // Handle additional properties
150
- if (schema.additionalProperties) {
151
- if (typeof schema.additionalProperties === 'boolean') {
152
- return schema.additionalProperties ? 'Record<string, KnownAny>' : '{}';
153
- }
154
- else {
155
- const valueType = schemaToType(schema.additionalProperties, indentation);
156
- return `Record<string, ${valueType}>`;
157
- }
158
- }
159
- return '{}';
160
- }
161
- const props = propertyEntries
162
- .map(([propName, propSchema]) => {
163
- if (typeof propSchema === 'boolean') {
164
- const type = propSchema ? 'KnownAny' : 'never';
165
- const isOptional = !required.includes(propName);
166
- const jsDoc = getJSDoc(propSchema, indentation);
167
- return `${jsDoc}\n${indentation}${formatPropertyName(propName)}${isOptional ? '?' : ''}: ${type};`;
168
- }
169
- const isOptional = !required.includes(propName);
170
- const defaultValue = propSchema.default !== undefined ? ` // default: ${JSON.stringify(propSchema.default)}` : '';
171
- const jsDoc = getJSDoc(propSchema, indentation);
172
- const propType = schemaToType(propSchema, indentation + ' ');
173
- return [
174
- `${jsDoc}`,
175
- `${indentation}${formatPropertyName(propName)}${isOptional ? '?' : ''}: ${propType};${defaultValue}`,
176
- ]
177
- .filter(Boolean)
178
- .join('\n');
179
- })
180
- .join('\n');
181
- // Handle additional properties
182
- let additionalPropsType = '';
183
- if (schema.additionalProperties) {
184
- if (typeof schema.additionalProperties === 'boolean') {
185
- if (schema.additionalProperties) {
186
- additionalPropsType = `\n${indentation}[key: string]: KnownAny;`;
187
- }
188
- }
189
- else {
190
- const valueType = schemaToType(schema.additionalProperties, indentation + ' ');
191
- additionalPropsType = `\n${indentation}[key: string]: ${valueType};`;
192
- }
193
- }
194
- return `{\n${props}${additionalPropsType}\n${indentation.slice(2)}}`;
195
- }
196
- if (schema.type === 'array' && schema.items) {
197
- if (Array.isArray(schema.items)) {
198
- // Tuple
199
- const tupleTypes = schema.items.map((item) => schemaToType(item, indentation));
200
- let tupleType = `[${tupleTypes.join(', ')}]`;
201
- // Handle additional items
202
- if (schema.additionalItems === true) {
203
- tupleType += ' & KnownAny[]';
204
- }
205
- else if (typeof schema.additionalItems === 'object') {
206
- const additionalType = schemaToType(schema.additionalItems, indentation);
207
- tupleType += ` & ${additionalType}[]`;
208
- }
209
- return tupleType;
210
- }
211
- else {
212
- // Array
213
- return `${schemaToType(schema.items, indentation)}[]`;
214
- }
215
- }
216
- // Handle multiple types
217
- if (Array.isArray(schema.type)) {
218
- return schema.type
219
- .map((t) => {
220
- const singleTypeSchema = { ...schema, type: t };
221
- singleTypeSchema.type = t;
222
- return schemaToType(singleTypeSchema, indentation);
223
- })
224
- .join(' | ');
225
- }
226
- // Handle primitive types
227
- switch (schema.type) {
228
- case 'string':
229
- return 'string';
230
- case 'number':
231
- case 'integer':
232
- return 'number';
233
- case 'boolean':
234
- return 'boolean';
235
- case 'null':
236
- return 'null';
237
- case 'array':
238
- return 'KnownAny[]'; // For arrays with no items defined
239
- default:
240
- return 'undefined';
241
- }
242
- };
243
- const defsToType = (defs) => {
244
- return Object.entries(defs)
245
- .map(([defName, defSchema]) => {
246
- if (defSchema) {
247
- const jsDoc = getJSDoc(defSchema);
248
- const defType = schemaToType(defSchema);
249
- return [jsDoc, `type ${defsPrefix}__${defName.replace(/[^a-zA-Z0-9_$]/g, '_')} = ${defType};`]
250
- .filter(Boolean)
251
- .join('\n');
252
- }
253
- return '';
254
- })
255
- .filter(Boolean)
256
- .join('\n');
257
- };
258
- // Process definitions from both $defs and definitions properties
259
- const allDefs = {
260
- ...(schema.definitions || {}),
261
- ...(schema.$defs || {}),
262
- };
263
- // Generate the main type
264
- const jsDoc = getJSDoc(schema);
265
- const mainType = schemaToType(schema);
266
- const defsType = defsToType(allDefs);
267
- return {
268
- $type: jsDoc ? `${jsDoc}\n${mainType}` : mainType,
269
- $defs: defsType,
270
- };
271
- }