@kubb/plugin-ts 5.0.0-beta.3 → 5.0.0-beta.30
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/README.md +26 -5
- package/dist/index.cjs +343 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +81 -58
- package/dist/index.js +342 -110
- package/dist/index.js.map +1 -1
- package/extension.yaml +1078 -0
- package/package.json +11 -13
- package/src/components/Enum.tsx +3 -3
- package/src/factory.ts +23 -20
- package/src/generators/typeGenerator.tsx +101 -29
- package/src/plugin.ts +18 -9
- package/src/printers/printerTs.ts +31 -30
- package/src/resolvers/resolverTs.ts +28 -25
- package/src/types.ts +44 -37
- package/src/utils.ts +23 -13
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import {
|
|
3
|
-
import { File,
|
|
1
|
+
import { t as __name } from "./chunk--u3MIqq1.js";
|
|
2
|
+
import { parserTs } from "@kubb/parser-ts";
|
|
3
|
+
import { File, jsxRendererSync } from "@kubb/renderer-jsx";
|
|
4
4
|
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
5
5
|
import { isNumber } from "remeda";
|
|
6
6
|
import ts from "typescript";
|
|
@@ -141,6 +141,129 @@ function stringify(value) {
|
|
|
141
141
|
return JSON.stringify(trimQuotes(value.toString()));
|
|
142
142
|
}
|
|
143
143
|
//#endregion
|
|
144
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
145
|
+
/**
|
|
146
|
+
* JavaScript and Java reserved words.
|
|
147
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
148
|
+
*/
|
|
149
|
+
const reservedWords = new Set([
|
|
150
|
+
"abstract",
|
|
151
|
+
"arguments",
|
|
152
|
+
"boolean",
|
|
153
|
+
"break",
|
|
154
|
+
"byte",
|
|
155
|
+
"case",
|
|
156
|
+
"catch",
|
|
157
|
+
"char",
|
|
158
|
+
"class",
|
|
159
|
+
"const",
|
|
160
|
+
"continue",
|
|
161
|
+
"debugger",
|
|
162
|
+
"default",
|
|
163
|
+
"delete",
|
|
164
|
+
"do",
|
|
165
|
+
"double",
|
|
166
|
+
"else",
|
|
167
|
+
"enum",
|
|
168
|
+
"eval",
|
|
169
|
+
"export",
|
|
170
|
+
"extends",
|
|
171
|
+
"false",
|
|
172
|
+
"final",
|
|
173
|
+
"finally",
|
|
174
|
+
"float",
|
|
175
|
+
"for",
|
|
176
|
+
"function",
|
|
177
|
+
"goto",
|
|
178
|
+
"if",
|
|
179
|
+
"implements",
|
|
180
|
+
"import",
|
|
181
|
+
"in",
|
|
182
|
+
"instanceof",
|
|
183
|
+
"int",
|
|
184
|
+
"interface",
|
|
185
|
+
"let",
|
|
186
|
+
"long",
|
|
187
|
+
"native",
|
|
188
|
+
"new",
|
|
189
|
+
"null",
|
|
190
|
+
"package",
|
|
191
|
+
"private",
|
|
192
|
+
"protected",
|
|
193
|
+
"public",
|
|
194
|
+
"return",
|
|
195
|
+
"short",
|
|
196
|
+
"static",
|
|
197
|
+
"super",
|
|
198
|
+
"switch",
|
|
199
|
+
"synchronized",
|
|
200
|
+
"this",
|
|
201
|
+
"throw",
|
|
202
|
+
"throws",
|
|
203
|
+
"transient",
|
|
204
|
+
"true",
|
|
205
|
+
"try",
|
|
206
|
+
"typeof",
|
|
207
|
+
"var",
|
|
208
|
+
"void",
|
|
209
|
+
"volatile",
|
|
210
|
+
"while",
|
|
211
|
+
"with",
|
|
212
|
+
"yield",
|
|
213
|
+
"Array",
|
|
214
|
+
"Date",
|
|
215
|
+
"hasOwnProperty",
|
|
216
|
+
"Infinity",
|
|
217
|
+
"isFinite",
|
|
218
|
+
"isNaN",
|
|
219
|
+
"isPrototypeOf",
|
|
220
|
+
"length",
|
|
221
|
+
"Math",
|
|
222
|
+
"name",
|
|
223
|
+
"NaN",
|
|
224
|
+
"Number",
|
|
225
|
+
"Object",
|
|
226
|
+
"prototype",
|
|
227
|
+
"String",
|
|
228
|
+
"toString",
|
|
229
|
+
"undefined",
|
|
230
|
+
"valueOf"
|
|
231
|
+
]);
|
|
232
|
+
/**
|
|
233
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* isValidVarName('status') // true
|
|
238
|
+
* isValidVarName('class') // false (reserved word)
|
|
239
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
function isValidVarName(name) {
|
|
243
|
+
if (!name || reservedWords.has(name)) return false;
|
|
244
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
248
|
+
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
249
|
+
*
|
|
250
|
+
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
251
|
+
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
252
|
+
* variable, type, or function names.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* ensureValidVarName('409') // '_409'
|
|
257
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
258
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
259
|
+
* ensureValidVarName('class') // '_class'
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
function ensureValidVarName(name) {
|
|
263
|
+
if (!name || isValidVarName(name)) return name;
|
|
264
|
+
return `_${name}`;
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
144
267
|
//#region src/constants.ts
|
|
145
268
|
/**
|
|
146
269
|
* `optionalType` values that cause a property's type to include `| undefined`.
|
|
@@ -203,6 +326,10 @@ const syntaxKind = {
|
|
|
203
326
|
literalType: SyntaxKind.LiteralType,
|
|
204
327
|
stringLiteral: SyntaxKind.StringLiteral
|
|
205
328
|
};
|
|
329
|
+
function isNonNullable$1(value) {
|
|
330
|
+
return value !== null && value !== void 0;
|
|
331
|
+
}
|
|
332
|
+
__name(isNonNullable$1, "isNonNullable");
|
|
206
333
|
function isValidIdentifier(str) {
|
|
207
334
|
if (!str.length || str.trim() !== str) return false;
|
|
208
335
|
const node = ts.parseIsolatedEntityName(str, ts.ScriptTarget.Latest);
|
|
@@ -272,7 +399,7 @@ function createUnionDeclaration({ nodes, withParentheses }) {
|
|
|
272
399
|
* Supports optional markers, readonly modifiers, and type annotations.
|
|
273
400
|
*/
|
|
274
401
|
function createPropertySignature({ readOnly, modifiers = [], name, questionToken, type }) {
|
|
275
|
-
return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : void 0].filter(
|
|
402
|
+
return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : void 0].filter((modifier) => modifier !== void 0), propertyName(name), createQuestionToken(questionToken), type);
|
|
276
403
|
}
|
|
277
404
|
/**
|
|
278
405
|
* Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
|
|
@@ -370,8 +497,8 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
370
497
|
}
|
|
371
498
|
if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
|
|
372
499
|
if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
|
|
373
|
-
}).filter(
|
|
374
|
-
if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter(
|
|
500
|
+
}).filter((node) => node !== void 0)))];
|
|
501
|
+
if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value]) => {
|
|
375
502
|
let initializer = factory.createStringLiteral(value?.toString());
|
|
376
503
|
if (Number.parseInt(value.toString(), 10) === value && isNumber(Number.parseInt(value.toString(), 10))) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
|
|
377
504
|
else initializer = factory.createNumericLiteral(value);
|
|
@@ -384,7 +511,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
384
511
|
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
|
|
385
512
|
return factory.createEnumMember(propertyName(casingKey), initializer);
|
|
386
513
|
}
|
|
387
|
-
}).filter(
|
|
514
|
+
}).filter((member) => member !== void 0))];
|
|
388
515
|
const identifierName = name;
|
|
389
516
|
if (enums.length === 0) return [void 0, factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))];
|
|
390
517
|
return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value]) => {
|
|
@@ -396,7 +523,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
396
523
|
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
|
|
397
524
|
return factory.createPropertyAssignment(propertyName(casingKey), initializer);
|
|
398
525
|
}
|
|
399
|
-
}).filter(
|
|
526
|
+
}).filter((property) => property !== void 0), true), factory.createTypeReferenceNode(factory.createIdentifier("const"), void 0)))], ts.NodeFlags.Const)), factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0)), factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0))))];
|
|
400
527
|
}
|
|
401
528
|
/**
|
|
402
529
|
* Creates a TypeScript `Omit<T, Keys>` type reference node.
|
|
@@ -536,14 +663,14 @@ function dateOrStringNode(node) {
|
|
|
536
663
|
* Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
|
|
537
664
|
*/
|
|
538
665
|
function buildMemberNodes(members, print) {
|
|
539
|
-
return (members ?? []).map(print).filter(
|
|
666
|
+
return (members ?? []).map(print).filter(isNonNullable$1);
|
|
540
667
|
}
|
|
541
668
|
/**
|
|
542
669
|
* Builds a TypeScript tuple type node from an array schema's `items`,
|
|
543
670
|
* applying min/max slice and optional/rest element rules.
|
|
544
671
|
*/
|
|
545
672
|
function buildTupleNode(node, print) {
|
|
546
|
-
let items = (node.items ?? []).map(print).filter(
|
|
673
|
+
let items = (node.items ?? []).map(print).filter(isNonNullable$1);
|
|
547
674
|
const restNode = node.rest ? print(node.rest) ?? void 0 : void 0;
|
|
548
675
|
const { min, max } = node;
|
|
549
676
|
if (max !== void 0) {
|
|
@@ -630,13 +757,13 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
|
|
|
630
757
|
isExportable: true,
|
|
631
758
|
isIndexable: true,
|
|
632
759
|
isTypeOnly: false,
|
|
633
|
-
children:
|
|
760
|
+
children: parserTs.print(nameNode)
|
|
634
761
|
}), /* @__PURE__ */ jsx(File.Source, {
|
|
635
762
|
name: typeName,
|
|
636
763
|
isIndexable: true,
|
|
637
764
|
isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
|
|
638
765
|
isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
|
|
639
|
-
children:
|
|
766
|
+
children: parserTs.print(typeNode)
|
|
640
767
|
})] });
|
|
641
768
|
}
|
|
642
769
|
//#endregion
|
|
@@ -676,6 +803,17 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
676
803
|
})] });
|
|
677
804
|
}
|
|
678
805
|
//#endregion
|
|
806
|
+
//#region ../../internals/shared/src/operation.ts
|
|
807
|
+
function getOperationParameters(node, options = {}) {
|
|
808
|
+
const params = ast.caseParams(node.parameters, options.paramsCasing);
|
|
809
|
+
return {
|
|
810
|
+
path: params.filter((param) => param.in === "path"),
|
|
811
|
+
query: params.filter((param) => param.in === "query"),
|
|
812
|
+
header: params.filter((param) => param.in === "header"),
|
|
813
|
+
cookie: params.filter((param) => param.in === "cookie")
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
//#endregion
|
|
679
817
|
//#region src/utils.ts
|
|
680
818
|
/**
|
|
681
819
|
* Collects JSDoc annotation strings for a schema node.
|
|
@@ -687,15 +825,18 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
687
825
|
function buildPropertyJSDocComments(schema) {
|
|
688
826
|
const meta = ast.syncSchemaRef(schema);
|
|
689
827
|
const isArray = meta?.primitive === "array";
|
|
828
|
+
const hasDescription = meta && "description" in meta && meta.description;
|
|
829
|
+
const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
|
|
690
830
|
return [
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
!isArray && meta && "
|
|
695
|
-
meta && "
|
|
696
|
-
meta && "
|
|
697
|
-
meta && "
|
|
698
|
-
meta && "
|
|
831
|
+
hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
|
|
832
|
+
...formatComment,
|
|
833
|
+
meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
|
|
834
|
+
!isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : null,
|
|
835
|
+
!isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
|
|
836
|
+
meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
|
|
837
|
+
meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
|
|
838
|
+
meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
|
|
839
|
+
meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : null].filter(Boolean).join("") : null
|
|
699
840
|
].filter(Boolean);
|
|
700
841
|
}
|
|
701
842
|
function buildParams(node, { params, resolver }) {
|
|
@@ -712,9 +853,7 @@ function buildParams(node, { params, resolver }) {
|
|
|
712
853
|
});
|
|
713
854
|
}
|
|
714
855
|
function buildData(node, { resolver }) {
|
|
715
|
-
const pathParams
|
|
716
|
-
const queryParams = node.parameters.filter((p) => p.in === "query");
|
|
717
|
-
const headerParams = node.parameters.filter((p) => p.in === "header");
|
|
856
|
+
const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
|
|
718
857
|
return ast.createSchema({
|
|
719
858
|
type: "object",
|
|
720
859
|
deprecated: node.deprecated,
|
|
@@ -796,7 +935,7 @@ function buildResponses(node, { resolver }) {
|
|
|
796
935
|
});
|
|
797
936
|
}
|
|
798
937
|
function buildResponseUnion(node, { resolver }) {
|
|
799
|
-
const responsesWithSchema = node.responses.filter((res) => res.schema);
|
|
938
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.[0]?.schema);
|
|
800
939
|
if (responsesWithSchema.length === 0) return null;
|
|
801
940
|
return ast.createSchema({
|
|
802
941
|
type: "union",
|
|
@@ -808,6 +947,9 @@ function buildResponseUnion(node, { resolver }) {
|
|
|
808
947
|
}
|
|
809
948
|
//#endregion
|
|
810
949
|
//#region src/printers/printerTs.ts
|
|
950
|
+
function isNonNullable(value) {
|
|
951
|
+
return value !== null && value !== void 0;
|
|
952
|
+
}
|
|
811
953
|
/**
|
|
812
954
|
* TypeScript type printer built with `definePrinter`.
|
|
813
955
|
*
|
|
@@ -860,7 +1002,7 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
860
1002
|
date: dateOrStringNode,
|
|
861
1003
|
time: dateOrStringNode,
|
|
862
1004
|
ref(node) {
|
|
863
|
-
if (!node.name) return;
|
|
1005
|
+
if (!node.name) return null;
|
|
864
1006
|
const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
|
|
865
1007
|
return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
|
|
866
1008
|
},
|
|
@@ -868,7 +1010,7 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
868
1010
|
const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
|
|
869
1011
|
if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
|
|
870
1012
|
withParentheses: true,
|
|
871
|
-
nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(
|
|
1013
|
+
nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
|
|
872
1014
|
}) ?? void 0;
|
|
873
1015
|
return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
|
|
874
1016
|
},
|
|
@@ -886,7 +1028,7 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
886
1028
|
withParentheses: true
|
|
887
1029
|
});
|
|
888
1030
|
return this.transform(m);
|
|
889
|
-
}).filter(
|
|
1031
|
+
}).filter(isNonNullable)
|
|
890
1032
|
}) ?? void 0;
|
|
891
1033
|
return createUnionDeclaration({
|
|
892
1034
|
withParentheses: true,
|
|
@@ -897,16 +1039,16 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
897
1039
|
return createIntersectionDeclaration({
|
|
898
1040
|
withParentheses: true,
|
|
899
1041
|
nodes: buildMemberNodes(node.members, this.transform)
|
|
900
|
-
}) ??
|
|
1042
|
+
}) ?? null;
|
|
901
1043
|
},
|
|
902
1044
|
array(node) {
|
|
903
1045
|
return createArrayDeclaration({
|
|
904
|
-
nodes: (node.items ?? []).map((item) => this.transform(item)).filter(
|
|
1046
|
+
nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
|
|
905
1047
|
arrayType: this.options.arrayType
|
|
906
|
-
}) ??
|
|
1048
|
+
}) ?? null;
|
|
907
1049
|
},
|
|
908
1050
|
tuple(node) {
|
|
909
|
-
return buildTupleNode(node, this.transform);
|
|
1051
|
+
return buildTupleNode(node, this.transform) ?? null;
|
|
910
1052
|
},
|
|
911
1053
|
object(node) {
|
|
912
1054
|
const { transform, options } = this;
|
|
@@ -933,46 +1075,67 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
933
1075
|
},
|
|
934
1076
|
print(node) {
|
|
935
1077
|
const { name, syntaxType = "type", description, keysToOmit } = this.options;
|
|
936
|
-
|
|
937
|
-
if (!
|
|
1078
|
+
const transformed = this.transform(node);
|
|
1079
|
+
if (!transformed) return null;
|
|
938
1080
|
const meta = ast.syncSchemaRef(node);
|
|
939
1081
|
if (!name) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
return
|
|
1082
|
+
const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
|
|
1083
|
+
const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
|
|
1084
|
+
return parserTs.print(result);
|
|
943
1085
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1086
|
+
const inner = (() => {
|
|
1087
|
+
const omitted = keysToOmit?.length ? createOmitDeclaration({
|
|
1088
|
+
keys: keysToOmit,
|
|
1089
|
+
type: transformed,
|
|
1090
|
+
nonNullable: true
|
|
1091
|
+
}) : transformed;
|
|
1092
|
+
const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [omitted, keywordTypeNodes.null] }) : omitted;
|
|
1093
|
+
return meta.nullish || meta.optional ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
|
|
1094
|
+
})();
|
|
1095
|
+
const typeNode = createTypeDeclaration({
|
|
953
1096
|
name,
|
|
954
1097
|
isExportable: true,
|
|
955
1098
|
type: inner,
|
|
956
|
-
syntax:
|
|
1099
|
+
syntax: syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length ? "type" : "interface",
|
|
957
1100
|
comments: buildPropertyJSDocComments({
|
|
958
1101
|
...meta,
|
|
959
1102
|
description
|
|
960
1103
|
})
|
|
961
|
-
})
|
|
1104
|
+
});
|
|
1105
|
+
return parserTs.print(typeNode);
|
|
962
1106
|
}
|
|
963
1107
|
};
|
|
964
1108
|
});
|
|
965
1109
|
//#endregion
|
|
966
1110
|
//#region src/generators/typeGenerator.tsx
|
|
1111
|
+
function getContentTypeSuffix(contentType) {
|
|
1112
|
+
const baseType = contentType.split(";")[0].trim();
|
|
1113
|
+
if (baseType === "application/json") return "Json";
|
|
1114
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
1115
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
1116
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
1117
|
+
if (parts.length === 0) return "Unknown";
|
|
1118
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1119
|
+
}
|
|
1120
|
+
function getPerContentTypeName(dataName, suffix) {
|
|
1121
|
+
if (dataName.endsWith("Data")) return suffix.endsWith("Data") ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`;
|
|
1122
|
+
return dataName + suffix;
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
|
|
1126
|
+
* schema in the spec plus per-operation request, response, and parameter
|
|
1127
|
+
* types. Drop-replace with a custom `Generator<PluginTs>` to change how
|
|
1128
|
+
* TypeScript output is produced.
|
|
1129
|
+
*/
|
|
967
1130
|
const typeGenerator = defineGenerator({
|
|
968
1131
|
name: "typescript",
|
|
969
|
-
renderer:
|
|
1132
|
+
renderer: jsxRendererSync,
|
|
970
1133
|
schema(node, ctx) {
|
|
971
1134
|
const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
|
|
972
1135
|
const { adapter, config, resolver, root } = ctx;
|
|
973
1136
|
if (!node.name) return;
|
|
974
1137
|
const mode = ctx.getMode(output);
|
|
975
|
-
const enumSchemaNames = new Set(
|
|
1138
|
+
const enumSchemaNames = new Set(ctx.meta.enumNames);
|
|
976
1139
|
function resolveImportName(schemaName) {
|
|
977
1140
|
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
|
|
978
1141
|
return resolver.resolveTypeName(schemaName);
|
|
@@ -985,7 +1148,7 @@ const typeGenerator = defineGenerator({
|
|
|
985
1148
|
}, {
|
|
986
1149
|
root,
|
|
987
1150
|
output,
|
|
988
|
-
group
|
|
1151
|
+
group: group ?? void 0
|
|
989
1152
|
}).path
|
|
990
1153
|
}));
|
|
991
1154
|
const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
|
|
@@ -997,7 +1160,7 @@ const typeGenerator = defineGenerator({
|
|
|
997
1160
|
}, {
|
|
998
1161
|
root,
|
|
999
1162
|
output,
|
|
1000
|
-
group
|
|
1163
|
+
group: group ?? void 0
|
|
1001
1164
|
})
|
|
1002
1165
|
};
|
|
1003
1166
|
const schemaPrinter = printerTs({
|
|
@@ -1016,13 +1179,21 @@ const typeGenerator = defineGenerator({
|
|
|
1016
1179
|
baseName: meta.file.baseName,
|
|
1017
1180
|
path: meta.file.path,
|
|
1018
1181
|
meta: meta.file.meta,
|
|
1019
|
-
banner: resolver.resolveBanner(
|
|
1182
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1020
1183
|
output,
|
|
1021
|
-
config
|
|
1184
|
+
config,
|
|
1185
|
+
file: {
|
|
1186
|
+
path: meta.file.path,
|
|
1187
|
+
baseName: meta.file.baseName
|
|
1188
|
+
}
|
|
1022
1189
|
}),
|
|
1023
|
-
footer: resolver.resolveFooter(
|
|
1190
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1024
1191
|
output,
|
|
1025
|
-
config
|
|
1192
|
+
config,
|
|
1193
|
+
file: {
|
|
1194
|
+
path: meta.file.path,
|
|
1195
|
+
baseName: meta.file.baseName
|
|
1196
|
+
}
|
|
1026
1197
|
}),
|
|
1027
1198
|
children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1028
1199
|
root: meta.file.path,
|
|
@@ -1057,9 +1228,9 @@ const typeGenerator = defineGenerator({
|
|
|
1057
1228
|
}, {
|
|
1058
1229
|
root,
|
|
1059
1230
|
output,
|
|
1060
|
-
group
|
|
1231
|
+
group: group ?? void 0
|
|
1061
1232
|
}) };
|
|
1062
|
-
const enumSchemaNames = new Set(
|
|
1233
|
+
const enumSchemaNames = new Set(ctx.meta.enumNames);
|
|
1063
1234
|
function resolveImportName(schemaName) {
|
|
1064
1235
|
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
|
|
1065
1236
|
return resolver.resolveTypeName(schemaName);
|
|
@@ -1074,7 +1245,7 @@ const typeGenerator = defineGenerator({
|
|
|
1074
1245
|
}, {
|
|
1075
1246
|
root,
|
|
1076
1247
|
output,
|
|
1077
|
-
group
|
|
1248
|
+
group: group ?? void 0
|
|
1078
1249
|
}).path
|
|
1079
1250
|
}));
|
|
1080
1251
|
const schemaPrinter = printerTs({
|
|
@@ -1113,18 +1284,58 @@ const typeGenerator = defineGenerator({
|
|
|
1113
1284
|
schema: param.schema,
|
|
1114
1285
|
name: resolver.resolveParamName(node, param)
|
|
1115
1286
|
}));
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1287
|
+
const requestBodyContent = node.requestBody?.content ?? [];
|
|
1288
|
+
function buildRequestType() {
|
|
1289
|
+
if (requestBodyContent.length === 0) return null;
|
|
1290
|
+
if (requestBodyContent.length === 1) {
|
|
1291
|
+
const entry = requestBodyContent[0];
|
|
1292
|
+
if (!entry.schema) return null;
|
|
1293
|
+
return renderSchemaType({
|
|
1294
|
+
schema: {
|
|
1295
|
+
...entry.schema,
|
|
1296
|
+
description: node.requestBody.description ?? entry.schema.description
|
|
1297
|
+
},
|
|
1298
|
+
name: resolver.resolveDataName(node),
|
|
1299
|
+
keysToOmit: entry.keysToOmit
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
const dataName = resolver.resolveDataName(node);
|
|
1303
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
1304
|
+
const individualItems = requestBodyContent.filter((entry) => entry.schema).map((entry) => {
|
|
1305
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
1306
|
+
let individualName = getPerContentTypeName(dataName, baseSuffix);
|
|
1307
|
+
let counter = 2;
|
|
1308
|
+
while (usedNames.has(individualName)) individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`);
|
|
1309
|
+
usedNames.add(individualName);
|
|
1310
|
+
return {
|
|
1311
|
+
name: individualName,
|
|
1312
|
+
rendered: renderSchemaType({
|
|
1313
|
+
schema: {
|
|
1314
|
+
...entry.schema,
|
|
1315
|
+
description: node.requestBody.description ?? entry.schema.description
|
|
1316
|
+
},
|
|
1317
|
+
name: individualName,
|
|
1318
|
+
keysToOmit: entry.keysToOmit
|
|
1319
|
+
})
|
|
1320
|
+
};
|
|
1321
|
+
});
|
|
1322
|
+
const unionType = renderSchemaType({
|
|
1323
|
+
schema: ast.createSchema({
|
|
1324
|
+
type: "union",
|
|
1325
|
+
members: individualItems.map((item) => ast.createSchema({
|
|
1326
|
+
type: "ref",
|
|
1327
|
+
name: item.name
|
|
1328
|
+
}))
|
|
1329
|
+
}),
|
|
1330
|
+
name: dataName
|
|
1331
|
+
});
|
|
1332
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [individualItems.map((item) => item.rendered), unionType] });
|
|
1333
|
+
}
|
|
1334
|
+
const requestType = buildRequestType();
|
|
1124
1335
|
const responseTypes = node.responses.map((res) => renderSchemaType({
|
|
1125
|
-
schema: res.schema,
|
|
1336
|
+
schema: res.content?.[0]?.schema ?? null,
|
|
1126
1337
|
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
1127
|
-
keysToOmit: res.keysToOmit
|
|
1338
|
+
keysToOmit: res.content?.[0]?.keysToOmit
|
|
1128
1339
|
}));
|
|
1129
1340
|
const dataType = renderSchemaType({
|
|
1130
1341
|
schema: buildData({
|
|
@@ -1137,11 +1348,11 @@ const typeGenerator = defineGenerator({
|
|
|
1137
1348
|
schema: buildResponses(node, { resolver }),
|
|
1138
1349
|
name: resolver.resolveResponsesName(node)
|
|
1139
1350
|
});
|
|
1140
|
-
|
|
1141
|
-
if (!node.responses.some((res) => res.schema)) return null;
|
|
1351
|
+
function buildResponseType() {
|
|
1352
|
+
if (!node.responses.some((res) => res.content?.[0]?.schema)) return null;
|
|
1142
1353
|
const responseName = resolver.resolveResponseName(node);
|
|
1143
|
-
const responsesWithSchema = node.responses.filter((res) => res.schema);
|
|
1144
|
-
if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(res.schema, (schemaName) => ({
|
|
1354
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.[0]?.schema);
|
|
1355
|
+
if (new Set(responsesWithSchema.flatMap((res) => res.content?.[0]?.schema ? adapter.getImports(res.content[0].schema, (schemaName) => ({
|
|
1145
1356
|
name: resolveImportName(schemaName),
|
|
1146
1357
|
path: ""
|
|
1147
1358
|
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
|
|
@@ -1152,18 +1363,27 @@ const typeGenerator = defineGenerator({
|
|
|
1152
1363
|
},
|
|
1153
1364
|
name: responseName
|
|
1154
1365
|
});
|
|
1155
|
-
}
|
|
1366
|
+
}
|
|
1367
|
+
const responseType = buildResponseType();
|
|
1156
1368
|
return /* @__PURE__ */ jsxs(File, {
|
|
1157
1369
|
baseName: meta.file.baseName,
|
|
1158
1370
|
path: meta.file.path,
|
|
1159
1371
|
meta: meta.file.meta,
|
|
1160
|
-
banner: resolver.resolveBanner(
|
|
1372
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1161
1373
|
output,
|
|
1162
|
-
config
|
|
1374
|
+
config,
|
|
1375
|
+
file: {
|
|
1376
|
+
path: meta.file.path,
|
|
1377
|
+
baseName: meta.file.baseName
|
|
1378
|
+
}
|
|
1163
1379
|
}),
|
|
1164
|
-
footer: resolver.resolveFooter(
|
|
1380
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1165
1381
|
output,
|
|
1166
|
-
config
|
|
1382
|
+
config,
|
|
1383
|
+
file: {
|
|
1384
|
+
path: meta.file.path,
|
|
1385
|
+
baseName: meta.file.baseName
|
|
1386
|
+
}
|
|
1167
1387
|
}),
|
|
1168
1388
|
children: [
|
|
1169
1389
|
paramTypes,
|
|
@@ -1179,86 +1399,98 @@ const typeGenerator = defineGenerator({
|
|
|
1179
1399
|
//#endregion
|
|
1180
1400
|
//#region src/resolvers/resolverTs.ts
|
|
1181
1401
|
/**
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1402
|
+
* Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
|
|
1403
|
+
* for every generated TypeScript type. Import this in other plugins that need
|
|
1404
|
+
* to reference the exact names `plugin-ts` produces without duplicating the
|
|
1405
|
+
* casing/file-layout rules.
|
|
1185
1406
|
*
|
|
1186
|
-
* The `default` method is
|
|
1187
|
-
*
|
|
1407
|
+
* The `default` method is supplied by `defineResolver`. It uses PascalCase for
|
|
1408
|
+
* type names and PascalCase-with-isFile for files.
|
|
1188
1409
|
*
|
|
1189
|
-
* @example
|
|
1410
|
+
* @example Resolve a type and file name
|
|
1190
1411
|
* ```ts
|
|
1191
|
-
* import {
|
|
1412
|
+
* import { resolverTs } from '@kubb/plugin-ts'
|
|
1192
1413
|
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
1414
|
+
* resolverTs.default('list pets', 'type') // 'ListPets'
|
|
1415
|
+
* resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
|
|
1416
|
+
* resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
|
|
1196
1417
|
* ```
|
|
1197
1418
|
*/
|
|
1198
|
-
const resolverTs = defineResolver((
|
|
1419
|
+
const resolverTs = defineResolver(() => {
|
|
1199
1420
|
return {
|
|
1200
1421
|
name: "default",
|
|
1201
1422
|
pluginName: "plugin-ts",
|
|
1202
1423
|
default(name, type) {
|
|
1203
|
-
|
|
1424
|
+
const resolved = pascalCase(name, { isFile: type === "file" });
|
|
1425
|
+
return type === "file" ? resolved : ensureValidVarName(resolved);
|
|
1204
1426
|
},
|
|
1205
1427
|
resolveTypeName(name) {
|
|
1206
|
-
return pascalCase(name);
|
|
1428
|
+
return ensureValidVarName(pascalCase(name));
|
|
1207
1429
|
},
|
|
1208
1430
|
resolvePathName(name, type) {
|
|
1209
|
-
|
|
1431
|
+
const resolved = pascalCase(name, { isFile: type === "file" });
|
|
1432
|
+
return type === "file" ? resolved : ensureValidVarName(resolved);
|
|
1210
1433
|
},
|
|
1211
1434
|
resolveParamName(node, param) {
|
|
1212
|
-
return
|
|
1435
|
+
return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
|
|
1213
1436
|
},
|
|
1214
1437
|
resolveResponseStatusName(node, statusCode) {
|
|
1215
|
-
return
|
|
1438
|
+
return this.resolveTypeName(`${node.operationId} Status ${statusCode}`);
|
|
1216
1439
|
},
|
|
1217
1440
|
resolveDataName(node) {
|
|
1218
|
-
return
|
|
1441
|
+
return this.resolveTypeName(`${node.operationId} Data`);
|
|
1219
1442
|
},
|
|
1220
1443
|
resolveRequestConfigName(node) {
|
|
1221
|
-
return
|
|
1444
|
+
return this.resolveTypeName(`${node.operationId} RequestConfig`);
|
|
1222
1445
|
},
|
|
1223
1446
|
resolveResponsesName(node) {
|
|
1224
|
-
return
|
|
1447
|
+
return this.resolveTypeName(`${node.operationId} Responses`);
|
|
1225
1448
|
},
|
|
1226
1449
|
resolveResponseName(node) {
|
|
1227
|
-
return
|
|
1450
|
+
return this.resolveTypeName(`${node.operationId} Response`);
|
|
1228
1451
|
},
|
|
1229
1452
|
resolveEnumKeyName(node, enumTypeSuffix = "key") {
|
|
1230
|
-
return `${
|
|
1453
|
+
return `${this.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
|
|
1231
1454
|
},
|
|
1232
1455
|
resolvePathParamsName(node, param) {
|
|
1233
|
-
return
|
|
1456
|
+
return this.resolveParamName(node, param);
|
|
1234
1457
|
},
|
|
1235
1458
|
resolveQueryParamsName(node, param) {
|
|
1236
|
-
return
|
|
1459
|
+
return this.resolveParamName(node, param);
|
|
1237
1460
|
},
|
|
1238
1461
|
resolveHeaderParamsName(node, param) {
|
|
1239
|
-
return
|
|
1462
|
+
return this.resolveParamName(node, param);
|
|
1240
1463
|
}
|
|
1241
1464
|
};
|
|
1242
1465
|
});
|
|
1243
1466
|
//#endregion
|
|
1244
1467
|
//#region src/plugin.ts
|
|
1245
1468
|
/**
|
|
1246
|
-
* Canonical plugin name for `@kubb/plugin-ts
|
|
1469
|
+
* Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
|
|
1470
|
+
* cross-plugin dependency references.
|
|
1247
1471
|
*/
|
|
1248
1472
|
const pluginTsName = "plugin-ts";
|
|
1249
1473
|
/**
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
* and writes barrel files based on `output.barrelType`.
|
|
1474
|
+
* Generates TypeScript `type` aliases and `interface` declarations from an
|
|
1475
|
+
* OpenAPI spec. The foundation that every other Kubb plugin builds on:
|
|
1476
|
+
* clients, query hooks, mocks, and validators all reference the names this
|
|
1477
|
+
* plugin produces.
|
|
1255
1478
|
*
|
|
1256
1479
|
* @example
|
|
1257
1480
|
* ```ts
|
|
1258
|
-
* import
|
|
1481
|
+
* import { defineConfig } from 'kubb'
|
|
1482
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1259
1483
|
*
|
|
1260
1484
|
* export default defineConfig({
|
|
1261
|
-
*
|
|
1485
|
+
* input: { path: './petStore.yaml' },
|
|
1486
|
+
* output: { path: './src/gen' },
|
|
1487
|
+
* plugins: [
|
|
1488
|
+
* pluginTs({
|
|
1489
|
+
* output: { path: './types' },
|
|
1490
|
+
* enumType: 'asConst',
|
|
1491
|
+
* optionalType: 'questionTokenAndUndefined',
|
|
1492
|
+
* }),
|
|
1493
|
+
* ],
|
|
1262
1494
|
* })
|
|
1263
1495
|
* ```
|
|
1264
1496
|
*/
|
|
@@ -1273,7 +1505,7 @@ const pluginTs = definePlugin((options) => {
|
|
|
1273
1505
|
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1274
1506
|
return `${camelCase(ctx.group)}Controller`;
|
|
1275
1507
|
}
|
|
1276
|
-
} :
|
|
1508
|
+
} : null;
|
|
1277
1509
|
return {
|
|
1278
1510
|
name: pluginTsName,
|
|
1279
1511
|
options,
|