@kubb/plugin-ts 5.5.0-canary.20261111T122006 → 5.5.0
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/dist/index.cjs +687 -817
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -5
- package/dist/index.d.ts +24 -5
- package/dist/index.js +688 -818
- package/dist/index.js.map +1 -1
- package/docs/guides/customization.md +2 -2
- package/docs/options.md +2 -1
- package/kubb-extension.json +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -22,40 +22,6 @@ let _kubb_renderer_jsx = require("@kubb/renderer-jsx");
|
|
|
22
22
|
let yuku_codegen = require("yuku-codegen");
|
|
23
23
|
let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
|
|
24
24
|
let node_module = require("node:module");
|
|
25
|
-
// ../../internals/utils/src/casing.ts
|
|
26
|
-
const wordStart = /(?<=[a-z\d])(?=[A-Z])|(?<=[A-Z]{2})(?=[A-Z][a-z]{2})/;
|
|
27
|
-
/**
|
|
28
|
-
* Splits `text` into words on separators (whitespace, hyphens, underscores, dots, slashes, colons)
|
|
29
|
-
* and on {@link wordStart}. A segment without lowercase letters, such as `COOL` or `2D`, is one word,
|
|
30
|
-
* so snake case leaves a name that is already in it unchanged.
|
|
31
|
-
*/
|
|
32
|
-
function words(text) {
|
|
33
|
-
return text.split(/[\s\-_./\\:]+/).filter(Boolean).flatMap((segment) => segment === segment.toUpperCase() ? segment : segment.split(wordStart));
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Converts `text` to snake_case.
|
|
37
|
-
*
|
|
38
|
-
* @example From camelCase
|
|
39
|
-
* `snakeCase('helloWorld') // 'hello_world'`
|
|
40
|
-
*
|
|
41
|
-
* @example From mixed separators
|
|
42
|
-
* `snakeCase('Hello-World') // 'hello_world'`
|
|
43
|
-
*/
|
|
44
|
-
function snakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
45
|
-
return words(`${prefix} ${text} ${suffix}`).map((word) => word.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()).filter(Boolean).join("_");
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Converts `text` to SCREAMING_SNAKE_CASE.
|
|
49
|
-
*
|
|
50
|
-
* @example From camelCase
|
|
51
|
-
* `screamingSnakeCase('helloWorld') // 'HELLO_WORLD'`
|
|
52
|
-
*/
|
|
53
|
-
function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
54
|
-
return snakeCase(text, {
|
|
55
|
-
prefix,
|
|
56
|
-
suffix
|
|
57
|
-
}).toUpperCase();
|
|
58
|
-
}
|
|
59
25
|
// ../../internals/utils/src/reserved.ts
|
|
60
26
|
/**
|
|
61
27
|
* JavaScript and Java reserved words.
|
|
@@ -195,53 +161,6 @@ function ensureValidVarName(name) {
|
|
|
195
161
|
function isIdentifier(name) {
|
|
196
162
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
197
163
|
}
|
|
198
|
-
// ../../internals/utils/src/strings.ts
|
|
199
|
-
/**
|
|
200
|
-
* Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
|
|
201
|
-
* Returns the string unchanged when no balanced quote pair is found.
|
|
202
|
-
*
|
|
203
|
-
* @example
|
|
204
|
-
* ```ts
|
|
205
|
-
* trimQuotes('"hello"') // 'hello'
|
|
206
|
-
* trimQuotes('hello') // 'hello'
|
|
207
|
-
* ```
|
|
208
|
-
*/
|
|
209
|
-
function trimQuotes(text) {
|
|
210
|
-
if (text.length >= 2) {
|
|
211
|
-
const first = text[0];
|
|
212
|
-
const last = text[text.length - 1];
|
|
213
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
|
|
214
|
-
}
|
|
215
|
-
return text;
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
|
|
219
|
-
*
|
|
220
|
-
* Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
|
|
221
|
-
* code matches the repo style without a formatter.
|
|
222
|
-
*
|
|
223
|
-
* @example
|
|
224
|
-
* ```ts
|
|
225
|
-
* stringify('hello') // "'hello'"
|
|
226
|
-
* stringify('"hello"') // "'hello'"
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
229
|
-
function stringify(value) {
|
|
230
|
-
if (value === void 0 || value === null) return "''";
|
|
231
|
-
return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Escapes an `@` at the start of a line, so a multi-line description can't start a new JSDoc tag.
|
|
235
|
-
* Editors render `\@` as `@` in hovers.
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* ```ts
|
|
239
|
-
* jsDocEscape('intro\n@example value') // 'intro\n\\@example value'
|
|
240
|
-
* ```
|
|
241
|
-
*/
|
|
242
|
-
function jsDocEscape(input) {
|
|
243
|
-
return `${input}`.replace(/^(\s*)@/gm, "$1\\@");
|
|
244
|
-
}
|
|
245
164
|
// ../../internals/utils/src/fs.ts
|
|
246
165
|
/**
|
|
247
166
|
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
@@ -262,9 +181,22 @@ function jsDocEscape(input) {
|
|
|
262
181
|
* @example Suffix applied to the final segment only
|
|
263
182
|
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
264
183
|
*/
|
|
265
|
-
function toFilePath(name, caseLast = _kubb_kit.
|
|
184
|
+
function toFilePath(name, caseLast = _kubb_kit.casing.camel) {
|
|
266
185
|
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
267
|
-
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) :
|
|
186
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : _kubb_kit.casing.camel(part)).filter(Boolean).join("/");
|
|
187
|
+
}
|
|
188
|
+
// ../../internals/utils/src/strings.ts
|
|
189
|
+
/**
|
|
190
|
+
* Escapes an `@` at the start of a line, so a multi-line description can't start a new JSDoc tag.
|
|
191
|
+
* Editors render `\@` as `@` in hovers.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* jsDocEscape('intro\n@example value') // 'intro\n\\@example value'
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
function jsDocEscape(input) {
|
|
199
|
+
return `${input}`.replace(/^(\s*)@/gm, "$1\\@");
|
|
268
200
|
}
|
|
269
201
|
// src/factory.ts
|
|
270
202
|
var factory_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -350,12 +282,6 @@ function propertyName(name) {
|
|
|
350
282
|
return isValidIdentifier(name) ? identifier(name) : literal(name);
|
|
351
283
|
}
|
|
352
284
|
/**
|
|
353
|
-
* Indents every non-empty line of `text` by two spaces.
|
|
354
|
-
*/
|
|
355
|
-
function indent(text) {
|
|
356
|
-
return text.split("\n").map((line) => line ? ` ${line}` : line).join("\n");
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
285
|
* Creates a TypeScript intersection type.
|
|
360
286
|
* Returns the single type if only one is provided. `yuku-codegen` adds the parentheses a union member needs.
|
|
361
287
|
*/
|
|
@@ -516,12 +442,12 @@ function valueToExpression(value, asNumber = isNumber(value)) {
|
|
|
516
442
|
/**
|
|
517
443
|
* Apply casing transformation to enum keys
|
|
518
444
|
*/
|
|
519
|
-
function applyEnumKeyCasing(key,
|
|
520
|
-
if (
|
|
521
|
-
if (
|
|
522
|
-
if (
|
|
523
|
-
if (
|
|
524
|
-
if (
|
|
445
|
+
function applyEnumKeyCasing(key, keyCasing = "none") {
|
|
446
|
+
if (keyCasing === "none") return key;
|
|
447
|
+
if (keyCasing === "screamingSnakeCase") return _kubb_kit.casing.screamingSnake(key);
|
|
448
|
+
if (keyCasing === "snakeCase") return _kubb_kit.casing.snake(key);
|
|
449
|
+
if (keyCasing === "pascalCase") return _kubb_kit.casing.pascal(key);
|
|
450
|
+
if (keyCasing === "camelCase") return _kubb_kit.casing.camel(key);
|
|
525
451
|
return key;
|
|
526
452
|
}
|
|
527
453
|
/**
|
|
@@ -600,7 +526,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
600
526
|
exprName: identifier(identifierName),
|
|
601
527
|
typeArguments: null
|
|
602
528
|
};
|
|
603
|
-
return [`export const ${identifierName} = {\n${properties.map(indent).join(",\n")},\n} as const;`, typeAlias({
|
|
529
|
+
return [`export const ${identifierName} = {\n${properties.map((property) => _kubb_kit.text.indent({ text: property })).join(",\n")},\n} as const;`, typeAlias({
|
|
604
530
|
type: "TSIndexedAccessType",
|
|
605
531
|
objectType: {
|
|
606
532
|
type: "TSParenthesizedType",
|
|
@@ -724,7 +650,7 @@ function wrapNullableOptional(type, nullable, addsUndefined) {
|
|
|
724
650
|
*/
|
|
725
651
|
function buildPropertyType(schema, baseType, optionalType, optional) {
|
|
726
652
|
const addsUndefined = optionalType === "undefined" || optionalType === "questionTokenAndUndefined";
|
|
727
|
-
const meta =
|
|
653
|
+
const meta = _kubb_kit.ast.ref.mergeSchema(schema);
|
|
728
654
|
return wrapNullableOptional(baseType, !!meta.nullable, addsUndefined && !!(optional || meta.nullish || meta.optional));
|
|
729
655
|
}
|
|
730
656
|
/**
|
|
@@ -778,7 +704,7 @@ function buildIndexSignatures(node, propertyCount, printNode) {
|
|
|
778
704
|
function getEnumNames$1({ node, enum: enumOptions, resolver }) {
|
|
779
705
|
const resolved = resolver.name(node.name);
|
|
780
706
|
return {
|
|
781
|
-
enumName: enumOptions.constCasing === "pascalCase" ? resolved : ensureValidVarName(
|
|
707
|
+
enumName: enumOptions.constCasing === "pascalCase" ? resolved : ensureValidVarName(_kubb_kit.casing.camel(node.name)),
|
|
782
708
|
typeName: enumOptions.type === "asConst" ? resolver.enum.keyName(node, enumOptions.typeSuffix) : resolved
|
|
783
709
|
};
|
|
784
710
|
}
|
|
@@ -804,10 +730,10 @@ function Enum({ node, enum: enumOptions, resolver }) {
|
|
|
804
730
|
name: enumName,
|
|
805
731
|
typeName,
|
|
806
732
|
enums: node.namedEnumValues?.map((v) => [
|
|
807
|
-
|
|
733
|
+
_kubb_kit.text.unquote(v.name.toString()),
|
|
808
734
|
v.value,
|
|
809
735
|
v.description
|
|
810
|
-
]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [
|
|
736
|
+
]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [_kubb_kit.text.unquote(v.toString()), v]) ?? [],
|
|
811
737
|
type: enumOptions.type,
|
|
812
738
|
enumKeyCasing: enumOptions.keyCasing
|
|
813
739
|
});
|
|
@@ -832,169 +758,567 @@ function Enum({ node, enum: enumOptions, resolver }) {
|
|
|
832
758
|
children: typeNode
|
|
833
759
|
})] });
|
|
834
760
|
}
|
|
835
|
-
//
|
|
761
|
+
// src/utils.ts
|
|
836
762
|
/**
|
|
837
|
-
*
|
|
838
|
-
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
839
|
-
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
840
|
-
* that was repeated at dozens of call sites across the client and query plugins.
|
|
841
|
-
*
|
|
842
|
-
* @example
|
|
843
|
-
* ```ts
|
|
844
|
-
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
845
|
-
* ```
|
|
763
|
+
* Renames inline enum declarations and their uses together.
|
|
846
764
|
*/
|
|
847
|
-
function
|
|
848
|
-
return {
|
|
849
|
-
name
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
765
|
+
function resolveInlineEnumNames({ node, owner, resolver, enumOptions }) {
|
|
766
|
+
return _kubb_kit.ast.node.transform(node, { schema(child) {
|
|
767
|
+
if (child === node || child.type !== "enum" || !child.name) return;
|
|
768
|
+
const name = resolver.resolveName({
|
|
769
|
+
key: [
|
|
770
|
+
"enum",
|
|
771
|
+
owner,
|
|
772
|
+
child.pointer ?? child.name
|
|
773
|
+
],
|
|
774
|
+
name: child.name,
|
|
775
|
+
normalize: (value) => {
|
|
776
|
+
const { enumName, typeName } = getEnumNames$1({
|
|
777
|
+
node: {
|
|
778
|
+
...child,
|
|
779
|
+
name: value
|
|
780
|
+
},
|
|
781
|
+
enum: enumOptions,
|
|
782
|
+
resolver
|
|
783
|
+
});
|
|
784
|
+
return enumOptions.type === "literal" ? [typeName] : [enumName, typeName];
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
return name === child.name ? void 0 : {
|
|
788
|
+
...child,
|
|
789
|
+
name
|
|
790
|
+
};
|
|
791
|
+
} });
|
|
792
|
+
}
|
|
793
|
+
function enumTypeName({ resolver, name, enumOptions, isEnum }) {
|
|
794
|
+
return isEnum && enumOptions?.type === "asConst" && enumOptions.typeSuffix ? resolver.enum.keyName({ name }, enumOptions.typeSuffix) : resolver.name(name);
|
|
854
795
|
}
|
|
855
796
|
/**
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* operation. The plugin itself and every plugin that imports from it call this, so they all get
|
|
859
|
-
* the same path.
|
|
860
|
-
*
|
|
861
|
-
* When that path matches the file of a component schema in `ctx.driver.inputNode`, ignoring case, the file name
|
|
862
|
-
* gets an `Operation` suffix: operation `point` goes to `PointOperation.ts` next to schema `Point.ts`.
|
|
863
|
-
* Otherwise the two merge into one file, or overwrite each other on a case-insensitive file system.
|
|
864
|
-
* Only the file name changes. The declarations keep their names.
|
|
797
|
+
* Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
|
|
798
|
+
* rather than a named enum reference or a runtime enum declaration.
|
|
865
799
|
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* const
|
|
869
|
-
*
|
|
800
|
+
* The parser folds `const` into a single-value enum node. The adapter decides which schemas are
|
|
801
|
+
* named enums and lists them in `enumSchemaNames`, and references to those names get suffixed (for
|
|
802
|
+
* example `StatusKey`). A const renders as a literal only when the adapter has not registered it as
|
|
803
|
+
* a named enum, which keeps the declaration and its references in sync across adapter versions.
|
|
870
804
|
*/
|
|
871
|
-
function
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
output,
|
|
877
|
-
group: options.group ?? void 0
|
|
878
|
-
};
|
|
879
|
-
return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
|
|
880
|
-
const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
|
|
881
|
-
name: schema.name,
|
|
882
|
-
extname: ".ts",
|
|
883
|
-
...location
|
|
884
|
-
}).path.toLowerCase()] : [])));
|
|
885
|
-
let file = resolver.file({
|
|
886
|
-
...operationFileEntry(node, node.operationId),
|
|
887
|
-
...location
|
|
888
|
-
});
|
|
889
|
-
for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
|
|
890
|
-
const next = resolver.file({
|
|
891
|
-
...operationFileEntry(node, name),
|
|
892
|
-
...location
|
|
893
|
-
});
|
|
894
|
-
if (next.path === file.path) break;
|
|
895
|
-
file = next;
|
|
896
|
-
}
|
|
897
|
-
return file;
|
|
898
|
-
});
|
|
805
|
+
function isInlineConstEnum(node, enumSchemaNames) {
|
|
806
|
+
return (node.namedEnumValues ?? node.enumValues ?? []).length === 1 && !(node.name && enumSchemaNames?.has(node.name));
|
|
807
|
+
}
|
|
808
|
+
function isSchemaOptional(schema) {
|
|
809
|
+
return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
|
|
899
810
|
}
|
|
900
|
-
function
|
|
901
|
-
|
|
811
|
+
function formatExample(value) {
|
|
812
|
+
if (value === null || typeof value !== "object") return String(value);
|
|
813
|
+
return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
|
|
902
814
|
}
|
|
903
815
|
/**
|
|
904
|
-
* Whether a
|
|
905
|
-
* `application/x-ndjson`, `application/json-seq`, or any media type with an OpenAPI 3.2 `itemSchema`.
|
|
816
|
+
* Whether a schema is binary, or an array of binary items, so its TypeScript type is `Blob` or `Blob[]`.
|
|
906
817
|
*/
|
|
907
|
-
function
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
function isJsonContentType(baseType) {
|
|
912
|
-
return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
|
|
818
|
+
function isBinarySchema(schema) {
|
|
819
|
+
const meta = _kubb_kit.ast.ref.mergeSchema(schema);
|
|
820
|
+
const isBlob = (node) => _kubb_kit.ast.ref.mergeSchema(node)?.type === "blob";
|
|
821
|
+
return !!meta && (isBlob(meta) || meta.type === "array" && "items" in meta && (meta.items ?? []).some(isBlob));
|
|
913
822
|
}
|
|
914
823
|
/**
|
|
915
|
-
*
|
|
824
|
+
* Collects JSDoc annotation strings for a schema node.
|
|
916
825
|
*
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
* binary type. Other `text/*` is text, and `application/octet-stream`, `application/pdf`,
|
|
920
|
-
* `image/*`, `audio/*`, and `video/*` are blobs.
|
|
826
|
+
* Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@minimum`, `@maximum`, `@minLength`,
|
|
827
|
+
* `@maxLength`, `@pattern`, `@default`, `@example`, `@see` and `@type`.
|
|
921
828
|
*/
|
|
922
|
-
function
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
829
|
+
function buildPropertyJSDocComments(schema, optional) {
|
|
830
|
+
const meta = _kubb_kit.ast.ref.mergeSchema(schema);
|
|
831
|
+
const isArray = meta?.primitive === "array";
|
|
832
|
+
const isObject = meta?.type === "object";
|
|
833
|
+
const isNumeric = meta?.primitive === "number" || meta?.primitive === "integer" || meta?.primitive === "bigint";
|
|
834
|
+
const hasDescription = meta && "description" in meta && meta.description;
|
|
835
|
+
const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
|
|
836
|
+
const exampleValues = meta?.examples ?? [];
|
|
837
|
+
const comments = [
|
|
838
|
+
hasDescription ? `@description ${jsDocEscape(meta.description)}` : null,
|
|
839
|
+
...formatComment,
|
|
840
|
+
meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
|
|
841
|
+
!isArray && !isObject && meta && "min" in meta && meta.min !== void 0 ? `@${isNumeric ? "minimum" : "minLength"} ${meta.min}` : null,
|
|
842
|
+
!isArray && !isObject && meta && "max" in meta && meta.max !== void 0 ? `@${isNumeric ? "maximum" : "maxLength"} ${meta.max}` : null,
|
|
843
|
+
meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
|
|
844
|
+
meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" && typeof meta.default === "string" ? _kubb_kit.text.quote(meta.default) : formatExample(meta.default)}` : null,
|
|
845
|
+
...exampleValues.map((example) => `@example ${formatExample(example)}`),
|
|
846
|
+
meta?.externalDocs ? `@see ${jsDocEscape([meta.externalDocs.url, meta.externalDocs.description].filter(Boolean).join(" "))}` : null
|
|
847
|
+
].filter(Boolean);
|
|
848
|
+
const typeTag = comments.length && !isBinarySchema(schema) && meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null;
|
|
849
|
+
return [...comments, typeTag].filter(Boolean);
|
|
933
850
|
}
|
|
934
851
|
/**
|
|
935
|
-
*
|
|
936
|
-
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
852
|
+
* The example values. With `serialized`, each `serializedValue` is added as a `<caption>Serialized</caption>` example.
|
|
937
853
|
*/
|
|
938
|
-
function
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
if (baseType === "multipart/form-data") return "FormData";
|
|
942
|
-
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
943
|
-
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
944
|
-
if (parts.length === 0) return "Unknown";
|
|
945
|
-
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
854
|
+
function getExampleValues(examples, serialized = false) {
|
|
855
|
+
const values = (examples ?? []).flatMap((example) => [...example.value !== void 0 ? [example.value] : [], ...serialized && example.serializedValue !== void 0 ? [`<caption>Serialized</caption> ${jsDocEscape(example.serializedValue)}`] : []]);
|
|
856
|
+
return values.length > 0 ? values : void 0;
|
|
946
857
|
}
|
|
947
858
|
/**
|
|
948
|
-
*
|
|
949
|
-
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
859
|
+
* The JSDoc lines of a response type: `@summary`, and one `@see` per link, such as `@see getPet: getPet({ petId: $response.body#/id })`.
|
|
950
860
|
*/
|
|
951
|
-
function
|
|
952
|
-
|
|
953
|
-
|
|
861
|
+
function buildResponseComments(response) {
|
|
862
|
+
const links = Object.entries(response.links ?? {}).map(([name, link]) => {
|
|
863
|
+
const values = {
|
|
864
|
+
...link.parameters,
|
|
865
|
+
...link.requestBody !== void 0 ? { body: link.requestBody } : {}
|
|
866
|
+
};
|
|
867
|
+
const args = Object.entries(values).map(([key, value]) => `${key}: ${formatExample(value)}`);
|
|
868
|
+
return `@see ${jsDocEscape(`${name}: ${link.operationId ?? link.operationRef ?? ""}(${args.length ? `{ ${args.join(", ")} }` : ""})`)}`;
|
|
869
|
+
});
|
|
870
|
+
return [...response.summary ? [`@summary ${jsDocEscape(response.summary)}`] : [], ...links];
|
|
954
871
|
}
|
|
955
872
|
/**
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
* only through `parseAs: 'blob'`, which needs every entry in `entries` binary. JSON, vendor (`application/vnd.*`), stream,
|
|
959
|
-
* schemaless, and wildcard entries keep their schema. `entries` is the response's full list of content types.
|
|
873
|
+
* Builds the object schema for one parameter group or a response's headers. Each entry embeds its own schema and JSDoc,
|
|
874
|
+
* so the group is the only type these entries are exported as.
|
|
960
875
|
*/
|
|
961
|
-
function
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
}
|
|
977
|
-
if (resolved.primitive === "string") return entry;
|
|
978
|
-
const responseType = responseTypeForContent(entry);
|
|
979
|
-
if (responseType !== "text" && (resolved.type === "unknown" || resolved.type === "any" || resolved.type === "void")) return entry;
|
|
980
|
-
const isVendorJson = /^[^/]+\/vnd\./.test(baseType) && !/(xml|yaml|yml|csv)$/.test(baseType);
|
|
981
|
-
return responseType === "text" || !responseType && isPlainMediaType && !isVendorJson ? {
|
|
982
|
-
...entry,
|
|
983
|
-
schema: _kubb_kit.ast.factory.createSchema({ type: "string" }),
|
|
984
|
-
keysToOmit: null
|
|
985
|
-
} : entry;
|
|
876
|
+
function buildParams({ params, serializedExamples }) {
|
|
877
|
+
const pointer = params[0]?.pointer;
|
|
878
|
+
return _kubb_kit.ast.factory.createSchema({
|
|
879
|
+
type: "object",
|
|
880
|
+
pointer: pointer?.slice(0, pointer.lastIndexOf("/")),
|
|
881
|
+
properties: params.map((param) => _kubb_kit.ast.factory.createProperty({
|
|
882
|
+
name: param.name,
|
|
883
|
+
required: param.required,
|
|
884
|
+
schema: _kubb_kit.ast.factory.createSchema({
|
|
885
|
+
...param.schema,
|
|
886
|
+
optional: !param.required,
|
|
887
|
+
deprecated: param.deprecated || param.schema.deprecated,
|
|
888
|
+
examples: getExampleValues(param.examples, serializedExamples) ?? param.schema.examples
|
|
889
|
+
})
|
|
890
|
+
}))
|
|
891
|
+
});
|
|
986
892
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
893
|
+
function buildResponseUnion(node, { resolver }) {
|
|
894
|
+
const allWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
895
|
+
const successWithSchema = allWithSchema.filter((res) => _kubb_kit.ast.http.status.isSuccess(res));
|
|
896
|
+
const responsesWithSchema = successWithSchema.length > 0 ? successWithSchema : allWithSchema;
|
|
897
|
+
if (responsesWithSchema.length === 0) return null;
|
|
898
|
+
return _kubb_kit.ast.factory.createSchema({
|
|
899
|
+
type: "union",
|
|
900
|
+
members: responsesWithSchema.map((res) => _kubb_kit.ast.factory.createSchema({
|
|
901
|
+
type: "ref",
|
|
902
|
+
name: resolver.response.status(node, res.statusCode)
|
|
903
|
+
}))
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
// src/printers/printerTs.ts
|
|
907
|
+
const { isNonNullable } = factory_exports;
|
|
908
|
+
const requireModule = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
909
|
+
/**
|
|
910
|
+
* Loads `@kubb/parser-ts` on first use. It is an optional peer dependency: only a deprecated `printer.nodes`
|
|
911
|
+
* handler that returns a TypeScript compiler node needs it. Loads synchronously because node handlers are synchronous.
|
|
912
|
+
*/
|
|
913
|
+
function loadParserTs(load = () => requireModule("@kubb/parser-ts")) {
|
|
914
|
+
try {
|
|
915
|
+
return load();
|
|
916
|
+
} catch (error) {
|
|
917
|
+
if (error.code !== "MODULE_NOT_FOUND" || !error.message.split("\n")[0].includes("@kubb/parser-ts")) throw error;
|
|
918
|
+
throw new _kubb_kit.Diagnostics.Error({
|
|
919
|
+
code: _kubb_kit.Diagnostics.code.missingDependency,
|
|
920
|
+
severity: "error",
|
|
921
|
+
message: "Missing dependency: @kubb/parser-ts. A `printer.nodes` handler returned a TypeScript compiler node, and printing it needs @kubb/parser-ts.",
|
|
922
|
+
help: "Run `npm i -D @kubb/parser-ts` in the project, or return a string from the handler.",
|
|
923
|
+
plugin: "plugin-ts"
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function printNullable(node, print) {
|
|
928
|
+
const type = print(node);
|
|
929
|
+
return type && node.nullable ? createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] }) : type;
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Adapts user overrides, which work in strings, to the node-based printer: `this.transform` hands them printed
|
|
933
|
+
* strings and their result is wrapped back into a node. A deprecated compiler node prints through `@kubb/parser-ts`.
|
|
934
|
+
*/
|
|
935
|
+
function toNodeHandlers(nodes) {
|
|
936
|
+
return Object.fromEntries(Object.entries(nodes ?? {}).map(([type, handler]) => [type, function(node) {
|
|
937
|
+
const transform = (child) => {
|
|
938
|
+
const output = this.transform(child);
|
|
939
|
+
return output ? print(output) : null;
|
|
940
|
+
};
|
|
941
|
+
const result = handler.call({
|
|
942
|
+
...this,
|
|
943
|
+
transform
|
|
944
|
+
}, node);
|
|
945
|
+
if (result === null || result === void 0) return result;
|
|
946
|
+
return createRaw(typeof result === "string" ? result : loadParserTs().parserTs().print(result).trimEnd());
|
|
947
|
+
}]));
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* TypeScript type printer built with `definePrinter`.
|
|
951
|
+
*
|
|
952
|
+
* Converts a `SchemaNode` AST node into TypeScript source:
|
|
953
|
+
* - **`printer.print(node)`** — when `options.name` is set, returns a full
|
|
954
|
+
* `type Name = …` or `interface Name { … }` declaration.
|
|
955
|
+
* Without `name`, returns the type for the schema, such as `string | null`.
|
|
956
|
+
*
|
|
957
|
+
* Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
|
|
958
|
+
* over per printer instance, so each call to `printerTs(options)` produces an independent printer.
|
|
959
|
+
*
|
|
960
|
+
* @example Raw type node (no `typeName`)
|
|
961
|
+
* ```ts
|
|
962
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
|
|
963
|
+
* const type = printer.print(schemaNode) // 'string | null'
|
|
964
|
+
* ```
|
|
965
|
+
*
|
|
966
|
+
* @example Full declaration (with `typeName`)
|
|
967
|
+
* ```ts
|
|
968
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
|
|
969
|
+
* const declaration = printer.print(schemaNode) // 'export type MyType = …'
|
|
970
|
+
* ```
|
|
971
|
+
*/
|
|
972
|
+
const printerTs = (0, _kubb_kit.createPrinter)((options) => {
|
|
973
|
+
const addsUndefined = options.optionalType === "undefined" || options.optionalType === "questionTokenAndUndefined";
|
|
974
|
+
return {
|
|
975
|
+
name: "typescript",
|
|
976
|
+
options,
|
|
977
|
+
nodes: {
|
|
978
|
+
any: () => keywordTypeNodes.any,
|
|
979
|
+
unknown: () => keywordTypeNodes.unknown,
|
|
980
|
+
void: () => keywordTypeNodes.void,
|
|
981
|
+
never: () => keywordTypeNodes.never,
|
|
982
|
+
boolean: () => keywordTypeNodes.boolean,
|
|
983
|
+
null: () => keywordTypeNodes.null,
|
|
984
|
+
blob: () => createTypeReferenceNode("Blob"),
|
|
985
|
+
string: () => keywordTypeNodes.string,
|
|
986
|
+
uuid: () => keywordTypeNodes.string,
|
|
987
|
+
email: () => keywordTypeNodes.string,
|
|
988
|
+
url: (node) => {
|
|
989
|
+
if (node.path) return createUrlTemplateType(node.path);
|
|
990
|
+
return keywordTypeNodes.string;
|
|
991
|
+
},
|
|
992
|
+
ipv4: () => keywordTypeNodes.string,
|
|
993
|
+
ipv6: () => keywordTypeNodes.string,
|
|
994
|
+
datetime: () => keywordTypeNodes.string,
|
|
995
|
+
number: () => keywordTypeNodes.number,
|
|
996
|
+
integer: () => keywordTypeNodes.number,
|
|
997
|
+
bigint: () => keywordTypeNodes.bigint,
|
|
998
|
+
date: dateOrStringNode,
|
|
999
|
+
time: dateOrStringNode,
|
|
1000
|
+
ref(node) {
|
|
1001
|
+
if (!node.name) return null;
|
|
1002
|
+
const refName = _kubb_kit.ast.ref.getName(node);
|
|
1003
|
+
if (!refName) return null;
|
|
1004
|
+
return createTypeReferenceNode(node.ref ? enumTypeName({
|
|
1005
|
+
resolver: this.options.resolver,
|
|
1006
|
+
name: refName,
|
|
1007
|
+
enumOptions: this.options.enum,
|
|
1008
|
+
isEnum: !!this.options.enumSchemaNames?.has(refName)
|
|
1009
|
+
}) : refName);
|
|
1010
|
+
},
|
|
1011
|
+
enum(node) {
|
|
1012
|
+
const values = _kubb_kit.ast.schema.getLiterals(node);
|
|
1013
|
+
if (values.some((value) => value !== null && typeof value === "object")) return createUnionDeclaration({ nodes: values.map((value) => constToTypeNode(value)) });
|
|
1014
|
+
if (this.options.enum.type === "inlineLiteral" || !node.name || isInlineConstEnum(node, this.options.enumSchemaNames)) return createUnionDeclaration({ nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable) });
|
|
1015
|
+
return createTypeReferenceNode(enumTypeName({
|
|
1016
|
+
resolver: this.options.resolver,
|
|
1017
|
+
name: node.name,
|
|
1018
|
+
enumOptions: this.options.enum,
|
|
1019
|
+
isEnum: true
|
|
1020
|
+
}));
|
|
1021
|
+
},
|
|
1022
|
+
union(node) {
|
|
1023
|
+
const members = node.members ?? [];
|
|
1024
|
+
const hasStringLiteral = members.some((m) => {
|
|
1025
|
+
return _kubb_kit.ast.schema.narrow(m, _kubb_kit.ast.schema.types.enum)?.primitive === "string";
|
|
1026
|
+
});
|
|
1027
|
+
const hasPlainString = members.some((m) => _kubb_kit.ast.schema.isString(m));
|
|
1028
|
+
if (hasStringLiteral && hasPlainString) return createUnionDeclaration({ nodes: members.map((m) => {
|
|
1029
|
+
const print = (node) => _kubb_kit.ast.schema.isString(node) ? createIntersectionDeclaration({ nodes: [keywordTypeNodes.string, createTypeLiteralNode([])] }) : this.transform(node);
|
|
1030
|
+
return printNullable(m, print);
|
|
1031
|
+
}).filter(isNonNullable) });
|
|
1032
|
+
return createUnionDeclaration({ nodes: buildMemberNodes(members, (member) => printNullable(member, this.transform)) });
|
|
1033
|
+
},
|
|
1034
|
+
intersection(node) {
|
|
1035
|
+
return createIntersectionDeclaration({ nodes: buildMemberNodes(node.members, this.transform) });
|
|
1036
|
+
},
|
|
1037
|
+
array(node) {
|
|
1038
|
+
return createArrayDeclaration({
|
|
1039
|
+
nodes: (node.items ?? []).map((item) => printNullable(item, this.transform)).filter(isNonNullable),
|
|
1040
|
+
arrayType: this.options.arrayType
|
|
1041
|
+
});
|
|
1042
|
+
},
|
|
1043
|
+
tuple(node) {
|
|
1044
|
+
return buildTupleNode(node, (item) => printNullable(item, this.transform));
|
|
1045
|
+
},
|
|
1046
|
+
object(node) {
|
|
1047
|
+
const { transform, options } = this;
|
|
1048
|
+
const keySchema = node.propertyNames && _kubb_kit.ast.ref.mergeSchema(node.propertyNames);
|
|
1049
|
+
if (!node.properties.length && !Object.keys(node.patternProperties ?? {}).length && node.propertyNames && keySchema?.type === "enum" && node.additionalProperties !== false) {
|
|
1050
|
+
const keyType = transform(node.propertyNames) ?? keywordTypeNodes.string;
|
|
1051
|
+
const valueSchema = typeof node.additionalProperties === "object" ? node.additionalProperties : _kubb_kit.ast.factory.createSchema({ type: "unknown" });
|
|
1052
|
+
return createTypeReferenceNode("Partial", [createTypeReferenceNode("Record", [keyType, buildPropertyType(valueSchema, transform(valueSchema) ?? keywordTypeNodes.unknown, options.optionalType, false)])]);
|
|
1053
|
+
}
|
|
1054
|
+
const addsQuestionToken = options.optionalType === "questionToken" || options.optionalType === "questionTokenAndUndefined";
|
|
1055
|
+
const propertyNodes = node.properties.map((property) => {
|
|
1056
|
+
const { name } = property;
|
|
1057
|
+
const baseType = transform(property.schema) ?? keywordTypeNodes.unknown;
|
|
1058
|
+
const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
|
|
1059
|
+
const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
|
|
1060
|
+
const propMeta = _kubb_kit.ast.ref.mergeSchema(property.schema);
|
|
1061
|
+
const propertyNode = createPropertySignature({
|
|
1062
|
+
questionToken: optional ? addsQuestionToken : false,
|
|
1063
|
+
name,
|
|
1064
|
+
type,
|
|
1065
|
+
readOnly: propMeta?.readOnly
|
|
1066
|
+
});
|
|
1067
|
+
const binaryRemark = isBinarySchema(property.schema) ? "@remarks Pass a `File` to send its filename. A plain `Blob` is sent with the filename `blob`." : void 0;
|
|
1068
|
+
return appendJSDocToNode({
|
|
1069
|
+
node: propertyNode,
|
|
1070
|
+
comments: [...buildPropertyJSDocComments(property.schema, optional), binaryRemark]
|
|
1071
|
+
});
|
|
1072
|
+
});
|
|
1073
|
+
const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)];
|
|
1074
|
+
if (!allElements.length) return keywordTypeNodes.object;
|
|
1075
|
+
return createTypeLiteralNode(allElements);
|
|
1076
|
+
},
|
|
1077
|
+
...toNodeHandlers(options.nodes)
|
|
1078
|
+
},
|
|
1079
|
+
print(node) {
|
|
1080
|
+
const { name, syntaxType = "type", description, examples, comments = [], keysToOmit } = this.options;
|
|
1081
|
+
const transformed = this.transform(node);
|
|
1082
|
+
if (!transformed) return null;
|
|
1083
|
+
const meta = _kubb_kit.ast.ref.mergeSchema(node);
|
|
1084
|
+
if (!name) return print(wrapNullableOptional(keysToOmit?.length ? createOmitDeclaration({
|
|
1085
|
+
keys: keysToOmit,
|
|
1086
|
+
type: transformed,
|
|
1087
|
+
nonNullable: true
|
|
1088
|
+
}) : transformed, !!meta.nullable, !!(meta.nullish || meta.optional) && addsUndefined));
|
|
1089
|
+
return createTypeDeclaration({
|
|
1090
|
+
name,
|
|
1091
|
+
isExportable: true,
|
|
1092
|
+
type: (() => {
|
|
1093
|
+
return wrapNullableOptional(keysToOmit?.length ? createOmitDeclaration({
|
|
1094
|
+
keys: keysToOmit,
|
|
1095
|
+
type: transformed,
|
|
1096
|
+
nonNullable: true
|
|
1097
|
+
}) : transformed, !!meta.nullable, !!(meta.nullish || meta.optional));
|
|
1098
|
+
})(),
|
|
1099
|
+
syntax: syntaxType === "type" || !!keysToOmit?.length ? "type" : "interface",
|
|
1100
|
+
comments: [...comments, ...buildPropertyJSDocComments({
|
|
1101
|
+
...meta,
|
|
1102
|
+
description,
|
|
1103
|
+
examples: examples?.length ? examples : meta.examples
|
|
1104
|
+
})]
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
// src/components/Type.tsx
|
|
1110
|
+
function Type({ name, node, printer, enum: enumOptions, resolver, inline }) {
|
|
1111
|
+
if (inline) {
|
|
1112
|
+
const withoutEnums = _kubb_kit.ast.node.transform(node, { schema: (n) => n.type === _kubb_kit.ast.schema.types.enum && n.name ? {
|
|
1113
|
+
...n,
|
|
1114
|
+
name: void 0
|
|
1115
|
+
} : void 0 });
|
|
1116
|
+
const expression = printerTs({
|
|
1117
|
+
...printer.options,
|
|
1118
|
+
name: void 0
|
|
1119
|
+
}).print(withoutEnums)?.replace(/\/\*\*[\s\S]*?\*\/\s*/g, "");
|
|
1120
|
+
if (!expression) return;
|
|
1121
|
+
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
1122
|
+
name,
|
|
1123
|
+
isTypeOnly: true,
|
|
1124
|
+
isExportable: true,
|
|
1125
|
+
isIndexable: true,
|
|
1126
|
+
inline: { expression },
|
|
1127
|
+
children: expression
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
const enumSchemaNodes = _kubb_kit.ast.node.collect(node, { schema(n) {
|
|
1131
|
+
const enumNode = _kubb_kit.ast.schema.narrow(n, _kubb_kit.ast.schema.types.enum);
|
|
1132
|
+
if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
|
|
1133
|
+
} });
|
|
1134
|
+
const output = printer.print(node);
|
|
1135
|
+
const printerImports = printer.drainImports();
|
|
1136
|
+
if (!output) return;
|
|
1137
|
+
const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
|
|
1138
|
+
return {
|
|
1139
|
+
node,
|
|
1140
|
+
...getEnumNames$1({
|
|
1141
|
+
node,
|
|
1142
|
+
enum: enumOptions,
|
|
1143
|
+
resolver
|
|
1144
|
+
})
|
|
1145
|
+
};
|
|
1146
|
+
});
|
|
1147
|
+
const shouldExportEnums = enumOptions.type !== "inlineLiteral";
|
|
1148
|
+
const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
|
|
1149
|
+
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Enum, {
|
|
1150
|
+
node,
|
|
1151
|
+
enum: enumOptions,
|
|
1152
|
+
resolver
|
|
1153
|
+
}, node.name)), shouldExportType && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [printerImports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, { ...imp }, [
|
|
1154
|
+
name,
|
|
1155
|
+
imp.path,
|
|
1156
|
+
imp.name
|
|
1157
|
+
].join("-"))), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
1158
|
+
name,
|
|
1159
|
+
pointer: node.pointer,
|
|
1160
|
+
isTypeOnly: true,
|
|
1161
|
+
isExportable: true,
|
|
1162
|
+
isIndexable: true,
|
|
1163
|
+
children: output
|
|
1164
|
+
})] })] });
|
|
1165
|
+
}
|
|
1166
|
+
// ../../internals/shared/src/operation.ts
|
|
1167
|
+
/**
|
|
1168
|
+
* Builds the `ResolverFileParams` every operation generator passes to
|
|
1169
|
+
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
1170
|
+
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
1171
|
+
* that was repeated at dozens of call sites across the client and query plugins.
|
|
1172
|
+
*
|
|
1173
|
+
* @example
|
|
1174
|
+
* ```ts
|
|
1175
|
+
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
1176
|
+
* ```
|
|
1177
|
+
*/
|
|
1178
|
+
function operationFileEntry(node, name, extname = ".ts") {
|
|
1179
|
+
return {
|
|
1180
|
+
name,
|
|
1181
|
+
extname,
|
|
1182
|
+
tag: node.tags[0] ?? "default",
|
|
1183
|
+
path: node.path
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Resolves the file a schema-emitting plugin (`plugin-ts`, `plugin-zod`, `plugin-faker`) writes
|
|
1188
|
+
* `node` to, cached in `ctx.memo` under the resolver's plugin name and the
|
|
1189
|
+
* operation. The plugin itself and every plugin that imports from it call this, so they all get
|
|
1190
|
+
* the same path.
|
|
1191
|
+
*
|
|
1192
|
+
* When that path matches the file of a component schema in `ctx.driver.inputNode`, ignoring case, the file name
|
|
1193
|
+
* gets an `Operation` suffix: operation `point` goes to `PointOperation.ts` next to schema `Point.ts`.
|
|
1194
|
+
* Otherwise the two merge into one file, or overwrite each other on a case-insensitive file system.
|
|
1195
|
+
* Only the file name changes. The declarations keep their names.
|
|
1196
|
+
*
|
|
1197
|
+
* @example Resolve `plugin-ts`'s file for the current operation
|
|
1198
|
+
* ```ts
|
|
1199
|
+
* const fileTs = resolveOperationFile({ ctx, node, resolver: tsResolver, root, output })
|
|
1200
|
+
* ```
|
|
1201
|
+
*/
|
|
1202
|
+
function resolveOperationFile(options) {
|
|
1203
|
+
const { ctx, node, resolver, root, output } = options;
|
|
1204
|
+
const { memo } = ctx;
|
|
1205
|
+
const location = {
|
|
1206
|
+
root,
|
|
1207
|
+
output,
|
|
1208
|
+
group: options.group ?? void 0
|
|
1209
|
+
};
|
|
1210
|
+
return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
|
|
1211
|
+
const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
|
|
1212
|
+
name: schema.name,
|
|
1213
|
+
extname: ".ts",
|
|
1214
|
+
...location
|
|
1215
|
+
}).path.toLowerCase()] : [])));
|
|
1216
|
+
let file = resolver.file({
|
|
1217
|
+
...operationFileEntry(node, node.operationId),
|
|
1218
|
+
...location
|
|
1219
|
+
});
|
|
1220
|
+
for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
|
|
1221
|
+
const next = resolver.file({
|
|
1222
|
+
...operationFileEntry(node, name),
|
|
1223
|
+
...location
|
|
1224
|
+
});
|
|
1225
|
+
if (next.path === file.path) break;
|
|
1226
|
+
file = next;
|
|
1227
|
+
}
|
|
1228
|
+
return file;
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
|
|
1233
|
+
*/
|
|
1234
|
+
function getStreamFraming(entry) {
|
|
1235
|
+
if (_kubb_kit.ast.mediaType.getBase(entry) === "text/event-stream") return "events";
|
|
1236
|
+
return entry.stream && _kubb_kit.ast.mediaType.isStream(entry) ? "lines" : void 0;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Parser for one response content entry.
|
|
1240
|
+
*
|
|
1241
|
+
* JSON stays unset so the runtime detects it. `text/event-stream` and JSON Lines are streams. A binary schema
|
|
1242
|
+
* (`format: binary` / `contentMediaType`) is a blob even when the media type is not a well-known
|
|
1243
|
+
* binary type. Other `text/*` is text, and `application/octet-stream`, `application/pdf`,
|
|
1244
|
+
* `image/*`, `audio/*`, and `video/*` are blobs.
|
|
1245
|
+
*/
|
|
1246
|
+
function responseTypeForContent(entry) {
|
|
1247
|
+
const baseType = _kubb_kit.ast.mediaType.getBase(entry);
|
|
1248
|
+
if (!baseType) return void 0;
|
|
1249
|
+
if (getStreamFraming(entry)) return "stream";
|
|
1250
|
+
if (_kubb_kit.ast.mediaType.isJson(baseType)) return void 0;
|
|
1251
|
+
if (entry.schema?.type === "blob") return "blob";
|
|
1252
|
+
if (baseType.startsWith("text/")) return "text";
|
|
1253
|
+
if (isBinaryContentType(baseType)) return "blob";
|
|
1254
|
+
}
|
|
1255
|
+
function isBinaryContentType(baseType) {
|
|
1256
|
+
return baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType);
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
1260
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
1261
|
+
*/
|
|
1262
|
+
function getContentTypeSuffix(contentType) {
|
|
1263
|
+
const baseType = contentType.split(";")[0].trim();
|
|
1264
|
+
if (baseType === "application/json") return "Json";
|
|
1265
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
1266
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
1267
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
1268
|
+
if (parts.length === 0) return "Unknown";
|
|
1269
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
1273
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
1274
|
+
*/
|
|
1275
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
1276
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
1277
|
+
return baseName + suffix;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Returns the entry with the schema of the value the client returns: `string` for a body read as text, such as `Pet` on
|
|
1281
|
+
* `application/xml`, and `blob` for `image/*`, `audio/*`, `video/*`, PDF, and octet-stream. A binary schema elsewhere is a `Blob`
|
|
1282
|
+
* only through `parseAs: 'blob'`, which needs every entry in `entries` binary. JSON, vendor (`application/vnd.*`), stream,
|
|
1283
|
+
* schemaless, and wildcard entries keep their schema. `entries` is the response's full list of content types.
|
|
1284
|
+
*/
|
|
1285
|
+
function toResponseContent(entry, entries = [entry]) {
|
|
1286
|
+
const { contentType, schema } = entry;
|
|
1287
|
+
if (!schema || entry.stream) return entry;
|
|
1288
|
+
const baseType = _kubb_kit.ast.mediaType.getBase(contentType);
|
|
1289
|
+
const resolved = _kubb_kit.ast.ref.getSchema(schema);
|
|
1290
|
+
if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
|
|
1291
|
+
...entry,
|
|
1292
|
+
schema: _kubb_kit.ast.factory.createSchema({ type: "blob" }),
|
|
1293
|
+
keysToOmit: null
|
|
1294
|
+
};
|
|
1295
|
+
const isPlainMediaType = Boolean(baseType) && !_kubb_kit.ast.mediaType.isJson(baseType) && !baseType.includes("*");
|
|
1296
|
+
if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
|
|
1297
|
+
...entry,
|
|
1298
|
+
schema: _kubb_kit.ast.factory.createSchema({ type: "string" }),
|
|
1299
|
+
keysToOmit: null
|
|
1300
|
+
} : entry;
|
|
1301
|
+
if (resolved.primitive === "string") return entry;
|
|
1302
|
+
const responseType = responseTypeForContent(entry);
|
|
1303
|
+
if (responseType !== "text" && (resolved.type === "unknown" || resolved.type === "any" || resolved.type === "void")) return entry;
|
|
1304
|
+
const isVendorJson = /^[^/]+\/vnd\./.test(baseType) && !/(xml|yaml|yml|csv)$/.test(baseType);
|
|
1305
|
+
return responseType === "text" || !responseType && isPlainMediaType && !isVendorJson ? {
|
|
1306
|
+
...entry,
|
|
1307
|
+
schema: _kubb_kit.ast.factory.createSchema({ type: "string" }),
|
|
1308
|
+
keysToOmit: null
|
|
1309
|
+
} : entry;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
1313
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
1314
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
1315
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
1316
|
+
*/
|
|
1317
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
1318
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
1319
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
1320
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
1321
|
+
let suffix = baseSuffix;
|
|
998
1322
|
let name = getPerContentTypeName(baseName, suffix);
|
|
999
1323
|
let counter = 2;
|
|
1000
1324
|
while (usedNames.has(name)) {
|
|
@@ -1011,50 +1335,6 @@ function resolveContentTypeVariants(entries, baseName) {
|
|
|
1011
1335
|
};
|
|
1012
1336
|
});
|
|
1013
1337
|
}
|
|
1014
|
-
const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
|
|
1015
|
-
/**
|
|
1016
|
-
* An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
|
|
1017
|
-
* per property, typed and serialized like any other.
|
|
1018
|
-
* Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
|
|
1019
|
-
*/
|
|
1020
|
-
function spreadQuerystring(param) {
|
|
1021
|
-
const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
|
|
1022
|
-
const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
|
|
1023
|
-
if (schema?.type !== "object" || !formEncoded) return [param];
|
|
1024
|
-
return schema.properties.map((property) => _kubb_kit.ast.factory.createParameter({
|
|
1025
|
-
name: property.name,
|
|
1026
|
-
in: "query",
|
|
1027
|
-
schema: property.schema,
|
|
1028
|
-
required: param.required && property.required
|
|
1029
|
-
}));
|
|
1030
|
-
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
|
|
1033
|
-
* group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
|
|
1034
|
-
* (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
|
|
1035
|
-
* same parameters once per plugin.
|
|
1036
|
-
*/
|
|
1037
|
-
function getOperationParameters(node) {
|
|
1038
|
-
const cached = operationParameterGroupsByNode.get(node);
|
|
1039
|
-
if (cached) return cached;
|
|
1040
|
-
const dedupe = (params) => {
|
|
1041
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1042
|
-
return params.filter((param) => {
|
|
1043
|
-
if (seen.has(param.name)) return false;
|
|
1044
|
-
seen.add(param.name);
|
|
1045
|
-
return true;
|
|
1046
|
-
});
|
|
1047
|
-
};
|
|
1048
|
-
const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
|
|
1049
|
-
const groups = {
|
|
1050
|
-
path: group("path"),
|
|
1051
|
-
query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
|
|
1052
|
-
header: group("header"),
|
|
1053
|
-
cookie: group("cookie")
|
|
1054
|
-
};
|
|
1055
|
-
operationParameterGroupsByNode.set(node, groups);
|
|
1056
|
-
return groups;
|
|
1057
|
-
}
|
|
1058
1338
|
/**
|
|
1059
1339
|
* Whether the operation declares more than one request media type with a schema, so a caller must pick one on `contentType`.
|
|
1060
1340
|
*/
|
|
@@ -1070,8 +1350,8 @@ function hasRequestContentTypeChoice(node) {
|
|
|
1070
1350
|
* than a record.
|
|
1071
1351
|
*/
|
|
1072
1352
|
function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
|
|
1073
|
-
const { path, query, header, cookie } =
|
|
1074
|
-
const hasBody = Boolean(
|
|
1353
|
+
const { path, query, header, cookie } = _kubb_kit.ast.http.groupParameters(node);
|
|
1354
|
+
const hasBody = Boolean(_kubb_kit.ast.http.requestBody.getSchema(node));
|
|
1075
1355
|
const createNever = () => _kubb_kit.ast.factory.createSchema({
|
|
1076
1356
|
type: "never",
|
|
1077
1357
|
primitive: void 0,
|
|
@@ -1162,9 +1442,10 @@ function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
|
|
|
1162
1442
|
* The schema a status occupies in the `<Name>Responses` record. A status that documents several
|
|
1163
1443
|
* content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
|
|
1164
1444
|
* on `result.parsed`, while the standalone `<Name>StatusNNN` alias stays the plain body union that the
|
|
1165
|
-
* query hooks and `result.data` use.
|
|
1445
|
+
* query hooks and `result.data` use. With `streamData`, the stream variant of a 2xx status holds that schema, since the
|
|
1446
|
+
* client returns its body unread.
|
|
1166
1447
|
*/
|
|
1167
|
-
function buildResponseRecordEntry(node, res, resolver) {
|
|
1448
|
+
function buildResponseRecordEntry(node, res, resolver, streamData) {
|
|
1168
1449
|
const statusName = resolver.response.status(node, res.statusCode);
|
|
1169
1450
|
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
1170
1451
|
if (variants.length <= 1) return _kubb_kit.ast.factory.createSchema({
|
|
@@ -1181,12 +1462,12 @@ function buildResponseRecordEntry(node, res, resolver) {
|
|
|
1181
1462
|
required: true,
|
|
1182
1463
|
schema: _kubb_kit.ast.factory.createSchema({
|
|
1183
1464
|
type: "enum",
|
|
1184
|
-
enumValues: [
|
|
1465
|
+
enumValues: [_kubb_kit.ast.mediaType.getBase(variant)]
|
|
1185
1466
|
})
|
|
1186
1467
|
}), _kubb_kit.ast.factory.createProperty({
|
|
1187
1468
|
name: "data",
|
|
1188
1469
|
required: true,
|
|
1189
|
-
schema: _kubb_kit.ast.factory.createSchema({
|
|
1470
|
+
schema: streamData && _kubb_kit.ast.http.status.isSuccess(res) && _kubb_kit.ast.mediaType.isStream(variant) ? streamData : _kubb_kit.ast.factory.createSchema({
|
|
1190
1471
|
type: "ref",
|
|
1191
1472
|
name: variant.name
|
|
1192
1473
|
})
|
|
@@ -1202,26 +1483,20 @@ function buildResponseRecordEntry(node, res, resolver) {
|
|
|
1202
1483
|
* Always emits the keyed record, even when an operation declares no responses. An operation with no
|
|
1203
1484
|
* responses renders as an empty object, which keeps every consumer's import (for example the axios
|
|
1204
1485
|
* SDK's `RequestResult<XResponses>`) resolvable instead of pointing at a missing export.
|
|
1486
|
+
*
|
|
1487
|
+
* `streamData` is the type of a stream variant's `data`, such as a `ReadableStream<Uint8Array>` reference.
|
|
1205
1488
|
*/
|
|
1206
|
-
function buildResponses(node, resolver) {
|
|
1489
|
+
function buildResponses(node, resolver, { streamData } = {}) {
|
|
1207
1490
|
return _kubb_kit.ast.factory.createSchema({
|
|
1208
1491
|
type: "object",
|
|
1209
1492
|
primitive: "object",
|
|
1210
1493
|
properties: node.responses.map((res) => _kubb_kit.ast.factory.createProperty({
|
|
1211
1494
|
name: String(res.statusCode),
|
|
1212
1495
|
required: true,
|
|
1213
|
-
schema: buildResponseRecordEntry(node, res, resolver)
|
|
1496
|
+
schema: buildResponseRecordEntry(node, res, resolver, streamData)
|
|
1214
1497
|
}))
|
|
1215
1498
|
});
|
|
1216
1499
|
}
|
|
1217
|
-
function getStatusCodeNumber(statusCode) {
|
|
1218
|
-
const code = Number(statusCode);
|
|
1219
|
-
return Number.isNaN(code) ? null : code;
|
|
1220
|
-
}
|
|
1221
|
-
function isSuccessStatusCode(statusCode) {
|
|
1222
|
-
const code = getStatusCodeNumber(statusCode);
|
|
1223
|
-
return code !== null && code >= 200 && code < 300;
|
|
1224
|
-
}
|
|
1225
1500
|
// ../../internals/shared/src/resolver.ts
|
|
1226
1501
|
/**
|
|
1227
1502
|
* Builds the allocation key for a declaration an operation owns. `scope` names which declaration
|
|
@@ -1275,516 +1550,90 @@ function createOperationParamResolver() {
|
|
|
1275
1550
|
name
|
|
1276
1551
|
});
|
|
1277
1552
|
},
|
|
1278
|
-
headers(node) {
|
|
1279
|
-
const name = this.name(`${node.operationId} Headers`);
|
|
1280
|
-
return this.resolveName({
|
|
1281
|
-
key: operationKey(node, "param", "headers"),
|
|
1282
|
-
name
|
|
1283
|
-
});
|
|
1284
|
-
},
|
|
1285
|
-
cookies(node) {
|
|
1286
|
-
const name = this.name(`${node.operationId} Cookies`);
|
|
1287
|
-
return this.resolveName({
|
|
1288
|
-
key: operationKey(node, "param", "cookies"),
|
|
1289
|
-
name
|
|
1290
|
-
});
|
|
1291
|
-
}
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
/**
|
|
1295
|
-
* Builds the shared `response` namespace. Spread the result into
|
|
1296
|
-
* `createResolver` and add plugin-specific methods (`options`, `error`) next
|
|
1297
|
-
* to it.
|
|
1298
|
-
*
|
|
1299
|
-
* @example
|
|
1300
|
-
* ```ts
|
|
1301
|
-
* createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
|
|
1302
|
-
* ```
|
|
1303
|
-
*/
|
|
1304
|
-
function createOperationResponseResolver() {
|
|
1305
|
-
return {
|
|
1306
|
-
status(node, statusCode) {
|
|
1307
|
-
const name = this.name(`${node.operationId} Status ${statusCode}`);
|
|
1308
|
-
return this.resolveName({
|
|
1309
|
-
key: operationKey(node, "response", "status", statusCode),
|
|
1310
|
-
name
|
|
1311
|
-
});
|
|
1312
|
-
},
|
|
1313
|
-
body(node) {
|
|
1314
|
-
const name = this.name(`${node.operationId} Body`);
|
|
1315
|
-
return this.resolveName({
|
|
1316
|
-
key: operationKey(node, "response", "body"),
|
|
1317
|
-
name
|
|
1318
|
-
});
|
|
1319
|
-
},
|
|
1320
|
-
responses(node) {
|
|
1321
|
-
const name = this.name(`${node.operationId} Responses`);
|
|
1322
|
-
return this.resolveName({
|
|
1323
|
-
key: operationKey(node, "response", "responses"),
|
|
1324
|
-
name
|
|
1325
|
-
});
|
|
1326
|
-
},
|
|
1327
|
-
response(node) {
|
|
1328
|
-
const name = this.name(`${node.operationId} Response`);
|
|
1329
|
-
return this.resolveName({
|
|
1330
|
-
key: operationKey(node, "response", "response"),
|
|
1331
|
-
name
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
};
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Builds a resolver `file` override whose base name runs every path segment
|
|
1338
|
-
* through `toFilePath`, casing the final segment with `caseLast`.
|
|
1339
|
-
*
|
|
1340
|
-
* @example
|
|
1341
|
-
* ```ts
|
|
1342
|
-
* createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
|
|
1343
|
-
* ```
|
|
1344
|
-
*/
|
|
1345
|
-
function createCasedFile(caseLast) {
|
|
1346
|
-
return { baseName({ name, extname }) {
|
|
1347
|
-
return this.memo(`file:${name}\0${extname}`, () => `${toFilePath(name, caseLast)}${extname}`);
|
|
1348
|
-
} };
|
|
1349
|
-
}
|
|
1350
|
-
/**
|
|
1351
|
-
* Names of the schemas whose type is `enum`, computed once per build.
|
|
1352
|
-
*/
|
|
1353
|
-
function getEnumNames(ctx) {
|
|
1354
|
-
return ctx.memo("enumNames:set", () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.type === "enum" && schema.name ? [schema.name] : [])));
|
|
1355
|
-
}
|
|
1356
|
-
// ../../internals/shared/src/group.ts
|
|
1357
|
-
/**
|
|
1358
|
-
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
1359
|
-
* shared default naming so every plugin groups output consistently:
|
|
1360
|
-
*
|
|
1361
|
-
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
1362
|
-
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
1363
|
-
*
|
|
1364
|
-
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
1365
|
-
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
1366
|
-
* per-plugin convention.
|
|
1367
|
-
*
|
|
1368
|
-
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
1369
|
-
*
|
|
1370
|
-
* @example
|
|
1371
|
-
* ```ts
|
|
1372
|
-
* createGroupConfig(group) // shared across every plugin
|
|
1373
|
-
* ```
|
|
1374
|
-
*/
|
|
1375
|
-
function createGroupConfig(group) {
|
|
1376
|
-
if (!group) return null;
|
|
1377
|
-
const defaultName = (ctx) => {
|
|
1378
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1379
|
-
return (0, _kubb_kit.camelCase)(ctx.group);
|
|
1380
|
-
};
|
|
1381
|
-
return {
|
|
1382
|
-
...group,
|
|
1383
|
-
name: group.name ? group.name : defaultName
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1386
|
-
// ../../internals/shared/src/schemaTraversal.ts
|
|
1387
|
-
/**
|
|
1388
|
-
* Maps each property of an object schema to its transformed output. Pairs every result with the
|
|
1389
|
-
* original property so the printer keeps full control over modifiers, getters, and key syntax.
|
|
1390
|
-
*
|
|
1391
|
-
* @example
|
|
1392
|
-
* ```ts
|
|
1393
|
-
* const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
|
|
1394
|
-
* // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
|
|
1395
|
-
* ```
|
|
1396
|
-
*/
|
|
1397
|
-
function mapSchemaProperties(node, transform) {
|
|
1398
|
-
return node.properties.map((property) => ({
|
|
1399
|
-
name: property.name,
|
|
1400
|
-
property,
|
|
1401
|
-
output: transform(property.schema)
|
|
1402
|
-
}));
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Maps each item of an array or tuple schema to its transformed output, pairing every result with
|
|
1406
|
-
* the original item.
|
|
1407
|
-
*/
|
|
1408
|
-
function mapSchemaItems(node, transform) {
|
|
1409
|
-
return (node.items ?? []).map((schema) => ({
|
|
1410
|
-
schema,
|
|
1411
|
-
output: transform(schema)
|
|
1412
|
-
}));
|
|
1413
|
-
}
|
|
1414
|
-
// src/utils.ts
|
|
1415
|
-
/**
|
|
1416
|
-
* Renames inline enum declarations and their uses together.
|
|
1417
|
-
*/
|
|
1418
|
-
function resolveInlineEnumNames({ node, owner, resolver, enumOptions }) {
|
|
1419
|
-
return _kubb_kit.ast.transform(node, { schema(child) {
|
|
1420
|
-
if (child === node || child.type !== "enum" || !child.name) return;
|
|
1421
|
-
const name = resolver.resolveName({
|
|
1422
|
-
key: [
|
|
1423
|
-
"enum",
|
|
1424
|
-
owner,
|
|
1425
|
-
child.pointer ?? child.name
|
|
1426
|
-
],
|
|
1427
|
-
name: child.name,
|
|
1428
|
-
normalize: (value) => {
|
|
1429
|
-
const { enumName, typeName } = getEnumNames$1({
|
|
1430
|
-
node: {
|
|
1431
|
-
...child,
|
|
1432
|
-
name: value
|
|
1433
|
-
},
|
|
1434
|
-
enum: enumOptions,
|
|
1435
|
-
resolver
|
|
1436
|
-
});
|
|
1437
|
-
return enumOptions.type === "literal" ? [typeName] : [enumName, typeName];
|
|
1438
|
-
}
|
|
1439
|
-
});
|
|
1440
|
-
return name === child.name ? void 0 : {
|
|
1441
|
-
...child,
|
|
1442
|
-
name
|
|
1443
|
-
};
|
|
1444
|
-
} });
|
|
1445
|
-
}
|
|
1446
|
-
function enumTypeName({ resolver, name, enumOptions, isEnum }) {
|
|
1447
|
-
return isEnum && enumOptions?.type === "asConst" && enumOptions.typeSuffix ? resolver.enum.keyName({ name }, enumOptions.typeSuffix) : resolver.name(name);
|
|
1448
|
-
}
|
|
1449
|
-
/**
|
|
1450
|
-
* Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
|
|
1451
|
-
* rather than a named enum reference or a runtime enum declaration.
|
|
1452
|
-
*
|
|
1453
|
-
* The parser folds `const` into a single-value enum node. The adapter decides which schemas are
|
|
1454
|
-
* named enums and lists them in `enumSchemaNames`, and references to those names get suffixed (for
|
|
1455
|
-
* example `StatusKey`). A const renders as a literal only when the adapter has not registered it as
|
|
1456
|
-
* a named enum, which keeps the declaration and its references in sync across adapter versions.
|
|
1457
|
-
*/
|
|
1458
|
-
function isInlineConstEnum(node, enumSchemaNames) {
|
|
1459
|
-
return (node.namedEnumValues ?? node.enumValues ?? []).length === 1 && !(node.name && enumSchemaNames?.has(node.name));
|
|
1460
|
-
}
|
|
1461
|
-
/**
|
|
1462
|
-
* Collects JSDoc annotation strings for a schema node.
|
|
1463
|
-
*
|
|
1464
|
-
* Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@default`, `@example`, `@type`.
|
|
1465
|
-
* Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
|
|
1466
|
-
|
|
1467
|
-
*/
|
|
1468
|
-
function isSchemaOptional(schema) {
|
|
1469
|
-
return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
|
|
1470
|
-
}
|
|
1471
|
-
function formatExample(value) {
|
|
1472
|
-
if (value === null || typeof value !== "object") return String(value);
|
|
1473
|
-
return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
|
|
1474
|
-
}
|
|
1475
|
-
/**
|
|
1476
|
-
* Whether a schema is binary, or an array of binary items, so its TypeScript type is `Blob` or `Blob[]`.
|
|
1477
|
-
*/
|
|
1478
|
-
function isBinarySchema(schema) {
|
|
1479
|
-
const meta = (0, _kubb_kit.syncSchemaRef)(schema);
|
|
1480
|
-
const isBlob = (node) => (0, _kubb_kit.syncSchemaRef)(node)?.type === "blob";
|
|
1481
|
-
return !!meta && (isBlob(meta) || meta.type === "array" && "items" in meta && (meta.items ?? []).some(isBlob));
|
|
1482
|
-
}
|
|
1483
|
-
function buildPropertyJSDocComments(schema, optional) {
|
|
1484
|
-
const meta = (0, _kubb_kit.syncSchemaRef)(schema);
|
|
1485
|
-
const isArray = meta?.primitive === "array";
|
|
1486
|
-
const isNumeric = meta?.primitive === "number" || meta?.primitive === "integer" || meta?.primitive === "bigint";
|
|
1487
|
-
const hasDescription = meta && "description" in meta && meta.description;
|
|
1488
|
-
const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
|
|
1489
|
-
const exampleValues = meta?.examples ?? [];
|
|
1490
|
-
const comments = [
|
|
1491
|
-
hasDescription ? `@description ${jsDocEscape(meta.description)}` : null,
|
|
1492
|
-
...formatComment,
|
|
1493
|
-
meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
|
|
1494
|
-
!isArray && meta && "min" in meta && meta.min !== void 0 ? `@${isNumeric ? "minimum" : "minLength"} ${meta.min}` : null,
|
|
1495
|
-
!isArray && meta && "max" in meta && meta.max !== void 0 ? `@${isNumeric ? "maximum" : "maxLength"} ${meta.max}` : null,
|
|
1496
|
-
meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
|
|
1497
|
-
meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" && typeof meta.default === "string" ? stringify(meta.default) : formatExample(meta.default)}` : null,
|
|
1498
|
-
...exampleValues.map((example) => `@example ${formatExample(example)}`)
|
|
1499
|
-
].filter(Boolean);
|
|
1500
|
-
const typeTag = comments.length && !isBinarySchema(schema) && meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null;
|
|
1501
|
-
return [...comments, typeTag].filter(Boolean);
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* Builds the object schema for a group of parameters sharing one `in` location (path, query, or
|
|
1505
|
-
* header), embedding each param's own schema (and JSDoc) directly rather than referencing a
|
|
1506
|
-
* separate per-param type — the group itself is the only type these params get exported as.
|
|
1507
|
-
*/
|
|
1508
|
-
function buildParams({ params }) {
|
|
1509
|
-
return _kubb_kit.ast.factory.createSchema({
|
|
1510
|
-
type: "object",
|
|
1511
|
-
pointer: params[0]?.pointer?.replace(/\/\d+$/, ""),
|
|
1512
|
-
properties: params.map((param) => _kubb_kit.ast.factory.createProperty({
|
|
1513
|
-
name: param.name,
|
|
1514
|
-
required: param.required,
|
|
1515
|
-
schema: _kubb_kit.ast.factory.createSchema({
|
|
1516
|
-
...param.schema,
|
|
1517
|
-
optional: !param.required
|
|
1518
|
-
})
|
|
1519
|
-
}))
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
function buildResponseUnion(node, { resolver }) {
|
|
1523
|
-
const allWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
1524
|
-
const successWithSchema = allWithSchema.filter((res) => isSuccessStatusCode(res.statusCode));
|
|
1525
|
-
const responsesWithSchema = successWithSchema.length > 0 ? successWithSchema : allWithSchema;
|
|
1526
|
-
if (responsesWithSchema.length === 0) return null;
|
|
1527
|
-
return _kubb_kit.ast.factory.createSchema({
|
|
1528
|
-
type: "union",
|
|
1529
|
-
members: responsesWithSchema.map((res) => _kubb_kit.ast.factory.createSchema({
|
|
1530
|
-
type: "ref",
|
|
1531
|
-
name: resolver.response.status(node, res.statusCode)
|
|
1532
|
-
}))
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
|
-
// src/printers/printerTs.ts
|
|
1536
|
-
const { isNonNullable } = factory_exports;
|
|
1537
|
-
const requireModule = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
1538
|
-
/**
|
|
1539
|
-
* Loads `@kubb/parser-ts` on first use. It is an optional peer dependency: only a deprecated `printer.nodes`
|
|
1540
|
-
* handler that returns a TypeScript compiler node needs it. Loads synchronously because node handlers are synchronous.
|
|
1541
|
-
*/
|
|
1542
|
-
function loadParserTs(load = () => requireModule("@kubb/parser-ts")) {
|
|
1543
|
-
try {
|
|
1544
|
-
return load();
|
|
1545
|
-
} catch (error) {
|
|
1546
|
-
if (error.code !== "MODULE_NOT_FOUND" || !error.message.split("\n")[0].includes("@kubb/parser-ts")) throw error;
|
|
1547
|
-
throw new _kubb_kit.Diagnostics.Error({
|
|
1548
|
-
code: _kubb_kit.Diagnostics.code.missingDependency,
|
|
1549
|
-
severity: "error",
|
|
1550
|
-
message: "Missing dependency: @kubb/parser-ts. A `printer.nodes` handler returned a TypeScript compiler node, and printing it needs @kubb/parser-ts.",
|
|
1551
|
-
help: "Run `npm i -D @kubb/parser-ts` in the project, or return a string from the handler.",
|
|
1552
|
-
plugin: "plugin-ts"
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
/**
|
|
1557
|
-
* Adapts user overrides, which work in strings, to the node-based printer: `this.transform` hands them printed
|
|
1558
|
-
* strings and their result is wrapped back into a node. A deprecated compiler node prints through `@kubb/parser-ts`.
|
|
1559
|
-
*/
|
|
1560
|
-
function toNodeHandlers(nodes) {
|
|
1561
|
-
return Object.fromEntries(Object.entries(nodes ?? {}).map(([type, handler]) => [type, function(node) {
|
|
1562
|
-
const transform = (child) => {
|
|
1563
|
-
const output = this.transform(child);
|
|
1564
|
-
return output ? print(output) : null;
|
|
1565
|
-
};
|
|
1566
|
-
const result = handler.call({
|
|
1567
|
-
...this,
|
|
1568
|
-
transform
|
|
1569
|
-
}, node);
|
|
1570
|
-
if (result === null || result === void 0) return result;
|
|
1571
|
-
return createRaw(typeof result === "string" ? result : loadParserTs().parserTs().print(result).trimEnd());
|
|
1572
|
-
}]));
|
|
1553
|
+
headers(node) {
|
|
1554
|
+
const name = this.name(`${node.operationId} Headers`);
|
|
1555
|
+
return this.resolveName({
|
|
1556
|
+
key: operationKey(node, "param", "headers"),
|
|
1557
|
+
name
|
|
1558
|
+
});
|
|
1559
|
+
},
|
|
1560
|
+
cookies(node) {
|
|
1561
|
+
const name = this.name(`${node.operationId} Cookies`);
|
|
1562
|
+
return this.resolveName({
|
|
1563
|
+
key: operationKey(node, "param", "cookies"),
|
|
1564
|
+
name
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1573
1568
|
}
|
|
1574
1569
|
/**
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1578
|
-
* - **`printer.print(node)`** — when `options.name` is set, returns a full
|
|
1579
|
-
* `type Name = …` or `interface Name { … }` declaration.
|
|
1580
|
-
* Without `name`, returns the type for the schema, such as `string | null`.
|
|
1581
|
-
*
|
|
1582
|
-
* Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
|
|
1583
|
-
* over per printer instance, so each call to `printerTs(options)` produces an independent printer.
|
|
1584
|
-
*
|
|
1585
|
-
* @example Raw type node (no `typeName`)
|
|
1586
|
-
* ```ts
|
|
1587
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
|
|
1588
|
-
* const type = printer.print(schemaNode) // 'string | null'
|
|
1589
|
-
* ```
|
|
1570
|
+
* Builds the shared `response` namespace. Spread the result into
|
|
1571
|
+
* `createResolver` and add plugin-specific methods (`options`, `error`) next
|
|
1572
|
+
* to it.
|
|
1590
1573
|
*
|
|
1591
|
-
* @example
|
|
1574
|
+
* @example
|
|
1592
1575
|
* ```ts
|
|
1593
|
-
*
|
|
1594
|
-
* const declaration = printer.print(schemaNode) // 'export type MyType = …'
|
|
1576
|
+
* createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
|
|
1595
1577
|
* ```
|
|
1596
1578
|
*/
|
|
1597
|
-
|
|
1598
|
-
const addsUndefined = options.optionalType === "undefined" || options.optionalType === "questionTokenAndUndefined";
|
|
1579
|
+
function createOperationResponseResolver() {
|
|
1599
1580
|
return {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
never: () => keywordTypeNodes.never,
|
|
1607
|
-
boolean: () => keywordTypeNodes.boolean,
|
|
1608
|
-
null: () => keywordTypeNodes.null,
|
|
1609
|
-
blob: () => createTypeReferenceNode("Blob"),
|
|
1610
|
-
string: () => keywordTypeNodes.string,
|
|
1611
|
-
uuid: () => keywordTypeNodes.string,
|
|
1612
|
-
email: () => keywordTypeNodes.string,
|
|
1613
|
-
url: (node) => {
|
|
1614
|
-
if (node.path) return createUrlTemplateType(node.path);
|
|
1615
|
-
return keywordTypeNodes.string;
|
|
1616
|
-
},
|
|
1617
|
-
ipv4: () => keywordTypeNodes.string,
|
|
1618
|
-
ipv6: () => keywordTypeNodes.string,
|
|
1619
|
-
datetime: () => keywordTypeNodes.string,
|
|
1620
|
-
number: () => keywordTypeNodes.number,
|
|
1621
|
-
integer: () => keywordTypeNodes.number,
|
|
1622
|
-
bigint: () => keywordTypeNodes.bigint,
|
|
1623
|
-
date: dateOrStringNode,
|
|
1624
|
-
time: dateOrStringNode,
|
|
1625
|
-
ref(node) {
|
|
1626
|
-
if (!node.name) return null;
|
|
1627
|
-
const refName = _kubb_kit.ast.resolveRefName(node);
|
|
1628
|
-
if (!refName) return null;
|
|
1629
|
-
return createTypeReferenceNode(node.ref ? enumTypeName({
|
|
1630
|
-
resolver: this.options.resolver,
|
|
1631
|
-
name: refName,
|
|
1632
|
-
enumOptions: this.options.enum,
|
|
1633
|
-
isEnum: !!this.options.enumSchemaNames?.has(refName)
|
|
1634
|
-
}) : refName);
|
|
1635
|
-
},
|
|
1636
|
-
enum(node) {
|
|
1637
|
-
const values = _kubb_kit.ast.getSchemaLiteralValues(node);
|
|
1638
|
-
if (values.some((value) => value !== null && typeof value === "object")) return createUnionDeclaration({ nodes: values.map((value) => constToTypeNode(value)) });
|
|
1639
|
-
if (this.options.enum.type === "inlineLiteral" || !node.name || isInlineConstEnum(node, this.options.enumSchemaNames)) return createUnionDeclaration({ nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable) });
|
|
1640
|
-
return createTypeReferenceNode(enumTypeName({
|
|
1641
|
-
resolver: this.options.resolver,
|
|
1642
|
-
name: node.name,
|
|
1643
|
-
enumOptions: this.options.enum,
|
|
1644
|
-
isEnum: true
|
|
1645
|
-
}));
|
|
1646
|
-
},
|
|
1647
|
-
union(node) {
|
|
1648
|
-
const members = node.members ?? [];
|
|
1649
|
-
const hasStringLiteral = members.some((m) => {
|
|
1650
|
-
return _kubb_kit.ast.narrowSchema(m, _kubb_kit.ast.schemaTypes.enum)?.primitive === "string";
|
|
1651
|
-
});
|
|
1652
|
-
const hasPlainString = members.some((m) => (0, _kubb_kit.isStringType)(m));
|
|
1653
|
-
if (hasStringLiteral && hasPlainString) return createUnionDeclaration({ nodes: members.map((m) => {
|
|
1654
|
-
if ((0, _kubb_kit.isStringType)(m)) return createIntersectionDeclaration({ nodes: [keywordTypeNodes.string, createTypeLiteralNode([])] });
|
|
1655
|
-
return this.transform(m);
|
|
1656
|
-
}).filter(isNonNullable) });
|
|
1657
|
-
return createUnionDeclaration({ nodes: buildMemberNodes(members, this.transform) });
|
|
1658
|
-
},
|
|
1659
|
-
intersection(node) {
|
|
1660
|
-
return createIntersectionDeclaration({ nodes: buildMemberNodes(node.members, this.transform) });
|
|
1661
|
-
},
|
|
1662
|
-
array(node) {
|
|
1663
|
-
return createArrayDeclaration({
|
|
1664
|
-
nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
|
|
1665
|
-
arrayType: this.options.arrayType
|
|
1666
|
-
});
|
|
1667
|
-
},
|
|
1668
|
-
tuple(node) {
|
|
1669
|
-
return buildTupleNode(node, this.transform);
|
|
1670
|
-
},
|
|
1671
|
-
object(node) {
|
|
1672
|
-
const { transform, options } = this;
|
|
1673
|
-
const keySchema = node.propertyNames && (0, _kubb_kit.syncSchemaRef)(node.propertyNames);
|
|
1674
|
-
if (!node.properties.length && !Object.keys(node.patternProperties ?? {}).length && node.propertyNames && keySchema?.type === "enum" && node.additionalProperties !== false) {
|
|
1675
|
-
const keyType = transform(node.propertyNames) ?? keywordTypeNodes.string;
|
|
1676
|
-
const valueSchema = typeof node.additionalProperties === "object" ? node.additionalProperties : _kubb_kit.ast.factory.createSchema({ type: "unknown" });
|
|
1677
|
-
return createTypeReferenceNode("Partial", [createTypeReferenceNode("Record", [keyType, buildPropertyType(valueSchema, transform(valueSchema) ?? keywordTypeNodes.unknown, options.optionalType, false)])]);
|
|
1678
|
-
}
|
|
1679
|
-
const addsQuestionToken = options.optionalType === "questionToken" || options.optionalType === "questionTokenAndUndefined";
|
|
1680
|
-
const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
|
|
1681
|
-
const baseType = output ?? keywordTypeNodes.unknown;
|
|
1682
|
-
const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
|
|
1683
|
-
const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
|
|
1684
|
-
const propMeta = (0, _kubb_kit.syncSchemaRef)(property.schema);
|
|
1685
|
-
const propertyNode = createPropertySignature({
|
|
1686
|
-
questionToken: optional ? addsQuestionToken : false,
|
|
1687
|
-
name,
|
|
1688
|
-
type,
|
|
1689
|
-
readOnly: propMeta?.readOnly
|
|
1690
|
-
});
|
|
1691
|
-
const binaryRemark = isBinarySchema(property.schema) ? "@remarks Pass a `File` to send its filename. A plain `Blob` is sent with the filename `blob`." : void 0;
|
|
1692
|
-
return appendJSDocToNode({
|
|
1693
|
-
node: propertyNode,
|
|
1694
|
-
comments: [...buildPropertyJSDocComments(property.schema, optional), binaryRemark]
|
|
1695
|
-
});
|
|
1696
|
-
});
|
|
1697
|
-
const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)];
|
|
1698
|
-
if (!allElements.length) return keywordTypeNodes.object;
|
|
1699
|
-
return createTypeLiteralNode(allElements);
|
|
1700
|
-
},
|
|
1701
|
-
...toNodeHandlers(options.nodes)
|
|
1581
|
+
status(node, statusCode) {
|
|
1582
|
+
const name = this.name(`${node.operationId} Status ${statusCode}`);
|
|
1583
|
+
return this.resolveName({
|
|
1584
|
+
key: operationKey(node, "response", "status", statusCode),
|
|
1585
|
+
name
|
|
1586
|
+
});
|
|
1702
1587
|
},
|
|
1703
|
-
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
name
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1588
|
+
headers(node, statusCode) {
|
|
1589
|
+
const name = this.name(`${node.operationId} Status ${statusCode} Headers`);
|
|
1590
|
+
return this.resolveName({
|
|
1591
|
+
key: operationKey(node, "response", "headers", statusCode),
|
|
1592
|
+
name
|
|
1593
|
+
});
|
|
1594
|
+
},
|
|
1595
|
+
body(node) {
|
|
1596
|
+
const name = this.name(`${node.operationId} Body`);
|
|
1597
|
+
return this.resolveName({
|
|
1598
|
+
key: operationKey(node, "response", "body"),
|
|
1599
|
+
name
|
|
1600
|
+
});
|
|
1601
|
+
},
|
|
1602
|
+
responses(node) {
|
|
1603
|
+
const name = this.name(`${node.operationId} Responses`);
|
|
1604
|
+
return this.resolveName({
|
|
1605
|
+
key: operationKey(node, "response", "responses"),
|
|
1606
|
+
name
|
|
1607
|
+
});
|
|
1608
|
+
},
|
|
1609
|
+
response(node) {
|
|
1610
|
+
const name = this.name(`${node.operationId} Response`);
|
|
1611
|
+
return this.resolveName({
|
|
1612
|
+
key: operationKey(node, "response", "response"),
|
|
1613
|
+
name
|
|
1728
1614
|
});
|
|
1729
1615
|
}
|
|
1730
1616
|
};
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
const enumSchemaNodes = _kubb_kit.ast.collectSync(node, { schema(n) {
|
|
1754
|
-
const enumNode = _kubb_kit.ast.narrowSchema(n, _kubb_kit.ast.schemaTypes.enum);
|
|
1755
|
-
if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
|
|
1756
|
-
} });
|
|
1757
|
-
const output = printer.print(node);
|
|
1758
|
-
const printerImports = printer.drainImports();
|
|
1759
|
-
if (!output) return;
|
|
1760
|
-
const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
|
|
1761
|
-
return {
|
|
1762
|
-
node,
|
|
1763
|
-
...getEnumNames$1({
|
|
1764
|
-
node,
|
|
1765
|
-
enum: enumOptions,
|
|
1766
|
-
resolver
|
|
1767
|
-
})
|
|
1768
|
-
};
|
|
1769
|
-
});
|
|
1770
|
-
const shouldExportEnums = enumOptions.type !== "inlineLiteral";
|
|
1771
|
-
const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
|
|
1772
|
-
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Enum, {
|
|
1773
|
-
node,
|
|
1774
|
-
enum: enumOptions,
|
|
1775
|
-
resolver
|
|
1776
|
-
}, node.name)), shouldExportType && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [printerImports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, { ...imp }, [
|
|
1777
|
-
name,
|
|
1778
|
-
imp.path,
|
|
1779
|
-
imp.name
|
|
1780
|
-
].join("-"))), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
1781
|
-
name,
|
|
1782
|
-
pointer: node.pointer,
|
|
1783
|
-
isTypeOnly: true,
|
|
1784
|
-
isExportable: true,
|
|
1785
|
-
isIndexable: true,
|
|
1786
|
-
children: output
|
|
1787
|
-
})] })] });
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Builds a resolver `file` override whose base name runs every path segment
|
|
1620
|
+
* through `toFilePath`, casing the final segment with `caseLast`.
|
|
1621
|
+
*
|
|
1622
|
+
* @example
|
|
1623
|
+
* ```ts
|
|
1624
|
+
* createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
|
|
1625
|
+
* ```
|
|
1626
|
+
*/
|
|
1627
|
+
function createCasedFile(caseLast) {
|
|
1628
|
+
return { baseName({ name, extname }) {
|
|
1629
|
+
return this.memo(`file:${name}\0${extname}`, () => `${toFilePath(name, caseLast)}${extname}`);
|
|
1630
|
+
} };
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Names of the schemas whose type is `enum`, computed once per build.
|
|
1634
|
+
*/
|
|
1635
|
+
function getEnumNames(ctx) {
|
|
1636
|
+
return ctx.memo("enumNames:set", () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.type === "enum" && schema.name ? [schema.name] : [])));
|
|
1788
1637
|
}
|
|
1789
1638
|
// src/generators/typeGenerator.tsx
|
|
1790
1639
|
/**
|
|
@@ -1820,7 +1669,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
1820
1669
|
group: group ?? void 0,
|
|
1821
1670
|
name: importName
|
|
1822
1671
|
});
|
|
1823
|
-
const enumNode = _kubb_kit.ast.
|
|
1672
|
+
const enumNode = _kubb_kit.ast.schema.narrow(node, _kubb_kit.ast.schema.types.enum);
|
|
1824
1673
|
const isEnumSchema = !!enumNode && !isInlineConstEnum(enumNode, enumSchemaNames);
|
|
1825
1674
|
const meta = {
|
|
1826
1675
|
name: enumOptions.type === "asConst" && isEnumSchema ? resolver.enum.keyName(node, enumOptions.typeSuffix) : resolver.name(node.name),
|
|
@@ -1909,7 +1758,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
1909
1758
|
enumSchemaNames,
|
|
1910
1759
|
nodes: printer?.nodes
|
|
1911
1760
|
}) : null;
|
|
1912
|
-
function renderSchemaType({ schema, name, keysToOmit }) {
|
|
1761
|
+
function renderSchemaType({ schema, name, keysToOmit, examples, comments }) {
|
|
1913
1762
|
if (!schema) return null;
|
|
1914
1763
|
if (!inline && enumOptions.type !== "inlineLiteral") schema = resolveInlineEnumNames({
|
|
1915
1764
|
node: schema,
|
|
@@ -1931,6 +1780,8 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
1931
1780
|
name,
|
|
1932
1781
|
syntaxType,
|
|
1933
1782
|
description: schema.description,
|
|
1783
|
+
examples: getExampleValues(examples, true),
|
|
1784
|
+
comments,
|
|
1934
1785
|
keysToOmit,
|
|
1935
1786
|
resolver,
|
|
1936
1787
|
enumSchemaNames,
|
|
@@ -1960,7 +1811,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
1960
1811
|
* variants by content type in {@link buildResponses}; the standalone alias stays a plain union so
|
|
1961
1812
|
* query hooks and `result.data` keep the bare body.
|
|
1962
1813
|
*/
|
|
1963
|
-
function buildContentTypeVariants(entries, baseName, pointer, decorate) {
|
|
1814
|
+
function buildContentTypeVariants(entries, baseName, pointer, { decorate, comments }) {
|
|
1964
1815
|
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1965
1816
|
const keys = variants.map((variant) => inlinePrinter ? `${inlinePrinter.print(variant.schema)}:${variant.keysToOmit ?? ""}` : variant.name);
|
|
1966
1817
|
const members = variants.filter((_, index) => keys.indexOf(keys[index]) === index);
|
|
@@ -1975,20 +1826,25 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
1975
1826
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [variants.map((variant) => renderSchemaType({
|
|
1976
1827
|
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
1977
1828
|
name: variant.name,
|
|
1978
|
-
keysToOmit: variant.keysToOmit
|
|
1829
|
+
keysToOmit: variant.keysToOmit,
|
|
1830
|
+
examples: entries.find((entry) => entry.contentType === variant.contentType)?.examples
|
|
1979
1831
|
})), renderSchemaType({
|
|
1980
1832
|
schema: unionSchema,
|
|
1981
|
-
name: baseName
|
|
1833
|
+
name: baseName,
|
|
1834
|
+
comments
|
|
1982
1835
|
})] });
|
|
1983
1836
|
}
|
|
1984
|
-
const { path, query, header, cookie } =
|
|
1837
|
+
const { path, query, header, cookie } = _kubb_kit.ast.http.groupParameters(node);
|
|
1985
1838
|
const paramGroupTypes = [
|
|
1986
1839
|
[path, resolver.param.path],
|
|
1987
1840
|
[query, resolver.param.query],
|
|
1988
1841
|
[header, resolver.param.headers],
|
|
1989
1842
|
[cookie, resolver.param.cookies]
|
|
1990
1843
|
].map(([params, resolveName]) => params.length > 0 && renderSchemaType({
|
|
1991
|
-
schema: buildParams({
|
|
1844
|
+
schema: buildParams({
|
|
1845
|
+
params,
|
|
1846
|
+
serializedExamples: true
|
|
1847
|
+
}),
|
|
1992
1848
|
name: resolveName.call(resolver.param, node, params[0])
|
|
1993
1849
|
}));
|
|
1994
1850
|
const requestBodyContent = node.requestBody?.content ?? [];
|
|
@@ -2004,19 +1860,20 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
2004
1860
|
description: node.requestBody.description ?? entry.schema.description
|
|
2005
1861
|
},
|
|
2006
1862
|
name: resolver.response.body(node),
|
|
2007
|
-
keysToOmit: entry.keysToOmit
|
|
1863
|
+
keysToOmit: entry.keysToOmit,
|
|
1864
|
+
examples: entry.examples
|
|
2008
1865
|
});
|
|
2009
1866
|
}
|
|
2010
|
-
return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), node.requestBody.pointer, (schema) => ({
|
|
1867
|
+
return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), node.requestBody.pointer, { decorate: (schema) => ({
|
|
2011
1868
|
...schema,
|
|
2012
1869
|
description: node.requestBody.description ?? schema.description
|
|
2013
|
-
}));
|
|
1870
|
+
}) });
|
|
2014
1871
|
}
|
|
2015
1872
|
const requestType = buildRequestType();
|
|
2016
1873
|
const responseTypes = node.responses.map((res) => {
|
|
2017
1874
|
const content = (res.content ?? []).map((entry, _, content) => toResponseContent(entry, content));
|
|
2018
1875
|
const variants = content.filter((entry) => entry.schema);
|
|
2019
|
-
if (variants.length > 1) return buildContentTypeVariants(variants, resolver.response.status(node, res.statusCode), res.pointer);
|
|
1876
|
+
if (variants.length > 1) return buildContentTypeVariants(variants, resolver.response.status(node, res.statusCode), res.pointer, { comments: buildResponseComments(res) });
|
|
2020
1877
|
const primary = variants[0] ?? content[0];
|
|
2021
1878
|
return renderSchemaType({
|
|
2022
1879
|
schema: primary?.schema ? {
|
|
@@ -2024,22 +1881,34 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
2024
1881
|
pointer: res.pointer ?? primary.schema.pointer
|
|
2025
1882
|
} : _kubb_kit.ast.factory.createSchema({ type: "unknown" }),
|
|
2026
1883
|
name: resolver.response.status(node, res.statusCode),
|
|
2027
|
-
keysToOmit: primary?.keysToOmit
|
|
1884
|
+
keysToOmit: primary?.keysToOmit,
|
|
1885
|
+
examples: primary?.examples,
|
|
1886
|
+
comments: buildResponseComments(res)
|
|
2028
1887
|
});
|
|
2029
1888
|
});
|
|
1889
|
+
const responseHeaderTypes = node.responses.map((res) => res.headers?.length ? renderSchemaType({
|
|
1890
|
+
schema: buildParams({
|
|
1891
|
+
params: res.headers,
|
|
1892
|
+
serializedExamples: true
|
|
1893
|
+
}),
|
|
1894
|
+
name: resolver.response.headers(node, res.statusCode)
|
|
1895
|
+
}) : null);
|
|
2030
1896
|
const optionsType = renderSchemaType({
|
|
2031
1897
|
schema: buildOptionsSchema(node, resolver, { pairContentTypes: true }),
|
|
2032
1898
|
name: resolver.response.options(node)
|
|
2033
1899
|
});
|
|
2034
1900
|
const responsesType = renderSchemaType({
|
|
2035
|
-
schema: buildResponses(node, resolver
|
|
1901
|
+
schema: buildResponses(node, resolver, { streamData: _kubb_kit.ast.factory.createSchema({
|
|
1902
|
+
type: "ref",
|
|
1903
|
+
name: "ReadableStream<Uint8Array>"
|
|
1904
|
+
}) }),
|
|
2036
1905
|
name: resolver.response.responses(node)
|
|
2037
1906
|
});
|
|
2038
1907
|
function buildResponseType() {
|
|
2039
1908
|
const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
|
|
2040
1909
|
const responseName = resolver.response.response(node);
|
|
2041
1910
|
const responsesWithSchema = node.responses.filter(hasSchema);
|
|
2042
|
-
if (new Set(
|
|
1911
|
+
if (new Set(_kubb_kit.ast.http.responses.getImportedNames(responsesWithSchema).map(importName)).has(responseName)) return null;
|
|
2043
1912
|
return renderSchemaType({
|
|
2044
1913
|
schema: {
|
|
2045
1914
|
...buildResponseUnion(node, { resolver }) ?? _kubb_kit.ast.factory.createSchema({ type: "unknown" }),
|
|
@@ -2072,6 +1941,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
2072
1941
|
children: [
|
|
2073
1942
|
paramGroupTypes,
|
|
2074
1943
|
responseTypes,
|
|
1944
|
+
responseHeaderTypes,
|
|
2075
1945
|
requestType,
|
|
2076
1946
|
optionsType,
|
|
2077
1947
|
responsesType,
|
|
@@ -2082,11 +1952,11 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
2082
1952
|
});
|
|
2083
1953
|
// kubb-extension.json
|
|
2084
1954
|
var compatibility = {
|
|
2085
|
-
"kubb": ">=5.
|
|
1955
|
+
"kubb": ">=5.18.4",
|
|
2086
1956
|
"node": ">=22.22"
|
|
2087
1957
|
};
|
|
2088
1958
|
// package.json
|
|
2089
|
-
var version = "5.5.0
|
|
1959
|
+
var version = "5.5.0";
|
|
2090
1960
|
// src/resolvers/resolverTs.ts
|
|
2091
1961
|
/**
|
|
2092
1962
|
* Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
|
|
@@ -2109,9 +1979,9 @@ var version = "5.5.0-canary.20261111T122006";
|
|
|
2109
1979
|
const resolverTs = (0, _kubb_kit.createResolver)({
|
|
2110
1980
|
pluginName: "plugin-ts",
|
|
2111
1981
|
name(name) {
|
|
2112
|
-
return this.memo(`name:${name}`, () => ensureValidVarName(
|
|
1982
|
+
return this.memo(`name:${name}`, () => ensureValidVarName(_kubb_kit.casing.pascal(name)));
|
|
2113
1983
|
},
|
|
2114
|
-
file: createCasedFile(_kubb_kit.
|
|
1984
|
+
file: createCasedFile(_kubb_kit.casing.pascal),
|
|
2115
1985
|
param: createOperationParamResolver(),
|
|
2116
1986
|
response: {
|
|
2117
1987
|
...createOperationResponseResolver(),
|
|
@@ -2184,7 +2054,7 @@ const pluginTs = (0, _kubb_kit.defineExtension)({
|
|
|
2184
2054
|
include,
|
|
2185
2055
|
override,
|
|
2186
2056
|
optionalType,
|
|
2187
|
-
group:
|
|
2057
|
+
group: group ?? null,
|
|
2188
2058
|
arrayType,
|
|
2189
2059
|
enum: resolvedEnum,
|
|
2190
2060
|
syntaxType,
|