zod-codegen 1.0.3 → 1.1.1
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 +13 -0
- package/CONTRIBUTING.md +1 -1
- package/EXAMPLES.md +730 -0
- package/PERFORMANCE.md +59 -0
- package/README.md +272 -58
- package/dist/src/services/code-generator.service.js +249 -45
- package/dist/src/types/openapi.js +1 -1
- package/dist/tests/unit/code-generator.test.js +290 -0
- package/dist/tests/unit/file-reader.test.js +110 -0
- package/dist/tests/unit/generator.test.js +77 -7
- package/eslint.config.mjs +1 -0
- package/examples/.gitkeep +3 -0
- package/examples/README.md +74 -0
- package/examples/petstore/README.md +121 -0
- package/examples/petstore/authenticated-usage.ts +60 -0
- package/examples/petstore/basic-usage.ts +51 -0
- package/examples/petstore/server-variables-usage.ts +63 -0
- package/examples/petstore/type.ts +217 -0
- package/examples/pokeapi/README.md +105 -0
- package/examples/pokeapi/basic-usage.ts +57 -0
- package/examples/pokeapi/custom-client.ts +56 -0
- package/examples/pokeapi/type.ts +109 -0
- package/package.json +4 -2
- package/samples/pokeapi-openapi.json +212 -0
- package/samples/server-variables-example.yaml +49 -0
- package/src/services/code-generator.service.ts +707 -88
- package/src/types/openapi.ts +1 -1
- package/tests/unit/code-generator.test.ts +321 -0
- package/tests/unit/file-reader.test.ts +134 -0
- package/tests/unit/generator.test.ts +99 -7
- package/tsconfig.examples.json +17 -0
- package/tsconfig.json +1 -1
|
@@ -40,14 +40,14 @@ export class TypeScriptCodeGeneratorService {
|
|
|
40
40
|
buildAST(openapi) {
|
|
41
41
|
const imports = this.importBuilder.buildImports();
|
|
42
42
|
const schemas = this.buildSchemas(openapi);
|
|
43
|
+
const serverConfig = this.buildServerConfiguration(openapi);
|
|
43
44
|
const clientClass = this.buildClientClass(openapi, schemas);
|
|
44
|
-
const baseUrlConstant = this.buildBaseUrlConstant(openapi);
|
|
45
45
|
return [
|
|
46
46
|
this.createComment('Imports'),
|
|
47
47
|
...imports,
|
|
48
48
|
this.createComment('Components schemas'),
|
|
49
49
|
...Object.values(schemas),
|
|
50
|
-
...
|
|
50
|
+
...serverConfig,
|
|
51
51
|
this.createComment('Client class'),
|
|
52
52
|
clientClass,
|
|
53
53
|
];
|
|
@@ -73,24 +73,54 @@ export class TypeScriptCodeGeneratorService {
|
|
|
73
73
|
const methods = this.buildClientMethods(openapi, schemas);
|
|
74
74
|
return ts.factory.createClassDeclaration([ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(clientName), undefined, undefined, [
|
|
75
75
|
this.typeBuilder.createProperty('#baseUrl', 'string', true),
|
|
76
|
-
this.buildConstructor(),
|
|
76
|
+
this.buildConstructor(openapi),
|
|
77
|
+
this.buildGetBaseRequestOptionsMethod(),
|
|
77
78
|
this.buildHttpRequestMethod(),
|
|
78
79
|
...methods,
|
|
79
80
|
]);
|
|
80
81
|
}
|
|
81
|
-
buildConstructor() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
buildConstructor(openapi) {
|
|
83
|
+
const hasServers = openapi.servers && openapi.servers.length > 0;
|
|
84
|
+
if (hasServers) {
|
|
85
|
+
// Options-based constructor
|
|
86
|
+
return ts.factory.createConstructorDeclaration(undefined, [
|
|
87
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier('options'), undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('ClientOptions'), undefined), undefined),
|
|
88
|
+
], ts.factory.createBlock([
|
|
89
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
90
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('resolvedUrl'), undefined, undefined, ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('baseUrl')), ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), ts.factory.createNull()), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('baseUrl')), ts.factory.createToken(ts.SyntaxKind.ColonToken), ts.factory.createCallExpression(ts.factory.createIdentifier('resolveServerUrl'), undefined, [
|
|
91
|
+
ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('serverIndex')),
|
|
92
|
+
ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('serverVariables')),
|
|
93
|
+
]))),
|
|
94
|
+
], ts.NodeFlags.Const)),
|
|
95
|
+
ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createPrivateIdentifier('#baseUrl')), ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createIdentifier('resolvedUrl'))),
|
|
96
|
+
], true));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Fallback: simple baseUrl parameter
|
|
100
|
+
return ts.factory.createConstructorDeclaration(undefined, [
|
|
101
|
+
this.typeBuilder.createParameter('baseUrl', 'string', ts.factory.createStringLiteral('/', true)),
|
|
102
|
+
this.typeBuilder.createParameter('_', 'unknown', undefined, true),
|
|
103
|
+
], ts.factory.createBlock([
|
|
104
|
+
ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createPrivateIdentifier('#baseUrl')), ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createIdentifier('baseUrl'))),
|
|
105
|
+
], true));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
buildGetBaseRequestOptionsMethod() {
|
|
109
|
+
return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.ProtectedKeyword)], undefined, ts.factory.createIdentifier('getBaseRequestOptions'), undefined, undefined, [], ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Partial'), [
|
|
110
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Omit'), [
|
|
111
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('RequestInit'), undefined),
|
|
112
|
+
ts.factory.createUnionTypeNode([
|
|
113
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('method', true)),
|
|
114
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('body', true)),
|
|
115
|
+
]),
|
|
116
|
+
]),
|
|
117
|
+
]), ts.factory.createBlock([ts.factory.createReturnStatement(ts.factory.createObjectLiteralExpression([], false))], true));
|
|
88
118
|
}
|
|
89
119
|
buildHttpRequestMethod() {
|
|
90
120
|
return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.createPrivateIdentifier('#makeRequest'), undefined, [this.typeBuilder.createGenericType('T')], [
|
|
91
121
|
this.typeBuilder.createParameter('method', 'string'),
|
|
92
122
|
this.typeBuilder.createParameter('path', 'string'),
|
|
93
|
-
this.typeBuilder.createParameter('options', '{params?: Record<string, string | number | boolean>; data?: unknown; contentType?: string}', ts.factory.createObjectLiteralExpression([], false), false),
|
|
123
|
+
this.typeBuilder.createParameter('options', '{params?: Record<string, string | number | boolean>; data?: unknown; contentType?: string; headers?: Record<string, string>}', ts.factory.createObjectLiteralExpression([], false), false),
|
|
94
124
|
], ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
|
|
95
125
|
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('T'), undefined),
|
|
96
126
|
]), ts.factory.createBlock([
|
|
@@ -132,11 +162,27 @@ export class TypeScriptCodeGeneratorService {
|
|
|
132
162
|
ts.factory.createIdentifier('baseUrl'),
|
|
133
163
|
]), ts.factory.createIdentifier('toString')), undefined, []))),
|
|
134
164
|
], ts.NodeFlags.Const)),
|
|
135
|
-
//
|
|
165
|
+
// Get base request options (headers, signal, credentials, etc.)
|
|
136
166
|
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
137
|
-
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('
|
|
138
|
-
|
|
139
|
-
|
|
167
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('baseOptions'), undefined, undefined, ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier('getBaseRequestOptions')), undefined, [])),
|
|
168
|
+
], ts.NodeFlags.Const)),
|
|
169
|
+
// Build Content-Type header
|
|
170
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
171
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('contentType'), undefined, undefined, ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('contentType')), ts.factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.factory.createStringLiteral('application/x-www-form-urlencoded', true)), undefined, ts.factory.createStringLiteral('application/x-www-form-urlencoded', true), undefined, ts.factory.createStringLiteral('application/json', true))),
|
|
172
|
+
], ts.NodeFlags.Const)),
|
|
173
|
+
// Merge headers: base headers, Content-Type, and request-specific headers
|
|
174
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
175
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('baseHeaders'), undefined, undefined, ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('baseOptions'), ts.factory.createIdentifier('headers')), ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), ts.factory.createIdentifier('undefined')), undefined, ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('baseOptions'), ts.factory.createIdentifier('headers')), undefined, ts.factory.createObjectLiteralExpression([], false))),
|
|
176
|
+
], ts.NodeFlags.Const)),
|
|
177
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
178
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('headers'), undefined, undefined, ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('Object'), ts.factory.createIdentifier('assign')), undefined, [
|
|
179
|
+
ts.factory.createObjectLiteralExpression([], false),
|
|
180
|
+
ts.factory.createIdentifier('baseHeaders'),
|
|
181
|
+
ts.factory.createObjectLiteralExpression([
|
|
182
|
+
ts.factory.createPropertyAssignment(ts.factory.createStringLiteral('Content-Type', true), ts.factory.createIdentifier('contentType')),
|
|
183
|
+
], false),
|
|
184
|
+
ts.factory.createConditionalExpression(ts.factory.createBinaryExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('headers')), ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), ts.factory.createIdentifier('undefined')), undefined, ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('headers')), undefined, ts.factory.createObjectLiteralExpression([], false)),
|
|
185
|
+
])),
|
|
140
186
|
], ts.NodeFlags.Const)),
|
|
141
187
|
// Build body with form-urlencoded support
|
|
142
188
|
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
@@ -168,15 +214,19 @@ export class TypeScriptCodeGeneratorService {
|
|
|
168
214
|
ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('options'), ts.factory.createIdentifier('data')),
|
|
169
215
|
])), undefined, ts.factory.createNull())),
|
|
170
216
|
], ts.NodeFlags.Const)),
|
|
171
|
-
// Make fetch request
|
|
217
|
+
// Make fetch request: merge base options with method, headers, and body
|
|
172
218
|
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
173
219
|
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('response'), undefined, undefined, ts.factory.createAwaitExpression(ts.factory.createCallExpression(ts.factory.createIdentifier('fetch'), undefined, [
|
|
174
220
|
ts.factory.createIdentifier('url'),
|
|
175
|
-
ts.factory.
|
|
176
|
-
ts.factory.
|
|
177
|
-
ts.factory.
|
|
178
|
-
ts.factory.
|
|
179
|
-
|
|
221
|
+
ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('Object'), ts.factory.createIdentifier('assign')), undefined, [
|
|
222
|
+
ts.factory.createObjectLiteralExpression([], false),
|
|
223
|
+
ts.factory.createIdentifier('baseOptions'),
|
|
224
|
+
ts.factory.createObjectLiteralExpression([
|
|
225
|
+
ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier('method'), undefined),
|
|
226
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier('headers'), ts.factory.createIdentifier('headers')),
|
|
227
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier('body'), ts.factory.createIdentifier('body')),
|
|
228
|
+
], false),
|
|
229
|
+
]),
|
|
180
230
|
]))),
|
|
181
231
|
], ts.NodeFlags.Const)),
|
|
182
232
|
// Check response status
|
|
@@ -462,16 +512,151 @@ export class TypeScriptCodeGeneratorService {
|
|
|
462
512
|
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined),
|
|
463
513
|
]);
|
|
464
514
|
}
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
if (!
|
|
515
|
+
buildServerConfiguration(openapi) {
|
|
516
|
+
const servers = openapi.servers;
|
|
517
|
+
if (!servers || servers.length === 0) {
|
|
468
518
|
return [];
|
|
469
519
|
}
|
|
470
|
-
|
|
520
|
+
const statements = [];
|
|
521
|
+
// Build server configuration array
|
|
522
|
+
const serverConfigElements = servers.map((server) => {
|
|
523
|
+
const properties = [
|
|
524
|
+
ts.factory.createPropertyAssignment('url', ts.factory.createStringLiteral(server.url, true)),
|
|
525
|
+
];
|
|
526
|
+
if (server.description) {
|
|
527
|
+
properties.push(ts.factory.createPropertyAssignment('description', ts.factory.createStringLiteral(server.description, true)));
|
|
528
|
+
}
|
|
529
|
+
if (server.variables && Object.keys(server.variables).length > 0) {
|
|
530
|
+
const variableProperties = Object.entries(server.variables).map(([varName, varDef]) => {
|
|
531
|
+
const varProps = [
|
|
532
|
+
ts.factory.createPropertyAssignment('default', ts.factory.createStringLiteral(varDef.default, true)),
|
|
533
|
+
];
|
|
534
|
+
if (varDef.enum && varDef.enum.length > 0) {
|
|
535
|
+
varProps.push(ts.factory.createPropertyAssignment('enum', ts.factory.createArrayLiteralExpression(varDef.enum.map((val) => ts.factory.createStringLiteral(val, true)), false)));
|
|
536
|
+
}
|
|
537
|
+
if (varDef.description) {
|
|
538
|
+
varProps.push(ts.factory.createPropertyAssignment('description', ts.factory.createStringLiteral(varDef.description, true)));
|
|
539
|
+
}
|
|
540
|
+
return ts.factory.createPropertyAssignment(varName, ts.factory.createObjectLiteralExpression(varProps, true));
|
|
541
|
+
});
|
|
542
|
+
properties.push(ts.factory.createPropertyAssignment('variables', ts.factory.createObjectLiteralExpression(variableProperties, true)));
|
|
543
|
+
}
|
|
544
|
+
return ts.factory.createObjectLiteralExpression(properties, true);
|
|
545
|
+
});
|
|
546
|
+
// Export server configuration
|
|
547
|
+
statements.push(ts.factory.createVariableStatement([ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
548
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('serverConfigurations'), undefined, undefined, ts.factory.createArrayLiteralExpression(serverConfigElements, false)),
|
|
549
|
+
], ts.NodeFlags.Const)));
|
|
550
|
+
// Export default base URL (first server with default variables)
|
|
551
|
+
const firstServer = servers[0];
|
|
552
|
+
const defaultBaseUrl = firstServer ? this.resolveServerUrl(firstServer, {}) : '/';
|
|
553
|
+
statements.push(ts.factory.createVariableStatement([ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
554
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('defaultBaseUrl'), undefined, undefined, ts.factory.createStringLiteral(defaultBaseUrl, true)),
|
|
555
|
+
], ts.NodeFlags.Const)));
|
|
556
|
+
// Build ClientOptions type
|
|
557
|
+
const optionProperties = [
|
|
558
|
+
ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier('baseUrl'), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)),
|
|
559
|
+
ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier('serverIndex'), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
|
|
560
|
+
ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier('serverVariables'), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
|
|
561
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
562
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
563
|
+
])),
|
|
564
|
+
];
|
|
565
|
+
statements.push(ts.factory.createTypeAliasDeclaration([ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier('ClientOptions'), undefined, ts.factory.createTypeLiteralNode(optionProperties)));
|
|
566
|
+
// Build resolveServerUrl helper function
|
|
567
|
+
statements.push(this.buildResolveServerUrlFunction(servers));
|
|
568
|
+
return statements;
|
|
569
|
+
}
|
|
570
|
+
resolveServerUrl(server, variables) {
|
|
571
|
+
let url = server.url;
|
|
572
|
+
if (server.variables) {
|
|
573
|
+
for (const [varName, varDef] of Object.entries(server.variables)) {
|
|
574
|
+
const value = variables[varName] ?? varDef.default;
|
|
575
|
+
url = url.replace(`{${varName}}`, value);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return url;
|
|
579
|
+
}
|
|
580
|
+
buildResolveServerUrlFunction(servers) {
|
|
581
|
+
// Build server configs array inline
|
|
582
|
+
const serverConfigElements = servers.map((server) => {
|
|
583
|
+
const properties = [
|
|
584
|
+
ts.factory.createPropertyAssignment('url', ts.factory.createStringLiteral(server.url, true)),
|
|
585
|
+
];
|
|
586
|
+
if (server.variables && Object.keys(server.variables).length > 0) {
|
|
587
|
+
const variableProperties = Object.entries(server.variables).map(([varName, varDef]) => {
|
|
588
|
+
const varProps = [
|
|
589
|
+
ts.factory.createPropertyAssignment('default', ts.factory.createStringLiteral(varDef.default, true)),
|
|
590
|
+
];
|
|
591
|
+
if (varDef.enum && varDef.enum.length > 0) {
|
|
592
|
+
varProps.push(ts.factory.createPropertyAssignment('enum', ts.factory.createArrayLiteralExpression(varDef.enum.map((val) => ts.factory.createStringLiteral(val, true)), false)));
|
|
593
|
+
}
|
|
594
|
+
return ts.factory.createPropertyAssignment(varName, ts.factory.createObjectLiteralExpression(varProps, true));
|
|
595
|
+
});
|
|
596
|
+
properties.push(ts.factory.createPropertyAssignment('variables', ts.factory.createObjectLiteralExpression(variableProperties, true)));
|
|
597
|
+
}
|
|
598
|
+
return ts.factory.createObjectLiteralExpression(properties, true);
|
|
599
|
+
});
|
|
600
|
+
// Build function body - simplified version
|
|
601
|
+
const idx = ts.factory.createIdentifier('idx');
|
|
602
|
+
const configs = ts.factory.createIdentifier('configs');
|
|
603
|
+
const config = ts.factory.createIdentifier('config');
|
|
604
|
+
const url = ts.factory.createIdentifier('url');
|
|
605
|
+
const key = ts.factory.createIdentifier('key');
|
|
606
|
+
const value = ts.factory.createIdentifier('value');
|
|
607
|
+
const bodyStatements = [
|
|
608
|
+
// const configs = [...]
|
|
609
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
610
|
+
ts.factory.createVariableDeclaration(configs, undefined, undefined, ts.factory.createArrayLiteralExpression(serverConfigElements, false)),
|
|
611
|
+
], ts.NodeFlags.Const)),
|
|
612
|
+
// const idx = serverIndex ?? 0
|
|
471
613
|
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
472
|
-
ts.factory.createVariableDeclaration(ts.factory.createIdentifier('
|
|
614
|
+
ts.factory.createVariableDeclaration(idx, undefined, undefined, ts.factory.createBinaryExpression(ts.factory.createIdentifier('serverIndex'), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), ts.factory.createNumericLiteral('0'))),
|
|
473
615
|
], ts.NodeFlags.Const)),
|
|
616
|
+
// if (idx < configs.length) { ... }
|
|
617
|
+
ts.factory.createIfStatement(ts.factory.createBinaryExpression(idx, ts.factory.createToken(ts.SyntaxKind.LessThanToken), ts.factory.createPropertyAccessExpression(configs, ts.factory.createIdentifier('length'))), ts.factory.createBlock([
|
|
618
|
+
// const config = configs[idx]
|
|
619
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
620
|
+
ts.factory.createVariableDeclaration(config, undefined, undefined, ts.factory.createElementAccessExpression(configs, idx)),
|
|
621
|
+
], ts.NodeFlags.Const)),
|
|
622
|
+
// let url = config.url
|
|
623
|
+
ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
624
|
+
ts.factory.createVariableDeclaration(url, undefined, undefined, ts.factory.createPropertyAccessExpression(config, ts.factory.createIdentifier('url'))),
|
|
625
|
+
], ts.NodeFlags.Let)),
|
|
626
|
+
// if (config.variables && serverVariables) { ... }
|
|
627
|
+
ts.factory.createIfStatement(ts.factory.createLogicalAnd(ts.factory.createPropertyAccessExpression(config, ts.factory.createIdentifier('variables')), ts.factory.createIdentifier('serverVariables')), ts.factory.createBlock([
|
|
628
|
+
// for (const [key, value] of Object.entries(serverVariables)) { url = url.replace(...) }
|
|
629
|
+
ts.factory.createForOfStatement(undefined, ts.factory.createVariableDeclarationList([
|
|
630
|
+
ts.factory.createVariableDeclaration(ts.factory.createArrayBindingPattern([
|
|
631
|
+
ts.factory.createBindingElement(undefined, undefined, key),
|
|
632
|
+
ts.factory.createBindingElement(undefined, undefined, value),
|
|
633
|
+
]), undefined, undefined),
|
|
634
|
+
], ts.NodeFlags.Const), ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('Object'), ts.factory.createIdentifier('entries')), undefined, [ts.factory.createIdentifier('serverVariables')]), ts.factory.createBlock([
|
|
635
|
+
ts.factory.createExpressionStatement(ts.factory.createBinaryExpression(url, ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(url, ts.factory.createIdentifier('replace')), undefined, [
|
|
636
|
+
ts.factory.createNewExpression(ts.factory.createIdentifier('RegExp'), undefined, [
|
|
637
|
+
ts.factory.createBinaryExpression(ts.factory.createBinaryExpression(ts.factory.createStringLiteral('\\{'), ts.factory.createToken(ts.SyntaxKind.PlusToken), key), ts.factory.createToken(ts.SyntaxKind.PlusToken), ts.factory.createStringLiteral('\\}')),
|
|
638
|
+
ts.factory.createStringLiteral('g'),
|
|
639
|
+
]),
|
|
640
|
+
value,
|
|
641
|
+
]))),
|
|
642
|
+
], true)),
|
|
643
|
+
], true)),
|
|
644
|
+
// return url
|
|
645
|
+
ts.factory.createReturnStatement(url),
|
|
646
|
+
], true)),
|
|
647
|
+
// return default (first server with defaults)
|
|
648
|
+
ts.factory.createReturnStatement(ts.factory.createStringLiteral(servers[0] ? this.resolveServerUrl(servers[0], {}) : '/', true)),
|
|
474
649
|
];
|
|
650
|
+
return ts.factory.createFunctionDeclaration(undefined, undefined, ts.factory.createIdentifier('resolveServerUrl'), undefined, [
|
|
651
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier('serverIndex'), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createUnionTypeNode([
|
|
652
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
653
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
|
|
654
|
+
]), undefined),
|
|
655
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier('serverVariables'), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
|
|
656
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
657
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
658
|
+
]), ts.factory.createObjectLiteralExpression([], false)),
|
|
659
|
+
], ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ts.factory.createBlock(bodyStatements, true));
|
|
475
660
|
}
|
|
476
661
|
generateClientName(title) {
|
|
477
662
|
return title
|
|
@@ -523,26 +708,45 @@ export class TypeScriptCodeGeneratorService {
|
|
|
523
708
|
}
|
|
524
709
|
// Handle enum
|
|
525
710
|
if (prop['enum'] && Array.isArray(prop['enum']) && prop['enum'].length > 0) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
711
|
+
// Check if all enum values are strings (z.enum only works with strings)
|
|
712
|
+
const allStrings = prop['enum'].every((val) => typeof val === 'string');
|
|
713
|
+
if (allStrings) {
|
|
714
|
+
// Use z.enum() for string enums
|
|
715
|
+
const enumValues = prop['enum'].map((val) => ts.factory.createStringLiteral(val, true));
|
|
716
|
+
const enumExpression = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('enum')), undefined, [ts.factory.createArrayLiteralExpression(enumValues, false)]);
|
|
717
|
+
return required
|
|
718
|
+
? enumExpression
|
|
719
|
+
: ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(enumExpression, ts.factory.createIdentifier('optional')), undefined, []);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
// Use z.union([z.literal(...), ...]) for numeric/boolean/mixed enums
|
|
723
|
+
const literalSchemas = prop['enum'].map((val) => {
|
|
724
|
+
let literalValue;
|
|
725
|
+
if (typeof val === 'string') {
|
|
726
|
+
literalValue = ts.factory.createStringLiteral(val, true);
|
|
534
727
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
728
|
+
else if (typeof val === 'number') {
|
|
729
|
+
// Handle negative numbers correctly
|
|
730
|
+
if (val < 0) {
|
|
731
|
+
literalValue = ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral(String(Math.abs(val))));
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
literalValue = ts.factory.createNumericLiteral(String(val));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
else if (typeof val === 'boolean') {
|
|
738
|
+
literalValue = val ? ts.factory.createTrue() : ts.factory.createFalse();
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
literalValue = ts.factory.createStringLiteral(String(val), true);
|
|
742
|
+
}
|
|
743
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('literal')), undefined, [literalValue]);
|
|
744
|
+
});
|
|
745
|
+
const unionExpression = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('union')), undefined, [ts.factory.createArrayLiteralExpression(literalSchemas, false)]);
|
|
746
|
+
return required
|
|
747
|
+
? unionExpression
|
|
748
|
+
: ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(unionExpression, ts.factory.createIdentifier('optional')), undefined, []);
|
|
749
|
+
}
|
|
546
750
|
}
|
|
547
751
|
switch (prop['type']) {
|
|
548
752
|
case 'array': {
|
|
@@ -54,7 +54,7 @@ const ServerVariable = z.object({
|
|
|
54
54
|
enum: z.array(z.string()).optional(),
|
|
55
55
|
});
|
|
56
56
|
const Server = z.object({
|
|
57
|
-
url: z.
|
|
57
|
+
url: z.string(), // Allow templated URLs with {variables}
|
|
58
58
|
description: z.string().optional(),
|
|
59
59
|
variables: z.record(z.string(), ServerVariable).optional(),
|
|
60
60
|
});
|