@kubb/plugin-ts 5.0.0-beta.42 → 5.0.0-beta.64

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.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { t as __name } from "./chunk-C0LytTxp.js";
2
+ import { caseParams, extractRefName, isStringType, jsStringEscape, mapSchemaItems, mapSchemaProperties, stringify, syncSchemaRef, trimQuotes } from "@kubb/ast/utils";
2
3
  import { parserTs } from "@kubb/parser-ts";
3
- import { File, jsxRendererSync } from "@kubb/renderer-jsx";
4
- import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
4
+ import { File, jsxRenderer } from "@kubb/renderer-jsx";
5
5
  import ts from "typescript";
6
6
  import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
7
+ import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
7
8
  //#region ../../internals/utils/src/casing.ts
8
9
  /**
9
10
  * Shared implementation for camelCase and PascalCase conversion.
@@ -15,58 +16,41 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
15
16
  function toCamelOrPascal(text, pascal) {
16
17
  return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
17
18
  if (word.length > 1 && word === word.toUpperCase()) return word;
18
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
19
- return word.charAt(0).toUpperCase() + word.slice(1);
19
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
20
20
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
21
21
  }
22
22
  /**
23
- * Splits `text` on `.` and applies `transformPart` to each segment.
24
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
25
- * Segments are joined with `/` to form a file path.
26
- *
27
- * Only splits on dots followed by a letter so that version numbers
28
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
29
- */
30
- function applyToFileParts(text, transformPart) {
31
- const parts = text.split(/\.(?=[a-zA-Z])/);
32
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
33
- }
34
- /**
35
23
  * Converts `text` to camelCase.
36
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
37
24
  *
38
- * @example
39
- * camelCase('hello-world') // 'helloWorld'
40
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
25
+ * @example Word boundaries
26
+ * `camelCase('hello-world') // 'helloWorld'`
27
+ *
28
+ * @example With a prefix
29
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
41
30
  */
42
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
43
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
44
- prefix,
45
- suffix
46
- } : {}));
31
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
47
32
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
48
33
  }
49
34
  /**
50
35
  * Converts `text` to PascalCase.
51
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
52
36
  *
53
- * @example
54
- * pascalCase('hello-world') // 'HelloWorld'
55
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
37
+ * @example Word boundaries
38
+ * `pascalCase('hello-world') // 'HelloWorld'`
39
+ *
40
+ * @example With a suffix
41
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
56
42
  */
57
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
58
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
59
- prefix,
60
- suffix
61
- }) : camelCase(part));
43
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
62
44
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
63
45
  }
64
46
  /**
65
47
  * Converts `text` to snake_case.
66
48
  *
67
- * @example
68
- * snakeCase('helloWorld') // 'hello_world'
69
- * snakeCase('Hello-World') // 'hello_world'
49
+ * @example From camelCase
50
+ * `snakeCase('helloWorld') // 'hello_world'`
51
+ *
52
+ * @example From mixed separators
53
+ * `snakeCase('Hello-World') // 'hello_world'`
70
54
  */
71
55
  function snakeCase(text, { prefix = "", suffix = "" } = {}) {
72
56
  return `${prefix} ${text} ${suffix}`.trim().replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-.]+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").toLowerCase().split("_").filter(Boolean).join("_");
@@ -74,8 +58,8 @@ function snakeCase(text, { prefix = "", suffix = "" } = {}) {
74
58
  /**
75
59
  * Converts `text` to SCREAMING_SNAKE_CASE.
76
60
  *
77
- * @example
78
- * screamingSnakeCase('helloWorld') // 'HELLO_WORLD'
61
+ * @example From camelCase
62
+ * `screamingSnakeCase('helloWorld') // 'HELLO_WORLD'`
79
63
  */
80
64
  function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
81
65
  return snakeCase(text, {
@@ -84,60 +68,29 @@ function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
84
68
  }).toUpperCase();
85
69
  }
86
70
  //#endregion
87
- //#region ../../internals/utils/src/string.ts
71
+ //#region ../../internals/utils/src/fs.ts
88
72
  /**
89
- * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
90
- * Returns the string unchanged when no balanced quote pair is found.
73
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
74
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
75
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
91
76
  *
92
- * @example
93
- * trimQuotes('"hello"') // 'hello'
94
- * trimQuotes('hello') // 'hello'
95
- */
96
- function trimQuotes(text) {
97
- if (text.length >= 2) {
98
- const first = text[0];
99
- const last = text[text.length - 1];
100
- if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
101
- }
102
- return text;
103
- }
104
- /**
105
- * Escapes characters that are not allowed inside JS string literals.
106
- * Handles quotes, backslashes, and Unicode line terminators (U+2028 / U+2029).
77
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
78
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
79
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
80
+ * absolute path, letting generated files escape the configured output directory.
107
81
  *
108
- * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
82
+ * @example Nested path from a dotted name
83
+ * `toFilePath('pet.petId') // 'pet/petId'`
109
84
  *
110
- * @example
111
- * ```ts
112
- * jsStringEscape('say "hi"\nbye') // 'say \\"hi\\"\\nbye'
113
- * ```
114
- */
115
- function jsStringEscape(input) {
116
- return `${input}`.replace(/["'\\\n\r\u2028\u2029]/g, (character) => {
117
- switch (character) {
118
- case "\"":
119
- case "'":
120
- case "\\": return `\\${character}`;
121
- case "\n": return "\\n";
122
- case "\r": return "\\r";
123
- case "\u2028": return "\\u2028";
124
- case "\u2029": return "\\u2029";
125
- default: return "";
126
- }
127
- });
128
- }
129
- //#endregion
130
- //#region ../../internals/utils/src/object.ts
131
- /**
132
- * Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first.
85
+ * @example PascalCase the final segment
86
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
133
87
  *
134
- * @example
135
- * stringify('hello') // '"hello"'
136
- * stringify('"hello"') // '"hello"'
88
+ * @example Suffix applied to the final segment only
89
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
137
90
  */
138
- function stringify(value) {
139
- if (value === void 0 || value === null) return "\"\"";
140
- return JSON.stringify(trimQuotes(value.toString()));
91
+ function toFilePath(name, caseLast = camelCase) {
92
+ const parts = name.split(/\.(?=[a-zA-Z])/);
93
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
141
94
  }
142
95
  //#endregion
143
96
  //#region ../../internals/utils/src/reserved.ts
@@ -273,26 +226,24 @@ const OPTIONAL_ADDS_UNDEFINED = new Set(["undefined", "questionTokenAndUndefined
273
226
  */
274
227
  const OPTIONAL_ADDS_QUESTION_TOKEN = new Set(["questionToken", "questionTokenAndUndefined"]);
275
228
  /**
276
- * `enumType` values that append a `Key` suffix to the generated enum type alias.
229
+ * `enum.type` values that append a `typeSuffix` to the generated enum type alias.
277
230
  */
278
- const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst", "asPascalConst"]);
231
+ const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"]);
279
232
  /**
280
- * `enumType` values that require a runtime value declaration (object, enum, or literal).
233
+ * `enum.type` values that require a runtime value declaration (object, enum, or literal).
281
234
  */
282
235
  const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
283
236
  "enum",
284
237
  "asConst",
285
- "asPascalConst",
286
238
  "constEnum",
287
239
  "literal",
288
240
  void 0
289
241
  ]);
290
242
  /**
291
- * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
243
+ * `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
292
244
  */
293
245
  const ENUM_TYPES_WITH_TYPE_ONLY = new Set([
294
246
  "asConst",
295
- "asPascalConst",
296
247
  "literal",
297
248
  void 0
298
249
  ]);
@@ -503,7 +454,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
503
454
  return factory.createLiteralTypeNode(factory.createNumericLiteral(value?.toString()));
504
455
  }
505
456
  if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
506
- if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
457
+ if (value !== null && value !== void 0) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
507
458
  }).filter((node) => node !== void 0)))];
508
459
  if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value]) => {
509
460
  let initializer = factory.createStringLiteral(value?.toString());
@@ -514,7 +465,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
514
465
  const casingKey = applyEnumKeyCasing(`${typeName}_${key}`, enumKeyCasing);
515
466
  return factory.createEnumMember(propertyName(casingKey), initializer);
516
467
  }
517
- if (key) {
468
+ if (key !== null && key !== void 0) {
518
469
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
519
470
  return factory.createEnumMember(propertyName(casingKey), initializer);
520
471
  }
@@ -526,7 +477,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
526
477
  if (isNumber(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
527
478
  else initializer = factory.createNumericLiteral(value);
528
479
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
529
- if (key) {
480
+ if (key !== null && key !== void 0) {
530
481
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
531
482
  return factory.createPropertyAssignment(propertyName(casingKey), initializer);
532
483
  }
@@ -612,12 +563,10 @@ const createStringLiteral = factory.createStringLiteral;
612
563
  * Creates an array type node (e.g., `T[]`).
613
564
  */
614
565
  const createArrayTypeNode = factory.createArrayTypeNode;
615
- factory.createParenthesizedType;
616
566
  /**
617
567
  * Creates a literal type node (e.g., `'hello'`, `42`, `true`).
618
568
  */
619
569
  const createLiteralTypeNode = factory.createLiteralTypeNode;
620
- factory.createNull;
621
570
  /**
622
571
  * Creates an identifier node.
623
572
  */
@@ -642,8 +591,6 @@ const createTrue = factory.createTrue;
642
591
  * Creates a boolean false literal type node.
643
592
  */
644
593
  const createFalse = factory.createFalse;
645
- factory.createIndexedAccessTypeNode;
646
- factory.createTypeOperatorNode;
647
594
  /**
648
595
  * Creates a prefix unary expression (e.g., negative numbers, logical not).
649
596
  */
@@ -691,12 +638,12 @@ function buildTupleNode(node, print) {
691
638
  /**
692
639
  * Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
693
640
  */
694
- function buildPropertyType(schema, baseType, optionalType) {
641
+ function buildPropertyType(schema, baseType, optionalType, optional) {
695
642
  const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType);
696
- const meta = ast.syncSchemaRef(schema);
643
+ const meta = syncSchemaRef(schema);
697
644
  let type = baseType;
698
645
  if (meta.nullable) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] });
699
- if ((meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
646
+ if ((optional || meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
700
647
  return type;
701
648
  }
702
649
  /**
@@ -724,41 +671,44 @@ function buildIndexSignatures(node, propertyCount, print) {
724
671
  * Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
725
672
  *
726
673
  * The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
727
- * valid TypeScript identifier. The resolver normalizes it; for inline enum
728
- * properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
674
+ * valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
675
+ * already emits a PascalCase+suffix name, so resolution is typically a no-op.
676
+ *
677
+ * When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
678
+ * resolve to the same name, which TypeScript merges into a single value+type declaration.
729
679
  */
730
- function getEnumNames({ node, enumType, enumTypeSuffix, resolver }) {
680
+ function getEnumNames({ node, enum: enumOptions, resolver }) {
731
681
  const resolved = resolver.default(node.name, "type");
732
682
  return {
733
- enumName: enumType === "asPascalConst" ? resolved : camelCase(node.name),
734
- typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolved
683
+ enumName: enumOptions.constCasing === "pascalCase" ? resolved : camelCase(node.name),
684
+ typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
735
685
  };
736
686
  }
737
687
  /**
738
688
  * Renders the enum declaration(s) for a single named `EnumSchemaNode`.
739
689
  *
740
- * Depending on `enumType` this may emit:
741
- * - A runtime object (`asConst` / `asPascalConst`) plus a `typeof` type alias
690
+ * Depending on `enum.type` this may emit:
691
+ * - A runtime object (`asConst`) plus a `typeof` type alias
742
692
  * - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
743
693
  * - A union literal type alias (`literal`)
744
694
  *
745
695
  * The emitted `File.Source` nodes carry the resolved names so that the barrel
746
696
  * index picks up the correct export identifiers.
747
697
  */
748
- function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
698
+ function Enum({ node, enum: enumOptions, resolver }) {
749
699
  const { enumName, typeName } = getEnumNames({
750
700
  node,
751
- enumType,
752
- enumTypeSuffix,
701
+ enum: enumOptions,
753
702
  resolver
754
703
  });
755
704
  const [nameNode, typeNode] = createEnumDeclaration({
756
705
  name: enumName,
757
706
  typeName,
758
707
  enums: node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
759
- type: enumType,
760
- enumKeyCasing
708
+ type: enumOptions.type,
709
+ enumKeyCasing: enumOptions.keyCasing
761
710
  });
711
+ const namesMerge = !!nameNode && enumName === typeName;
762
712
  return /* @__PURE__ */ jsxs(Fragment, { children: [nameNode && /* @__PURE__ */ jsx(File.Source, {
763
713
  name: enumName,
764
714
  isExportable: true,
@@ -767,15 +717,15 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
767
717
  children: parserTs.print(nameNode)
768
718
  }), /* @__PURE__ */ jsx(File.Source, {
769
719
  name: typeName,
770
- isIndexable: true,
771
- isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType),
772
- isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumType),
720
+ isIndexable: !namesMerge,
721
+ isExportable: !namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type),
722
+ isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type),
773
723
  children: parserTs.print(typeNode)
774
724
  })] });
775
725
  }
776
726
  //#endregion
777
727
  //#region src/components/Type.tsx
778
- function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
728
+ function Type({ name, node, printer, enum: enumOptions, resolver }) {
779
729
  const enumSchemaNodes = ast.collect(node, { schema(n) {
780
730
  const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
781
731
  if (enumNode?.name) return enumNode;
@@ -787,19 +737,16 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
787
737
  node,
788
738
  ...getEnumNames({
789
739
  node,
790
- enumType,
791
- enumTypeSuffix,
740
+ enum: enumOptions,
792
741
  resolver
793
742
  })
794
743
  };
795
744
  });
796
- const shouldExportEnums = enumType !== "inlineLiteral";
797
- const shouldExportType = enumType === "inlineLiteral" || enums.every((item) => item.typeName !== name);
745
+ const shouldExportEnums = enumOptions.type !== "inlineLiteral";
746
+ const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
798
747
  return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
799
748
  node,
800
- enumType,
801
- enumTypeSuffix,
802
- enumKeyCasing,
749
+ enum: enumOptions,
803
750
  resolver
804
751
  }, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
805
752
  name,
@@ -860,7 +807,7 @@ function resolveContentTypeVariants(entries, baseName) {
860
807
  });
861
808
  }
862
809
  function getOperationParameters(node, options = {}) {
863
- const params = ast.caseParams(node.parameters, options.paramsCasing);
810
+ const params = caseParams(node.parameters, options.paramsCasing);
864
811
  return {
865
812
  path: params.filter((param) => param.in === "path"),
866
813
  query: params.filter((param) => param.in === "query"),
@@ -875,26 +822,24 @@ function getOperationParameters(node, options = {}) {
875
822
  * shared default naming so every plugin groups output consistently:
876
823
  *
877
824
  * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
878
- * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
825
+ * - other groups use the camelCased group (`pet store` → `petStore`).
879
826
  *
880
827
  * A user-provided `group.name` always wins over the default namer, so callers stay in
881
828
  * control of their output folders. Returns `null` when grouping is disabled, matching the
882
829
  * per-plugin convention.
883
830
  *
884
831
  * @param group - The user-supplied group option, or `undefined` to disable grouping.
885
- * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
886
832
  *
887
833
  * @example
888
834
  * ```ts
889
- * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-client, …
890
- * createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
835
+ * createGroupConfig(group) // shared across every plugin
891
836
  * ```
892
837
  */
893
- function createGroupConfig(group, options) {
838
+ function createGroupConfig(group) {
894
839
  if (!group) return null;
895
840
  const defaultName = (ctx) => {
896
841
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
897
- return `${camelCase(ctx.group)}${options.suffix}`;
842
+ return camelCase(ctx.group);
898
843
  };
899
844
  return {
900
845
  ...group,
@@ -910,8 +855,11 @@ function createGroupConfig(group, options) {
910
855
  * Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
911
856
 
912
857
  */
913
- function buildPropertyJSDocComments(schema) {
914
- const meta = ast.syncSchemaRef(schema);
858
+ function isSchemaOptional(schema) {
859
+ return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
860
+ }
861
+ function buildPropertyJSDocComments(schema, optional) {
862
+ const meta = syncSchemaRef(schema);
915
863
  const isArray = meta?.primitive === "array";
916
864
  const hasDescription = meta && "description" in meta && meta.description;
917
865
  const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
@@ -924,83 +872,85 @@ function buildPropertyJSDocComments(schema) {
924
872
  meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
925
873
  meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
926
874
  meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
927
- meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : null].filter(Boolean).join("") : null
875
+ meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null
928
876
  ].filter(Boolean);
929
877
  }
930
878
  function buildParams(node, { params, resolver }) {
931
- return ast.createSchema({
879
+ return ast.factory.createSchema({
932
880
  type: "object",
933
- properties: params.map((param) => ast.createProperty({
881
+ properties: params.map((param) => ast.factory.createProperty({
934
882
  name: param.name,
935
883
  required: param.required,
936
- schema: ast.createSchema({
884
+ schema: ast.factory.createSchema({
937
885
  type: "ref",
938
- name: resolver.resolveParamName(node, param)
886
+ name: resolver.resolveParamName(node, param),
887
+ optional: !param.required
939
888
  })
940
889
  }))
941
890
  });
942
891
  }
943
892
  function buildData(node, { resolver }) {
944
893
  const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
945
- return ast.createSchema({
894
+ return ast.factory.createSchema({
946
895
  type: "object",
947
896
  deprecated: node.deprecated,
948
897
  properties: [
949
- ast.createProperty({
898
+ ast.factory.createProperty({
950
899
  name: "data",
951
- schema: node.requestBody?.content?.[0]?.schema ? ast.createSchema({
900
+ schema: node.requestBody?.content?.[0]?.schema ? ast.factory.createSchema({
952
901
  type: "ref",
953
902
  name: resolver.resolveDataName(node),
954
903
  optional: true
955
- }) : ast.createSchema({
904
+ }) : ast.factory.createSchema({
956
905
  type: "never",
957
906
  primitive: void 0,
958
907
  optional: true
959
908
  })
960
909
  }),
961
- ast.createProperty({
910
+ ast.factory.createProperty({
962
911
  name: "pathParams",
963
912
  required: pathParams.length > 0,
964
913
  schema: pathParams.length > 0 ? buildParams(node, {
965
914
  params: pathParams,
966
915
  resolver
967
- }) : ast.createSchema({
916
+ }) : ast.factory.createSchema({
968
917
  type: "never",
969
- primitive: void 0
918
+ primitive: void 0,
919
+ optional: true
970
920
  })
971
921
  }),
972
- ast.createProperty({
922
+ ast.factory.createProperty({
973
923
  name: "queryParams",
974
- schema: queryParams.length > 0 ? ast.createSchema({
924
+ schema: queryParams.length > 0 ? ast.factory.createSchema({
975
925
  ...buildParams(node, {
976
926
  params: queryParams,
977
927
  resolver
978
928
  }),
979
929
  optional: true
980
- }) : ast.createSchema({
930
+ }) : ast.factory.createSchema({
981
931
  type: "never",
982
932
  primitive: void 0,
983
933
  optional: true
984
934
  })
985
935
  }),
986
- ast.createProperty({
936
+ ast.factory.createProperty({
987
937
  name: "headerParams",
988
- schema: headerParams.length > 0 ? ast.createSchema({
938
+ schema: headerParams.length > 0 ? ast.factory.createSchema({
989
939
  ...buildParams(node, {
990
940
  params: headerParams,
991
941
  resolver
992
942
  }),
993
943
  optional: true
994
- }) : ast.createSchema({
944
+ }) : ast.factory.createSchema({
995
945
  type: "never",
996
946
  primitive: void 0,
997
947
  optional: true
998
948
  })
999
949
  }),
1000
- ast.createProperty({
950
+ ast.factory.createProperty({
1001
951
  name: "url",
1002
952
  required: true,
1003
- schema: ast.createSchema({
953
+ schema: ast.factory.createSchema({
1004
954
  type: "url",
1005
955
  path: node.path
1006
956
  })
@@ -1010,12 +960,12 @@ function buildData(node, { resolver }) {
1010
960
  }
1011
961
  function buildResponses(node, { resolver }) {
1012
962
  if (node.responses.length === 0) return null;
1013
- return ast.createSchema({
963
+ return ast.factory.createSchema({
1014
964
  type: "object",
1015
- properties: node.responses.map((res) => ast.createProperty({
965
+ properties: node.responses.map((res) => ast.factory.createProperty({
1016
966
  name: String(res.statusCode),
1017
967
  required: true,
1018
- schema: ast.createSchema({
968
+ schema: ast.factory.createSchema({
1019
969
  type: "ref",
1020
970
  name: resolver.resolveResponseStatusName(node, res.statusCode)
1021
971
  })
@@ -1025,9 +975,9 @@ function buildResponses(node, { resolver }) {
1025
975
  function buildResponseUnion(node, { resolver }) {
1026
976
  const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
1027
977
  if (responsesWithSchema.length === 0) return null;
1028
- return ast.createSchema({
978
+ return ast.factory.createSchema({
1029
979
  type: "union",
1030
- members: responsesWithSchema.map((res) => ast.createSchema({
980
+ members: responsesWithSchema.map((res) => ast.factory.createSchema({
1031
981
  type: "ref",
1032
982
  name: resolver.resolveResponseStatusName(node, res.statusCode)
1033
983
  }))
@@ -1051,13 +1001,13 @@ function isNonNullable(value) {
1051
1001
  *
1052
1002
  * @example Raw type node (no `typeName`)
1053
1003
  * ```ts
1054
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
1004
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
1055
1005
  * const typeNode = printer.print(schemaNode) // ts.TypeNode
1056
1006
  * ```
1057
1007
  *
1058
1008
  * @example Full declaration (with `typeName`)
1059
1009
  * ```ts
1060
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
1010
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
1061
1011
  * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
1062
1012
  * ```
1063
1013
  */
@@ -1091,27 +1041,27 @@ const printerTs = ast.definePrinter((options) => {
1091
1041
  time: dateOrStringNode,
1092
1042
  ref(node) {
1093
1043
  if (!node.name) return null;
1094
- const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
1095
- return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
1044
+ const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
1045
+ return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
1096
1046
  },
1097
1047
  enum(node) {
1098
1048
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
1099
- if (this.options.enumType === "inlineLiteral" || !node.name) return createUnionDeclaration({
1049
+ if (this.options.enum.type === "inlineLiteral" || !node.name) return createUnionDeclaration({
1100
1050
  withParentheses: true,
1101
1051
  nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
1102
1052
  }) ?? void 0;
1103
- return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
1053
+ return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
1104
1054
  },
1105
1055
  union(node) {
1106
1056
  const members = node.members ?? [];
1107
1057
  const hasStringLiteral = members.some((m) => {
1108
1058
  return ast.narrowSchema(m, ast.schemaTypes.enum)?.primitive === "string";
1109
1059
  });
1110
- const hasPlainString = members.some((m) => ast.isStringType(m));
1060
+ const hasPlainString = members.some((m) => isStringType(m));
1111
1061
  if (hasStringLiteral && hasPlainString) return createUnionDeclaration({
1112
1062
  withParentheses: true,
1113
1063
  nodes: members.map((m) => {
1114
- if (ast.isStringType(m)) return createIntersectionDeclaration({
1064
+ if (isStringType(m)) return createIntersectionDeclaration({
1115
1065
  nodes: [keywordTypeNodes.string, createTypeLiteralNode([])],
1116
1066
  withParentheses: true
1117
1067
  });
@@ -1131,7 +1081,7 @@ const printerTs = ast.definePrinter((options) => {
1131
1081
  },
1132
1082
  array(node) {
1133
1083
  return createArrayDeclaration({
1134
- nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
1084
+ nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
1135
1085
  arrayType: this.options.arrayType
1136
1086
  }) ?? null;
1137
1087
  },
@@ -1141,18 +1091,19 @@ const printerTs = ast.definePrinter((options) => {
1141
1091
  object(node) {
1142
1092
  const { transform, options } = this;
1143
1093
  const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType);
1144
- const propertyNodes = node.properties.map((prop) => {
1145
- const baseType = transform(prop.schema) ?? keywordTypeNodes.unknown;
1146
- const type = buildPropertyType(prop.schema, baseType, options.optionalType);
1147
- const propMeta = ast.syncSchemaRef(prop.schema);
1094
+ const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
1095
+ const baseType = output ?? keywordTypeNodes.unknown;
1096
+ const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
1097
+ const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
1098
+ const propMeta = syncSchemaRef(property.schema);
1148
1099
  return appendJSDocToNode({
1149
1100
  node: createPropertySignature({
1150
- questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
1151
- name: prop.name,
1101
+ questionToken: optional ? addsQuestionToken : false,
1102
+ name,
1152
1103
  type,
1153
1104
  readOnly: propMeta?.readOnly
1154
1105
  }),
1155
- comments: buildPropertyJSDocComments(prop.schema)
1106
+ comments: buildPropertyJSDocComments(property.schema, optional)
1156
1107
  });
1157
1108
  });
1158
1109
  const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)];
@@ -1165,7 +1116,7 @@ const printerTs = ast.definePrinter((options) => {
1165
1116
  const { name, syntaxType = "type", description, keysToOmit } = this.options;
1166
1117
  const transformed = this.transform(node);
1167
1118
  if (!transformed) return null;
1168
- const meta = ast.syncSchemaRef(node);
1119
+ const meta = syncSchemaRef(node);
1169
1120
  if (!name) {
1170
1121
  const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
1171
1122
  const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
@@ -1204,15 +1155,14 @@ const printerTs = ast.definePrinter((options) => {
1204
1155
  */
1205
1156
  const typeGenerator = defineGenerator({
1206
1157
  name: "typescript",
1207
- renderer: jsxRendererSync,
1158
+ renderer: jsxRenderer,
1208
1159
  schema(node, ctx) {
1209
- const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
1160
+ const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
1210
1161
  const { adapter, config, resolver, root } = ctx;
1211
1162
  if (!node.name) return;
1212
- const mode = ctx.getMode(output);
1213
1163
  const enumSchemaNames = new Set(ctx.meta.enumNames);
1214
1164
  function resolveImportName(schemaName) {
1215
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1165
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
1216
1166
  return resolver.resolveTypeName(schemaName);
1217
1167
  }
1218
1168
  const imports = adapter.getImports(node, (schemaName) => ({
@@ -1228,7 +1178,7 @@ const typeGenerator = defineGenerator({
1228
1178
  }));
1229
1179
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
1230
1180
  const meta = {
1231
- name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
1181
+ name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolver.resolveTypeName(node.name),
1232
1182
  file: resolver.resolveFile({
1233
1183
  name: node.name,
1234
1184
  extname: ".ts"
@@ -1241,8 +1191,7 @@ const typeGenerator = defineGenerator({
1241
1191
  const schemaPrinter = printerTs({
1242
1192
  optionalType,
1243
1193
  arrayType,
1244
- enumType,
1245
- enumTypeSuffix,
1194
+ enum: enumOptions,
1246
1195
  name: meta.name,
1247
1196
  syntaxType,
1248
1197
  description: node.description,
@@ -1270,7 +1219,7 @@ const typeGenerator = defineGenerator({
1270
1219
  baseName: meta.file.baseName
1271
1220
  }
1272
1221
  }),
1273
- children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1222
+ children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1274
1223
  root: meta.file.path,
1275
1224
  path: imp.path,
1276
1225
  name: imp.name,
@@ -1282,19 +1231,16 @@ const typeGenerator = defineGenerator({
1282
1231
  ].join("-"))), /* @__PURE__ */ jsx(Type, {
1283
1232
  name: meta.name,
1284
1233
  node,
1285
- enumType,
1286
- enumTypeSuffix,
1287
- enumKeyCasing,
1234
+ enum: enumOptions,
1288
1235
  resolver,
1289
1236
  printer: schemaPrinter
1290
1237
  })]
1291
1238
  });
1292
1239
  },
1293
1240
  operation(node, ctx) {
1294
- const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
1241
+ const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
1295
1242
  const { adapter, config, resolver, root } = ctx;
1296
- const mode = ctx.getMode(output);
1297
- const params = ast.caseParams(node.parameters, paramsCasing);
1243
+ const params = caseParams(node.parameters, paramsCasing);
1298
1244
  const meta = { file: resolver.resolveFile({
1299
1245
  name: node.operationId,
1300
1246
  extname: ".ts",
@@ -1307,7 +1253,7 @@ const typeGenerator = defineGenerator({
1307
1253
  }) };
1308
1254
  const enumSchemaNames = new Set(ctx.meta.enumNames);
1309
1255
  function resolveImportName(schemaName) {
1310
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix);
1256
+ if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
1311
1257
  return resolver.resolveTypeName(schemaName);
1312
1258
  }
1313
1259
  function renderSchemaType({ schema, name, keysToOmit }) {
@@ -1326,8 +1272,7 @@ const typeGenerator = defineGenerator({
1326
1272
  const schemaPrinter = printerTs({
1327
1273
  optionalType,
1328
1274
  arrayType,
1329
- enumType,
1330
- enumTypeSuffix,
1275
+ enum: enumOptions,
1331
1276
  name,
1332
1277
  syntaxType,
1333
1278
  description: schema.description,
@@ -1336,7 +1281,7 @@ const typeGenerator = defineGenerator({
1336
1281
  enumSchemaNames,
1337
1282
  nodes: printer?.nodes
1338
1283
  });
1339
- return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1284
+ return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1340
1285
  root: meta.file.path,
1341
1286
  path: imp.path,
1342
1287
  name: imp.name,
@@ -1348,9 +1293,7 @@ const typeGenerator = defineGenerator({
1348
1293
  ].join("-"))), /* @__PURE__ */ jsx(Type, {
1349
1294
  name,
1350
1295
  node: schema,
1351
- enumType,
1352
- enumTypeSuffix,
1353
- enumKeyCasing,
1296
+ enum: enumOptions,
1354
1297
  resolver,
1355
1298
  printer: schemaPrinter
1356
1299
  })] });
@@ -1361,9 +1304,9 @@ const typeGenerator = defineGenerator({
1361
1304
  */
1362
1305
  function buildContentTypeVariants(entries, baseName, decorate) {
1363
1306
  const variants = resolveContentTypeVariants(entries, baseName);
1364
- const unionSchema = ast.createSchema({
1307
+ const unionSchema = ast.factory.createSchema({
1365
1308
  type: "union",
1366
- members: variants.map((variant) => ast.createSchema({
1309
+ members: variants.map((variant) => ast.factory.createSchema({
1367
1310
  type: "ref",
1368
1311
  name: variant.name
1369
1312
  }))
@@ -1378,7 +1321,10 @@ const typeGenerator = defineGenerator({
1378
1321
  })] });
1379
1322
  }
1380
1323
  const paramTypes = params.map((param) => renderSchemaType({
1381
- schema: param.schema,
1324
+ schema: {
1325
+ ...param.schema,
1326
+ optional: !param.required
1327
+ },
1382
1328
  name: resolver.resolveParamName(node, param)
1383
1329
  }));
1384
1330
  const requestBodyContent = node.requestBody?.content ?? [];
@@ -1481,7 +1427,7 @@ const typeGenerator = defineGenerator({
1481
1427
  * casing/file-layout rules.
1482
1428
  *
1483
1429
  * The `default` method is supplied by `defineResolver`. It uses PascalCase for
1484
- * type names and PascalCase-with-isFile for files.
1430
+ * type names and PascalCase file paths (dotted names become `/`-joined) for files.
1485
1431
  *
1486
1432
  * @example Resolve a type and file name
1487
1433
  * ```ts
@@ -1497,15 +1443,15 @@ const resolverTs = defineResolver(() => {
1497
1443
  name: "default",
1498
1444
  pluginName: "plugin-ts",
1499
1445
  default(name, type) {
1500
- const resolved = pascalCase(name, { isFile: type === "file" });
1501
- return type === "file" ? resolved : ensureValidVarName(resolved);
1446
+ if (type === "file") return toFilePath(name, pascalCase);
1447
+ return ensureValidVarName(pascalCase(name));
1502
1448
  },
1503
1449
  resolveTypeName(name) {
1504
1450
  return ensureValidVarName(pascalCase(name));
1505
1451
  },
1506
1452
  resolvePathName(name, type) {
1507
- const resolved = pascalCase(name, { isFile: type === "file" });
1508
- return type === "file" ? resolved : ensureValidVarName(resolved);
1453
+ if (type === "file") return toFilePath(name, pascalCase);
1454
+ return ensureValidVarName(pascalCase(name));
1509
1455
  },
1510
1456
  resolveParamName(node, param) {
1511
1457
  return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
@@ -1563,7 +1509,7 @@ const pluginTsName = "plugin-ts";
1563
1509
  * plugins: [
1564
1510
  * pluginTs({
1565
1511
  * output: { path: './types' },
1566
- * enumType: 'asConst',
1512
+ * enum: { type: 'asConst' },
1567
1513
  * optionalType: 'questionTokenAndUndefined',
1568
1514
  * }),
1569
1515
  * ],
@@ -1573,9 +1519,15 @@ const pluginTsName = "plugin-ts";
1573
1519
  const pluginTs = definePlugin((options) => {
1574
1520
  const { output = {
1575
1521
  path: "types",
1576
- barrelType: "named"
1577
- }, group, exclude = [], include, override = [], enumType = "asConst", enumTypeSuffix = "Key", enumKeyCasing = "none", optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
1578
- const groupConfig = createGroupConfig(group, { suffix: "Controller" });
1522
+ barrel: { type: "named" }
1523
+ }, group, exclude = [], include, override = [], enum: enumOptions = {}, optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, macros: userMacros, generators: userGenerators = [] } = options;
1524
+ const groupConfig = createGroupConfig(group);
1525
+ const resolvedEnum = {
1526
+ type: enumOptions.type ?? "asConst",
1527
+ constCasing: enumOptions.constCasing ?? "camelCase",
1528
+ typeSuffix: enumOptions.typeSuffix ?? "Key",
1529
+ keyCasing: enumOptions.keyCasing ?? "none"
1530
+ };
1579
1531
  return {
1580
1532
  name: pluginTsName,
1581
1533
  options,
@@ -1588,9 +1540,7 @@ const pluginTs = definePlugin((options) => {
1588
1540
  optionalType,
1589
1541
  group: groupConfig,
1590
1542
  arrayType,
1591
- enumType,
1592
- enumTypeSuffix,
1593
- enumKeyCasing,
1543
+ enum: resolvedEnum,
1594
1544
  syntaxType,
1595
1545
  paramsCasing,
1596
1546
  printer
@@ -1599,7 +1549,7 @@ const pluginTs = definePlugin((options) => {
1599
1549
  ...resolverTs,
1600
1550
  ...userResolver
1601
1551
  } : resolverTs);
1602
- if (userTransformer) ctx.setTransformer(userTransformer);
1552
+ if (userMacros?.length) ctx.setMacros(userMacros);
1603
1553
  ctx.addGenerator(typeGenerator);
1604
1554
  for (const gen of userGenerators) ctx.addGenerator(gen);
1605
1555
  } }
@@ -1607,37 +1557,75 @@ const pluginTs = definePlugin((options) => {
1607
1557
  });
1608
1558
  //#endregion
1609
1559
  //#region src/printers/functionPrinter.ts
1610
- const kindToHandlerKey = {
1611
- FunctionParameter: "functionParameter",
1612
- ParameterGroup: "parameterGroup",
1613
- FunctionParameters: "functionParameters",
1614
- ParamsType: "paramsType"
1615
- };
1616
1560
  /**
1617
- * Creates a function-parameter printer factory.
1561
+ * Renders a {@link ast.TypeExpression} to its TypeScript source.
1562
+ *
1563
+ * - a `string` is a type reference, returned as-is
1564
+ * - an `IndexedAccessType` becomes `objectType['indexType']`
1565
+ * - a `TypeLiteral` becomes `{ key: Type; key?: Type }`
1618
1566
  *
1619
- * Uses `createPrinterFactory` and dispatches handlers by `node.kind`
1620
- * (for function nodes) rather than by `node.type` (for schema nodes).
1567
+ * `transformType` is applied once to the fully rendered type, matching how the
1568
+ * printer wrapped reference types before the `ts.factory` model.
1621
1569
  */
1622
- const defineFunctionPrinter = ast.createPrinterFactory((node) => kindToHandlerKey[node.kind]);
1570
+ function renderType(type, transformType) {
1571
+ const rendered = renderTypeExpression(type);
1572
+ return transformType ? transformType(rendered) : rendered;
1573
+ }
1574
+ function renderTypeExpression(type) {
1575
+ if (typeof type === "string") return type;
1576
+ if (ast.indexedAccessTypeDef.is(type)) return `${type.target}['${type.key}']`;
1577
+ return `{ ${type.members.map((member) => {
1578
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(member.name) ? member.name : JSON.stringify(member.name);
1579
+ const value = renderTypeExpression(member.type);
1580
+ return member.optional ? `${key}?: ${value}` : `${key}: ${value}`;
1581
+ }).join("; ")} }`;
1582
+ }
1583
+ function groupMembers(param) {
1584
+ if (typeof param.name === "string") return null;
1585
+ return param.type && ast.typeLiteralDef.is(param.type) ? param.type.members : [];
1586
+ }
1623
1587
  function rank(param) {
1624
- if (param.kind === "ParameterGroup") {
1625
- if (param.default) return PARAM_RANK.withDefault;
1626
- return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? PARAM_RANK.optional : PARAM_RANK.required;
1627
- }
1628
1588
  if (param.rest) return PARAM_RANK.rest;
1629
1589
  if (param.default) return PARAM_RANK.withDefault;
1590
+ const members = groupMembers(param);
1591
+ if (members) return members.every((m) => m.optional) ? PARAM_RANK.optional : PARAM_RANK.required;
1630
1592
  return param.optional ? PARAM_RANK.optional : PARAM_RANK.required;
1631
1593
  }
1632
1594
  function sortParams(params) {
1633
1595
  return params.toSorted((a, b) => rank(a) - rank(b));
1634
1596
  }
1635
- function sortChildParams(params) {
1636
- return params.toSorted((a, b) => rank(a) - rank(b));
1597
+ /**
1598
+ * Orders a destructured group's binding elements and type members together,
1599
+ * required fields first, matching how grouped children were sorted before.
1600
+ */
1601
+ function sortedGroupMembers(name, type) {
1602
+ const members = type && ast.typeLiteralDef.is(type) ? type.members : [];
1603
+ const memberRank = (optional) => optional ? PARAM_RANK.optional : PARAM_RANK.required;
1604
+ return name.elements.map((element, index) => ({
1605
+ name: element.name,
1606
+ type: members[index]?.type,
1607
+ optional: members[index]?.optional
1608
+ })).toSorted((a, b) => memberRank(a.optional) - memberRank(b.optional));
1609
+ }
1610
+ /**
1611
+ * Renders the type annotation of a destructured group. An inline object type is
1612
+ * rendered from the already-sorted members so its key order matches the binding;
1613
+ * a reference type is rendered as-is.
1614
+ */
1615
+ function renderGroupType(type, sorted, transformType) {
1616
+ if (!type) return void 0;
1617
+ if (!ast.typeLiteralDef.is(type)) return renderType(type);
1618
+ const typed = sorted.filter((member) => member.type !== void 0);
1619
+ if (!typed.length) return void 0;
1620
+ return `{ ${typed.map((member) => {
1621
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(member.name) ? member.name : JSON.stringify(member.name);
1622
+ const value = renderType(member.type, transformType);
1623
+ return member.optional ? `${key}?: ${value}` : `${key}: ${value}`;
1624
+ }).join("; ")} }`;
1637
1625
  }
1638
1626
  /**
1639
- * Default function-signature printer.
1640
- * Covers the four standard output modes used across Kubb plugins.
1627
+ * Default function-signature printer. Renders a parameter list in one of two modes:
1628
+ * `declaration` for the function signature and `call` for the call arguments.
1641
1629
  *
1642
1630
  * @example
1643
1631
  * ```ts
@@ -1653,28 +1641,31 @@ function sortChildParams(params) {
1653
1641
  * printer.print(sig) // → "petId: string, config: Config = {}"
1654
1642
  * ```
1655
1643
  */
1656
- const functionPrinter = defineFunctionPrinter((options) => ({
1644
+ const functionPrinter = ast.createPrinterFactory((node) => node.kind)((options) => ({
1657
1645
  name: "functionParameters",
1658
1646
  options,
1659
1647
  nodes: {
1660
- paramsType(node) {
1661
- if (node.kind !== "ParamsType") return null;
1662
- if (node.variant === "member") return `${node.base}['${node.key}']`;
1663
- if (node.variant === "struct") return `{ ${node.properties.map((p) => {
1664
- const typeStr = this.transform(p.type);
1665
- const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name);
1666
- return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`;
1667
- }).join("; ")} }`;
1668
- if (node.variant === "reference") return node.name;
1669
- return null;
1670
- },
1671
- functionParameter(node) {
1648
+ FunctionParameter(node) {
1672
1649
  const { mode, transformName, transformType } = this.options;
1650
+ const isGroup = typeof node.name !== "string";
1651
+ if (mode === "call") {
1652
+ if (isGroup) return `{ ${sortedGroupMembers(node.name, node.type).map((member) => member.name).join(", ")} }`;
1653
+ const name = transformName ? transformName(node.name) : node.name;
1654
+ return node.rest ? `...${name}` : name;
1655
+ }
1656
+ if (isGroup) {
1657
+ const sorted = sortedGroupMembers(node.name, node.type);
1658
+ const binding = `{ ${sorted.map((member) => transformName ? transformName(member.name) : member.name).join(", ")} }`;
1659
+ const allOptional = sorted.every((member) => member.optional);
1660
+ const type = renderGroupType(node.type, sorted, transformType);
1661
+ if (type) {
1662
+ if (allOptional) return `${binding}: ${type} = ${node.default ?? "{}"}`;
1663
+ return node.default ? `${binding}: ${type} = ${node.default}` : `${binding}: ${type}`;
1664
+ }
1665
+ return node.default ? `${binding} = ${node.default}` : binding;
1666
+ }
1673
1667
  const name = transformName ? transformName(node.name) : node.name;
1674
- const rawType = node.type ? this.transform(node.type) : void 0;
1675
- const type = rawType != null && transformType ? transformType(rawType) : rawType;
1676
- if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
1677
- if (mode === "call") return node.rest ? `...${name}` : name;
1668
+ const type = node.type ? renderType(node.type, transformType) : void 0;
1678
1669
  if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
1679
1670
  if (type) {
1680
1671
  if (node.optional) return `${name}?: ${type}`;
@@ -1682,39 +1673,12 @@ const functionPrinter = defineFunctionPrinter((options) => ({
1682
1673
  }
1683
1674
  return node.default ? `${name} = ${node.default}` : name;
1684
1675
  },
1685
- parameterGroup(node) {
1686
- const { mode, transformName, transformType } = this.options;
1687
- const sorted = sortChildParams(node.properties);
1688
- const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
1689
- if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
1690
- if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
1691
- if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
1692
- const names = sorted.map((p) => {
1693
- return transformName ? transformName(p.name) : p.name;
1694
- });
1695
- const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
1696
- if (!nameStr) return null;
1697
- let typeAnnotation = node.type ? this.transform(node.type) ?? void 0 : void 0;
1698
- if (!typeAnnotation) {
1699
- const typeParts = sorted.filter((p) => p.type).map((p) => {
1700
- const rawT = p.type ? this.transform(p.type) : void 0;
1701
- const t = rawT != null && transformType ? transformType(rawT) : rawT;
1702
- return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
1703
- });
1704
- typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
1705
- }
1706
- if (typeAnnotation) {
1707
- if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
1708
- return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
1709
- }
1710
- return node.default ? `${nameStr} = ${node.default}` : nameStr;
1711
- },
1712
- functionParameters(node) {
1676
+ FunctionParameters(node) {
1713
1677
  return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
1714
1678
  }
1715
1679
  }
1716
1680
  }));
1717
1681
  //#endregion
1718
- export { Enum, Type, pluginTs as default, pluginTs, functionPrinter, pluginTsName, printerTs, resolverTs, typeGenerator };
1682
+ export { Enum, Type, pluginTs as default, pluginTs, functionPrinter, pluginTsName, printerTs, renderType, resolverTs, typeGenerator };
1719
1683
 
1720
1684
  //# sourceMappingURL=index.js.map