@kubb/plugin-ts 5.5.0-canary.20261111T115959 → 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 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.camelCase) {
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) : (0, _kubb_kit.camelCase)(part)).filter(Boolean).join("/");
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, casing = "none") {
520
- if (casing === "none") return key;
521
- if (casing === "screamingSnakeCase") return screamingSnakeCase(key);
522
- if (casing === "snakeCase") return snakeCase(key);
523
- if (casing === "pascalCase") return (0, _kubb_kit.pascalCase)(key);
524
- if (casing === "camelCase") return (0, _kubb_kit.camelCase)(key);
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 = (0, _kubb_kit.syncSchemaRef)(schema);
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((0, _kubb_kit.camelCase)(node.name)),
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
- trimQuotes(v.name.toString()),
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) => [trimQuotes(v.toString()), 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,228 +758,582 @@ function Enum({ node, enum: enumOptions, resolver }) {
832
758
  children: typeNode
833
759
  })] });
834
760
  }
835
- // ../../internals/shared/src/operation.ts
761
+ // src/utils.ts
836
762
  /**
837
- * Builds the `ResolverFileParams` every operation generator passes to
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 operationFileEntry(node, name, extname = ".ts") {
848
- return {
849
- name,
850
- extname,
851
- tag: node.tags[0] ?? "default",
852
- path: node.path
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
- * Resolves the file a schema-emitting plugin (`plugin-ts`, `plugin-zod`, `plugin-faker`) writes
857
- * `node` to, cached in `ctx.memo` under the resolver's plugin name and the
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
- * @example Resolve `plugin-ts`'s file for the current operation
867
- * ```ts
868
- * const fileTs = resolveOperationFile({ ctx, node, resolver: tsResolver, root, output })
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 resolveOperationFile(options) {
872
- const { ctx, node, resolver, root, output } = options;
873
- const { memo } = ctx;
874
- const location = {
875
- root,
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 baseContentType(contentType) {
901
- return contentType.split(";")[0].trim().toLowerCase();
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 content entry is a sequence of JSON values the adapter marks as a stream, such as `application/jsonl`,
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 isJsonLines(entry) {
908
- const baseType = baseContentType(entry.contentType);
909
- return Boolean(entry.stream) && baseType !== "text/event-stream" && !baseType.startsWith("multipart/");
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
- * Parser for one response content entry.
824
+ * Collects JSDoc annotation strings for a schema node.
916
825
  *
917
- * JSON stays unset so the runtime detects it. `text/event-stream` and JSON Lines are streams. A binary schema
918
- * (`format: binary` / `contentMediaType`) is a blob even when the media type is not a well-known
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 responseTypeForContent(entry) {
923
- const baseType = baseContentType(entry.contentType);
924
- if (!baseType) return void 0;
925
- if (baseType === "text/event-stream" || isJsonLines(entry)) return "stream";
926
- if (isJsonContentType(baseType)) return void 0;
927
- if (entry.schema?.type === "blob") return "blob";
928
- if (baseType.startsWith("text/")) return "text";
929
- if (isBinaryContentType(baseType)) return "blob";
930
- }
931
- function isBinaryContentType(baseType) {
932
- return baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType);
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
- * Maps a content type to the PascalCase suffix used to name per-content-type variants
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 getContentTypeSuffix(contentType) {
939
- const baseType = contentType.split(";")[0].trim();
940
- if (baseType === "application/json") return "Json";
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
- * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
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 getPerContentTypeName(baseName, suffix) {
952
- if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
953
- return baseName + suffix;
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
- * Returns the entry with the schema of the value the client returns: `string` for a body read as text, such as `Pet` on
957
- * `application/xml`, and `blob` for `image/*`, `audio/*`, `video/*`, PDF, and octet-stream. A binary schema elsewhere is a `Blob`
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 toResponseContent(entry, entries = [entry]) {
962
- const { contentType, schema } = entry;
963
- if (!schema || entry.stream) return entry;
964
- const baseType = baseContentType(contentType);
965
- const resolved = _kubb_kit.ast.resolveRef(schema);
966
- if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
967
- ...entry,
968
- schema: _kubb_kit.ast.factory.createSchema({ type: "blob" }),
969
- keysToOmit: null
970
- };
971
- const isPlainMediaType = Boolean(baseType) && !isJsonContentType(baseType) && !baseType.includes("*");
972
- if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
973
- ...entry,
974
- schema: _kubb_kit.ast.factory.createSchema({ type: "string" }),
975
- keysToOmit: null
976
- } : entry;
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
- * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
989
- * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
990
- * the final (possibly counter-augmented) value, so callers can derive parallel names in another
991
- * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
992
- */
993
- function resolveContentTypeVariants(entries, baseName) {
994
- const usedNames = /* @__PURE__ */ new Set();
995
- return entries.filter((entry) => entry.schema).map((entry) => {
996
- const baseSuffix = getContentTypeSuffix(entry.contentType);
997
- let suffix = baseSuffix;
998
- let name = getPerContentTypeName(baseName, suffix);
999
- let counter = 2;
1000
- while (usedNames.has(name)) {
1001
- suffix = `${baseSuffix}${counter++}`;
1002
- name = getPerContentTypeName(baseName, suffix);
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
+ });
1003
1106
  }
1004
- usedNames.add(name);
1005
- return {
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, {
1006
1122
  name,
1007
- suffix,
1008
- schema: entry.schema,
1009
- keysToOmit: entry.keysToOmit,
1010
- contentType: entry.contentType
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
+ })
1011
1145
  };
1012
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
+ })] })] });
1013
1165
  }
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
- }));
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
+ };
1030
1185
  }
1031
1186
  /**
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
- });
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
1047
1209
  };
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")
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
1054
1294
  };
1055
- operationParameterGroupsByNode.set(node, groups);
1056
- return groups;
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;
1322
+ let name = getPerContentTypeName(baseName, suffix);
1323
+ let counter = 2;
1324
+ while (usedNames.has(name)) {
1325
+ suffix = `${baseSuffix}${counter++}`;
1326
+ name = getPerContentTypeName(baseName, suffix);
1327
+ }
1328
+ usedNames.add(name);
1329
+ return {
1330
+ name,
1331
+ suffix,
1332
+ schema: entry.schema,
1333
+ keysToOmit: entry.keysToOmit,
1334
+ contentType: entry.contentType
1335
+ };
1336
+ });
1057
1337
  }
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`.
@@ -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 } = getOperationParameters(node);
1074
- const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
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: [baseContentType(variant.contentType)]
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,32 +1483,29 @@ 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
1228
1503
  * it is, so two of them never share an allocation even when a custom resolver gives both the same
1229
1504
  * name. Leave `scope` empty for the operation's own declaration.
1230
1505
  *
1506
+ * The key starts from the operation's input `pointer`, which macros keep, so a plugin whose macro
1507
+ * rewrites the method or path still resolves another plugin's names for the same operation.
1508
+ *
1231
1509
  * @example
1232
1510
  * ```ts
1233
1511
  * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
@@ -1236,8 +1514,7 @@ function isSuccessStatusCode(statusCode) {
1236
1514
  function operationKey(node, ...scope) {
1237
1515
  return [
1238
1516
  "operation",
1239
- node.method,
1240
- node.path,
1517
+ ...node.pointer ? [node.pointer] : [node.method, node.path],
1241
1518
  ...scope.map(String)
1242
1519
  ];
1243
1520
  }
@@ -1273,516 +1550,90 @@ function createOperationParamResolver() {
1273
1550
  name
1274
1551
  });
1275
1552
  },
1276
- headers(node) {
1277
- const name = this.name(`${node.operationId} Headers`);
1278
- return this.resolveName({
1279
- key: operationKey(node, "param", "headers"),
1280
- name
1281
- });
1282
- },
1283
- cookies(node) {
1284
- const name = this.name(`${node.operationId} Cookies`);
1285
- return this.resolveName({
1286
- key: operationKey(node, "param", "cookies"),
1287
- name
1288
- });
1289
- }
1290
- };
1291
- }
1292
- /**
1293
- * Builds the shared `response` namespace. Spread the result into
1294
- * `createResolver` and add plugin-specific methods (`options`, `error`) next
1295
- * to it.
1296
- *
1297
- * @example
1298
- * ```ts
1299
- * createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
1300
- * ```
1301
- */
1302
- function createOperationResponseResolver() {
1303
- return {
1304
- status(node, statusCode) {
1305
- const name = this.name(`${node.operationId} Status ${statusCode}`);
1306
- return this.resolveName({
1307
- key: operationKey(node, "response", "status", statusCode),
1308
- name
1309
- });
1310
- },
1311
- body(node) {
1312
- const name = this.name(`${node.operationId} Body`);
1313
- return this.resolveName({
1314
- key: operationKey(node, "response", "body"),
1315
- name
1316
- });
1317
- },
1318
- responses(node) {
1319
- const name = this.name(`${node.operationId} Responses`);
1320
- return this.resolveName({
1321
- key: operationKey(node, "response", "responses"),
1322
- name
1323
- });
1324
- },
1325
- response(node) {
1326
- const name = this.name(`${node.operationId} Response`);
1327
- return this.resolveName({
1328
- key: operationKey(node, "response", "response"),
1329
- name
1330
- });
1331
- }
1332
- };
1333
- }
1334
- /**
1335
- * Builds a resolver `file` override whose base name runs every path segment
1336
- * through `toFilePath`, casing the final segment with `caseLast`.
1337
- *
1338
- * @example
1339
- * ```ts
1340
- * createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
1341
- * ```
1342
- */
1343
- function createCasedFile(caseLast) {
1344
- return { baseName({ name, extname }) {
1345
- return this.memo(`file:${name}\0${extname}`, () => `${toFilePath(name, caseLast)}${extname}`);
1346
- } };
1347
- }
1348
- /**
1349
- * Names of the schemas whose type is `enum`, computed once per build.
1350
- */
1351
- function getEnumNames(ctx) {
1352
- return ctx.memo("enumNames:set", () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.type === "enum" && schema.name ? [schema.name] : [])));
1353
- }
1354
- // ../../internals/shared/src/group.ts
1355
- /**
1356
- * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
1357
- * shared default naming so every plugin groups output consistently:
1358
- *
1359
- * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
1360
- * - other groups use the camelCased group (`pet store` → `petStore`).
1361
- *
1362
- * A user-provided `group.name` always wins over the default namer, so callers stay in
1363
- * control of their output folders. Returns `null` when grouping is disabled, matching the
1364
- * per-plugin convention.
1365
- *
1366
- * @param group - The user-supplied group option, or `undefined` to disable grouping.
1367
- *
1368
- * @example
1369
- * ```ts
1370
- * createGroupConfig(group) // shared across every plugin
1371
- * ```
1372
- */
1373
- function createGroupConfig(group) {
1374
- if (!group) return null;
1375
- const defaultName = (ctx) => {
1376
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1377
- return (0, _kubb_kit.camelCase)(ctx.group);
1378
- };
1379
- return {
1380
- ...group,
1381
- name: group.name ? group.name : defaultName
1382
- };
1383
- }
1384
- // ../../internals/shared/src/schemaTraversal.ts
1385
- /**
1386
- * Maps each property of an object schema to its transformed output. Pairs every result with the
1387
- * original property so the printer keeps full control over modifiers, getters, and key syntax.
1388
- *
1389
- * @example
1390
- * ```ts
1391
- * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
1392
- * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
1393
- * ```
1394
- */
1395
- function mapSchemaProperties(node, transform) {
1396
- return node.properties.map((property) => ({
1397
- name: property.name,
1398
- property,
1399
- output: transform(property.schema)
1400
- }));
1401
- }
1402
- /**
1403
- * Maps each item of an array or tuple schema to its transformed output, pairing every result with
1404
- * the original item.
1405
- */
1406
- function mapSchemaItems(node, transform) {
1407
- return (node.items ?? []).map((schema) => ({
1408
- schema,
1409
- output: transform(schema)
1410
- }));
1411
- }
1412
- // src/utils.ts
1413
- /**
1414
- * Renames inline enum declarations and their uses together.
1415
- */
1416
- function resolveInlineEnumNames({ node, owner, resolver, enumOptions }) {
1417
- return _kubb_kit.ast.transform(node, { schema(child) {
1418
- if (child === node || child.type !== "enum" || !child.name) return;
1419
- const name = resolver.resolveName({
1420
- key: [
1421
- "enum",
1422
- owner,
1423
- child.pointer ?? child.name
1424
- ],
1425
- name: child.name,
1426
- normalize: (value) => {
1427
- const { enumName, typeName } = getEnumNames$1({
1428
- node: {
1429
- ...child,
1430
- name: value
1431
- },
1432
- enum: enumOptions,
1433
- resolver
1434
- });
1435
- return enumOptions.type === "literal" ? [typeName] : [enumName, typeName];
1436
- }
1437
- });
1438
- return name === child.name ? void 0 : {
1439
- ...child,
1440
- name
1441
- };
1442
- } });
1443
- }
1444
- function enumTypeName({ resolver, name, enumOptions, isEnum }) {
1445
- return isEnum && enumOptions?.type === "asConst" && enumOptions.typeSuffix ? resolver.enum.keyName({ name }, enumOptions.typeSuffix) : resolver.name(name);
1446
- }
1447
- /**
1448
- * Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
1449
- * rather than a named enum reference or a runtime enum declaration.
1450
- *
1451
- * The parser folds `const` into a single-value enum node. The adapter decides which schemas are
1452
- * named enums and lists them in `enumSchemaNames`, and references to those names get suffixed (for
1453
- * example `StatusKey`). A const renders as a literal only when the adapter has not registered it as
1454
- * a named enum, which keeps the declaration and its references in sync across adapter versions.
1455
- */
1456
- function isInlineConstEnum(node, enumSchemaNames) {
1457
- return (node.namedEnumValues ?? node.enumValues ?? []).length === 1 && !(node.name && enumSchemaNames?.has(node.name));
1458
- }
1459
- /**
1460
- * Collects JSDoc annotation strings for a schema node.
1461
- *
1462
- * Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@default`, `@example`, `@type`.
1463
- * Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
1464
-
1465
- */
1466
- function isSchemaOptional(schema) {
1467
- return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
1468
- }
1469
- function formatExample(value) {
1470
- if (value === null || typeof value !== "object") return String(value);
1471
- return (JSON.stringify(value) ?? String(value)).replaceAll("*/", "*\\/");
1472
- }
1473
- /**
1474
- * Whether a schema is binary, or an array of binary items, so its TypeScript type is `Blob` or `Blob[]`.
1475
- */
1476
- function isBinarySchema(schema) {
1477
- const meta = (0, _kubb_kit.syncSchemaRef)(schema);
1478
- const isBlob = (node) => (0, _kubb_kit.syncSchemaRef)(node)?.type === "blob";
1479
- return !!meta && (isBlob(meta) || meta.type === "array" && "items" in meta && (meta.items ?? []).some(isBlob));
1480
- }
1481
- function buildPropertyJSDocComments(schema, optional) {
1482
- const meta = (0, _kubb_kit.syncSchemaRef)(schema);
1483
- const isArray = meta?.primitive === "array";
1484
- const isNumeric = meta?.primitive === "number" || meta?.primitive === "integer" || meta?.primitive === "bigint";
1485
- const hasDescription = meta && "description" in meta && meta.description;
1486
- const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
1487
- const exampleValues = meta?.examples ?? [];
1488
- const comments = [
1489
- hasDescription ? `@description ${jsDocEscape(meta.description)}` : null,
1490
- ...formatComment,
1491
- meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
1492
- !isArray && meta && "min" in meta && meta.min !== void 0 ? `@${isNumeric ? "minimum" : "minLength"} ${meta.min}` : null,
1493
- !isArray && meta && "max" in meta && meta.max !== void 0 ? `@${isNumeric ? "maximum" : "maxLength"} ${meta.max}` : null,
1494
- meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
1495
- 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,
1496
- ...exampleValues.map((example) => `@example ${formatExample(example)}`)
1497
- ].filter(Boolean);
1498
- const typeTag = comments.length && !isBinarySchema(schema) && meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null;
1499
- return [...comments, typeTag].filter(Boolean);
1500
- }
1501
- /**
1502
- * Builds the object schema for a group of parameters sharing one `in` location (path, query, or
1503
- * header), embedding each param's own schema (and JSDoc) directly rather than referencing a
1504
- * separate per-param type — the group itself is the only type these params get exported as.
1505
- */
1506
- function buildParams({ params }) {
1507
- return _kubb_kit.ast.factory.createSchema({
1508
- type: "object",
1509
- pointer: params[0]?.pointer?.replace(/\/\d+$/, ""),
1510
- properties: params.map((param) => _kubb_kit.ast.factory.createProperty({
1511
- name: param.name,
1512
- required: param.required,
1513
- schema: _kubb_kit.ast.factory.createSchema({
1514
- ...param.schema,
1515
- optional: !param.required
1516
- })
1517
- }))
1518
- });
1519
- }
1520
- function buildResponseUnion(node, { resolver }) {
1521
- const allWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
1522
- const successWithSchema = allWithSchema.filter((res) => isSuccessStatusCode(res.statusCode));
1523
- const responsesWithSchema = successWithSchema.length > 0 ? successWithSchema : allWithSchema;
1524
- if (responsesWithSchema.length === 0) return null;
1525
- return _kubb_kit.ast.factory.createSchema({
1526
- type: "union",
1527
- members: responsesWithSchema.map((res) => _kubb_kit.ast.factory.createSchema({
1528
- type: "ref",
1529
- name: resolver.response.status(node, res.statusCode)
1530
- }))
1531
- });
1532
- }
1533
- // src/printers/printerTs.ts
1534
- const { isNonNullable } = factory_exports;
1535
- const requireModule = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
1536
- /**
1537
- * Loads `@kubb/parser-ts` on first use. It is an optional peer dependency: only a deprecated `printer.nodes`
1538
- * handler that returns a TypeScript compiler node needs it. Loads synchronously because node handlers are synchronous.
1539
- */
1540
- function loadParserTs(load = () => requireModule("@kubb/parser-ts")) {
1541
- try {
1542
- return load();
1543
- } catch (error) {
1544
- if (error.code !== "MODULE_NOT_FOUND" || !error.message.split("\n")[0].includes("@kubb/parser-ts")) throw error;
1545
- throw new _kubb_kit.Diagnostics.Error({
1546
- code: _kubb_kit.Diagnostics.code.missingDependency,
1547
- severity: "error",
1548
- message: "Missing dependency: @kubb/parser-ts. A `printer.nodes` handler returned a TypeScript compiler node, and printing it needs @kubb/parser-ts.",
1549
- help: "Run `npm i -D @kubb/parser-ts` in the project, or return a string from the handler.",
1550
- plugin: "plugin-ts"
1551
- });
1552
- }
1553
- }
1554
- /**
1555
- * Adapts user overrides, which work in strings, to the node-based printer: `this.transform` hands them printed
1556
- * strings and their result is wrapped back into a node. A deprecated compiler node prints through `@kubb/parser-ts`.
1557
- */
1558
- function toNodeHandlers(nodes) {
1559
- return Object.fromEntries(Object.entries(nodes ?? {}).map(([type, handler]) => [type, function(node) {
1560
- const transform = (child) => {
1561
- const output = this.transform(child);
1562
- return output ? print(output) : null;
1563
- };
1564
- const result = handler.call({
1565
- ...this,
1566
- transform
1567
- }, node);
1568
- if (result === null || result === void 0) return result;
1569
- return createRaw(typeof result === "string" ? result : loadParserTs().parserTs().print(result).trimEnd());
1570
- }]));
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
+ };
1571
1568
  }
1572
1569
  /**
1573
- * TypeScript type printer built with `definePrinter`.
1574
- *
1575
- * Converts a `SchemaNode` AST node into TypeScript source:
1576
- * - **`printer.print(node)`** — when `options.name` is set, returns a full
1577
- * `type Name = …` or `interface Name { … }` declaration.
1578
- * Without `name`, returns the type for the schema, such as `string | null`.
1579
- *
1580
- * Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
1581
- * over per printer instance, so each call to `printerTs(options)` produces an independent printer.
1582
- *
1583
- * @example Raw type node (no `typeName`)
1584
- * ```ts
1585
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
1586
- * const type = printer.print(schemaNode) // 'string | null'
1587
- * ```
1570
+ * Builds the shared `response` namespace. Spread the result into
1571
+ * `createResolver` and add plugin-specific methods (`options`, `error`) next
1572
+ * to it.
1588
1573
  *
1589
- * @example Full declaration (with `typeName`)
1574
+ * @example
1590
1575
  * ```ts
1591
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
1592
- * const declaration = printer.print(schemaNode) // 'export type MyType = …'
1576
+ * createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
1593
1577
  * ```
1594
1578
  */
1595
- const printerTs = _kubb_kit.ast.createPrinter((options) => {
1596
- const addsUndefined = options.optionalType === "undefined" || options.optionalType === "questionTokenAndUndefined";
1579
+ function createOperationResponseResolver() {
1597
1580
  return {
1598
- name: "typescript",
1599
- options,
1600
- nodes: {
1601
- any: () => keywordTypeNodes.any,
1602
- unknown: () => keywordTypeNodes.unknown,
1603
- void: () => keywordTypeNodes.void,
1604
- never: () => keywordTypeNodes.never,
1605
- boolean: () => keywordTypeNodes.boolean,
1606
- null: () => keywordTypeNodes.null,
1607
- blob: () => createTypeReferenceNode("Blob"),
1608
- string: () => keywordTypeNodes.string,
1609
- uuid: () => keywordTypeNodes.string,
1610
- email: () => keywordTypeNodes.string,
1611
- url: (node) => {
1612
- if (node.path) return createUrlTemplateType(node.path);
1613
- return keywordTypeNodes.string;
1614
- },
1615
- ipv4: () => keywordTypeNodes.string,
1616
- ipv6: () => keywordTypeNodes.string,
1617
- datetime: () => keywordTypeNodes.string,
1618
- number: () => keywordTypeNodes.number,
1619
- integer: () => keywordTypeNodes.number,
1620
- bigint: () => keywordTypeNodes.bigint,
1621
- date: dateOrStringNode,
1622
- time: dateOrStringNode,
1623
- ref(node) {
1624
- if (!node.name) return null;
1625
- const refName = _kubb_kit.ast.resolveRefName(node);
1626
- if (!refName) return null;
1627
- return createTypeReferenceNode(node.ref ? enumTypeName({
1628
- resolver: this.options.resolver,
1629
- name: refName,
1630
- enumOptions: this.options.enum,
1631
- isEnum: !!this.options.enumSchemaNames?.has(refName)
1632
- }) : refName);
1633
- },
1634
- enum(node) {
1635
- const values = _kubb_kit.ast.getSchemaLiteralValues(node);
1636
- if (values.some((value) => value !== null && typeof value === "object")) return createUnionDeclaration({ nodes: values.map((value) => constToTypeNode(value)) });
1637
- 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) });
1638
- return createTypeReferenceNode(enumTypeName({
1639
- resolver: this.options.resolver,
1640
- name: node.name,
1641
- enumOptions: this.options.enum,
1642
- isEnum: true
1643
- }));
1644
- },
1645
- union(node) {
1646
- const members = node.members ?? [];
1647
- const hasStringLiteral = members.some((m) => {
1648
- return _kubb_kit.ast.narrowSchema(m, _kubb_kit.ast.schemaTypes.enum)?.primitive === "string";
1649
- });
1650
- const hasPlainString = members.some((m) => (0, _kubb_kit.isStringType)(m));
1651
- if (hasStringLiteral && hasPlainString) return createUnionDeclaration({ nodes: members.map((m) => {
1652
- if ((0, _kubb_kit.isStringType)(m)) return createIntersectionDeclaration({ nodes: [keywordTypeNodes.string, createTypeLiteralNode([])] });
1653
- return this.transform(m);
1654
- }).filter(isNonNullable) });
1655
- return createUnionDeclaration({ nodes: buildMemberNodes(members, this.transform) });
1656
- },
1657
- intersection(node) {
1658
- return createIntersectionDeclaration({ nodes: buildMemberNodes(node.members, this.transform) });
1659
- },
1660
- array(node) {
1661
- return createArrayDeclaration({
1662
- nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
1663
- arrayType: this.options.arrayType
1664
- });
1665
- },
1666
- tuple(node) {
1667
- return buildTupleNode(node, this.transform);
1668
- },
1669
- object(node) {
1670
- const { transform, options } = this;
1671
- const keySchema = node.propertyNames && (0, _kubb_kit.syncSchemaRef)(node.propertyNames);
1672
- if (!node.properties.length && !Object.keys(node.patternProperties ?? {}).length && node.propertyNames && keySchema?.type === "enum" && node.additionalProperties !== false) {
1673
- const keyType = transform(node.propertyNames) ?? keywordTypeNodes.string;
1674
- const valueSchema = typeof node.additionalProperties === "object" ? node.additionalProperties : _kubb_kit.ast.factory.createSchema({ type: "unknown" });
1675
- return createTypeReferenceNode("Partial", [createTypeReferenceNode("Record", [keyType, buildPropertyType(valueSchema, transform(valueSchema) ?? keywordTypeNodes.unknown, options.optionalType, false)])]);
1676
- }
1677
- const addsQuestionToken = options.optionalType === "questionToken" || options.optionalType === "questionTokenAndUndefined";
1678
- const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1679
- const baseType = output ?? keywordTypeNodes.unknown;
1680
- const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
1681
- const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
1682
- const propMeta = (0, _kubb_kit.syncSchemaRef)(property.schema);
1683
- const propertyNode = createPropertySignature({
1684
- questionToken: optional ? addsQuestionToken : false,
1685
- name,
1686
- type,
1687
- readOnly: propMeta?.readOnly
1688
- });
1689
- const binaryRemark = isBinarySchema(property.schema) ? "@remarks Pass a `File` to send its filename. A plain `Blob` is sent with the filename `blob`." : void 0;
1690
- return appendJSDocToNode({
1691
- node: propertyNode,
1692
- comments: [...buildPropertyJSDocComments(property.schema, optional), binaryRemark]
1693
- });
1694
- });
1695
- const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)];
1696
- if (!allElements.length) return keywordTypeNodes.object;
1697
- return createTypeLiteralNode(allElements);
1698
- },
1699
- ...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
+ });
1700
1587
  },
1701
- print(node) {
1702
- const { name, syntaxType = "type", description, keysToOmit } = this.options;
1703
- const transformed = this.transform(node);
1704
- if (!transformed) return null;
1705
- const meta = (0, _kubb_kit.syncSchemaRef)(node);
1706
- if (!name) return print(wrapNullableOptional(keysToOmit?.length ? createOmitDeclaration({
1707
- keys: keysToOmit,
1708
- type: transformed,
1709
- nonNullable: true
1710
- }) : transformed, !!meta.nullable, !!(meta.nullish || meta.optional) && addsUndefined));
1711
- return createTypeDeclaration({
1712
- name,
1713
- isExportable: true,
1714
- type: (() => {
1715
- return wrapNullableOptional(keysToOmit?.length ? createOmitDeclaration({
1716
- keys: keysToOmit,
1717
- type: transformed,
1718
- nonNullable: true
1719
- }) : transformed, !!meta.nullable, !!(meta.nullish || meta.optional));
1720
- })(),
1721
- syntax: syntaxType === "type" || !!keysToOmit?.length ? "type" : "interface",
1722
- comments: buildPropertyJSDocComments({
1723
- ...meta,
1724
- description
1725
- })
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
1726
1614
  });
1727
1615
  }
1728
1616
  };
1729
- });
1730
- // src/components/Type.tsx
1731
- function Type({ name, node, printer, enum: enumOptions, resolver, inline }) {
1732
- if (inline) {
1733
- const withoutEnums = _kubb_kit.ast.transform(node, { schema: (n) => n.type === _kubb_kit.ast.schemaTypes.enum && n.name ? {
1734
- ...n,
1735
- name: void 0
1736
- } : void 0 });
1737
- const expression = printerTs({
1738
- ...printer.options,
1739
- name: void 0
1740
- }).print(withoutEnums)?.replace(/\/\*\*[\s\S]*?\*\/\s*/g, "");
1741
- if (!expression) return;
1742
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
1743
- name,
1744
- isTypeOnly: true,
1745
- isExportable: true,
1746
- isIndexable: true,
1747
- inline: { expression },
1748
- children: expression
1749
- });
1750
- }
1751
- const enumSchemaNodes = _kubb_kit.ast.collectSync(node, { schema(n) {
1752
- const enumNode = _kubb_kit.ast.narrowSchema(n, _kubb_kit.ast.schemaTypes.enum);
1753
- if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
1754
- } });
1755
- const output = printer.print(node);
1756
- const printerImports = printer.drainImports();
1757
- if (!output) return;
1758
- const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
1759
- return {
1760
- node,
1761
- ...getEnumNames$1({
1762
- node,
1763
- enum: enumOptions,
1764
- resolver
1765
- })
1766
- };
1767
- });
1768
- const shouldExportEnums = enumOptions.type !== "inlineLiteral";
1769
- const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
1770
- 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, {
1771
- node,
1772
- enum: enumOptions,
1773
- resolver
1774
- }, 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 }, [
1775
- name,
1776
- imp.path,
1777
- imp.name
1778
- ].join("-"))), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
1779
- name,
1780
- pointer: node.pointer,
1781
- isTypeOnly: true,
1782
- isExportable: true,
1783
- isIndexable: true,
1784
- children: output
1785
- })] })] });
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] : [])));
1786
1637
  }
1787
1638
  // src/generators/typeGenerator.tsx
1788
1639
  /**
@@ -1818,7 +1669,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
1818
1669
  group: group ?? void 0,
1819
1670
  name: importName
1820
1671
  });
1821
- const enumNode = _kubb_kit.ast.narrowSchema(node, _kubb_kit.ast.schemaTypes.enum);
1672
+ const enumNode = _kubb_kit.ast.schema.narrow(node, _kubb_kit.ast.schema.types.enum);
1822
1673
  const isEnumSchema = !!enumNode && !isInlineConstEnum(enumNode, enumSchemaNames);
1823
1674
  const meta = {
1824
1675
  name: enumOptions.type === "asConst" && isEnumSchema ? resolver.enum.keyName(node, enumOptions.typeSuffix) : resolver.name(node.name),
@@ -1907,7 +1758,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
1907
1758
  enumSchemaNames,
1908
1759
  nodes: printer?.nodes
1909
1760
  }) : null;
1910
- function renderSchemaType({ schema, name, keysToOmit }) {
1761
+ function renderSchemaType({ schema, name, keysToOmit, examples, comments }) {
1911
1762
  if (!schema) return null;
1912
1763
  if (!inline && enumOptions.type !== "inlineLiteral") schema = resolveInlineEnumNames({
1913
1764
  node: schema,
@@ -1929,6 +1780,8 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
1929
1780
  name,
1930
1781
  syntaxType,
1931
1782
  description: schema.description,
1783
+ examples: getExampleValues(examples, true),
1784
+ comments,
1932
1785
  keysToOmit,
1933
1786
  resolver,
1934
1787
  enumSchemaNames,
@@ -1958,7 +1811,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
1958
1811
  * variants by content type in {@link buildResponses}; the standalone alias stays a plain union so
1959
1812
  * query hooks and `result.data` keep the bare body.
1960
1813
  */
1961
- function buildContentTypeVariants(entries, baseName, pointer, decorate) {
1814
+ function buildContentTypeVariants(entries, baseName, pointer, { decorate, comments }) {
1962
1815
  const variants = resolveContentTypeVariants(entries, baseName);
1963
1816
  const keys = variants.map((variant) => inlinePrinter ? `${inlinePrinter.print(variant.schema)}:${variant.keysToOmit ?? ""}` : variant.name);
1964
1817
  const members = variants.filter((_, index) => keys.indexOf(keys[index]) === index);
@@ -1973,20 +1826,25 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
1973
1826
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [variants.map((variant) => renderSchemaType({
1974
1827
  schema: decorate ? decorate(variant.schema) : variant.schema,
1975
1828
  name: variant.name,
1976
- keysToOmit: variant.keysToOmit
1829
+ keysToOmit: variant.keysToOmit,
1830
+ examples: entries.find((entry) => entry.contentType === variant.contentType)?.examples
1977
1831
  })), renderSchemaType({
1978
1832
  schema: unionSchema,
1979
- name: baseName
1833
+ name: baseName,
1834
+ comments
1980
1835
  })] });
1981
1836
  }
1982
- const { path, query, header, cookie } = getOperationParameters(node);
1837
+ const { path, query, header, cookie } = _kubb_kit.ast.http.groupParameters(node);
1983
1838
  const paramGroupTypes = [
1984
1839
  [path, resolver.param.path],
1985
1840
  [query, resolver.param.query],
1986
1841
  [header, resolver.param.headers],
1987
1842
  [cookie, resolver.param.cookies]
1988
1843
  ].map(([params, resolveName]) => params.length > 0 && renderSchemaType({
1989
- schema: buildParams({ params }),
1844
+ schema: buildParams({
1845
+ params,
1846
+ serializedExamples: true
1847
+ }),
1990
1848
  name: resolveName.call(resolver.param, node, params[0])
1991
1849
  }));
1992
1850
  const requestBodyContent = node.requestBody?.content ?? [];
@@ -2002,19 +1860,20 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
2002
1860
  description: node.requestBody.description ?? entry.schema.description
2003
1861
  },
2004
1862
  name: resolver.response.body(node),
2005
- keysToOmit: entry.keysToOmit
1863
+ keysToOmit: entry.keysToOmit,
1864
+ examples: entry.examples
2006
1865
  });
2007
1866
  }
2008
- return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), node.requestBody.pointer, (schema) => ({
1867
+ return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), node.requestBody.pointer, { decorate: (schema) => ({
2009
1868
  ...schema,
2010
1869
  description: node.requestBody.description ?? schema.description
2011
- }));
1870
+ }) });
2012
1871
  }
2013
1872
  const requestType = buildRequestType();
2014
1873
  const responseTypes = node.responses.map((res) => {
2015
1874
  const content = (res.content ?? []).map((entry, _, content) => toResponseContent(entry, content));
2016
1875
  const variants = content.filter((entry) => entry.schema);
2017
- 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) });
2018
1877
  const primary = variants[0] ?? content[0];
2019
1878
  return renderSchemaType({
2020
1879
  schema: primary?.schema ? {
@@ -2022,22 +1881,34 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
2022
1881
  pointer: res.pointer ?? primary.schema.pointer
2023
1882
  } : _kubb_kit.ast.factory.createSchema({ type: "unknown" }),
2024
1883
  name: resolver.response.status(node, res.statusCode),
2025
- keysToOmit: primary?.keysToOmit
1884
+ keysToOmit: primary?.keysToOmit,
1885
+ examples: primary?.examples,
1886
+ comments: buildResponseComments(res)
2026
1887
  });
2027
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);
2028
1896
  const optionsType = renderSchemaType({
2029
1897
  schema: buildOptionsSchema(node, resolver, { pairContentTypes: true }),
2030
1898
  name: resolver.response.options(node)
2031
1899
  });
2032
1900
  const responsesType = renderSchemaType({
2033
- schema: buildResponses(node, resolver),
1901
+ schema: buildResponses(node, resolver, { streamData: _kubb_kit.ast.factory.createSchema({
1902
+ type: "ref",
1903
+ name: "ReadableStream<Uint8Array>"
1904
+ }) }),
2034
1905
  name: resolver.response.responses(node)
2035
1906
  });
2036
1907
  function buildResponseType() {
2037
1908
  const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
2038
1909
  const responseName = resolver.response.response(node);
2039
1910
  const responsesWithSchema = node.responses.filter(hasSchema);
2040
- if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? _kubb_kit.ast.collectImportedRefNames(entry.schema).map(importName) : []))).has(responseName)) return null;
1911
+ if (new Set(_kubb_kit.ast.http.responses.getImportedNames(responsesWithSchema).map(importName)).has(responseName)) return null;
2041
1912
  return renderSchemaType({
2042
1913
  schema: {
2043
1914
  ...buildResponseUnion(node, { resolver }) ?? _kubb_kit.ast.factory.createSchema({ type: "unknown" }),
@@ -2070,6 +1941,7 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
2070
1941
  children: [
2071
1942
  paramGroupTypes,
2072
1943
  responseTypes,
1944
+ responseHeaderTypes,
2073
1945
  requestType,
2074
1946
  optionsType,
2075
1947
  responsesType,
@@ -2080,11 +1952,11 @@ const typeGenerator = (0, _kubb_kit.defineGenerator)({
2080
1952
  });
2081
1953
  // kubb-extension.json
2082
1954
  var compatibility = {
2083
- "kubb": ">=5.16.0",
1955
+ "kubb": ">=5.18.4",
2084
1956
  "node": ">=22.22"
2085
1957
  };
2086
1958
  // package.json
2087
- var version = "5.5.0-canary.20261111T115959";
1959
+ var version = "5.5.0";
2088
1960
  // src/resolvers/resolverTs.ts
2089
1961
  /**
2090
1962
  * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
@@ -2107,9 +1979,9 @@ var version = "5.5.0-canary.20261111T115959";
2107
1979
  const resolverTs = (0, _kubb_kit.createResolver)({
2108
1980
  pluginName: "plugin-ts",
2109
1981
  name(name) {
2110
- return this.memo(`name:${name}`, () => ensureValidVarName((0, _kubb_kit.pascalCase)(name)));
1982
+ return this.memo(`name:${name}`, () => ensureValidVarName(_kubb_kit.casing.pascal(name)));
2111
1983
  },
2112
- file: createCasedFile(_kubb_kit.pascalCase),
1984
+ file: createCasedFile(_kubb_kit.casing.pascal),
2113
1985
  param: createOperationParamResolver(),
2114
1986
  response: {
2115
1987
  ...createOperationResponseResolver(),
@@ -2182,7 +2054,7 @@ const pluginTs = (0, _kubb_kit.defineExtension)({
2182
2054
  include,
2183
2055
  override,
2184
2056
  optionalType,
2185
- group: createGroupConfig(group),
2057
+ group: group ?? null,
2186
2058
  arrayType,
2187
2059
  enum: resolvedEnum,
2188
2060
  syntaxType,