@kubb/plugin-fetch 5.0.0-beta.84 → 5.0.0-beta.86

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
- import { Exclude, Group, Include, Output, OutputOptions, Override, PluginFactoryOptions, Resolver, ast } from "kubb/kit";
2
+ import { Exclude, Group, Include, Output, OutputOptions, Override, PluginFactoryOptions, Resolver, ResolverPatch, ast } from "kubb/kit";
3
3
  //#region ../../internals/client/src/types.d.ts
4
4
  /**
5
5
  * Validator applied to request and response bodies using schemas from `@kubb/plugin-zod`.
@@ -21,39 +21,28 @@ type ValidatorOptions = false | 'zod' | {
21
21
  */
22
22
  type Mode = 'tag' | 'flat';
23
23
  /**
24
- * The resolver shared by the client plugins. Functions and files use camelCase; URL helpers get
25
- * a `get<Operation>Url` name.
24
+ * The resolver shared by the client plugins. Inherits the built-in camelCase `name` and `file`;
25
+ * classes and tag groups use PascalCase (with a `Client` suffix for groups).
26
26
  */
27
27
  type ResolverClient = Resolver & {
28
- /**
29
- * Resolves the function name for a raw operation name.
30
- *
31
- * @example
32
- * `resolver.resolveName('show pet by id') // -> 'showPetById'`
33
- */
34
- resolveName(this: ResolverClient, name: string): string;
35
- /**
36
- * Resolves the output file name for a generated client module.
37
- */
38
- resolvePathName(this: ResolverClient, name: string, type?: 'file' | 'function' | 'type' | 'const'): string;
39
28
  /**
40
29
  * Resolves the generated class name for class-based clients.
41
30
  */
42
- resolveClassName(this: ResolverClient, name: string): string;
31
+ className(this: ResolverClient, name: string): string;
43
32
  /**
44
33
  * Resolves the generated class name for a tag-based client group. The default appends a
45
34
  * `Client` suffix (tag `pet` becomes `PetClient`) so the class never collides with the schema
46
35
  * model of the same name in the barrel.
47
36
  *
48
37
  * @example
49
- * `resolver.resolveGroupName('pet') // -> 'PetClient'`
38
+ * `resolver.groupName('pet') // -> 'PetClient'`
50
39
  */
51
- resolveGroupName(this: ResolverClient, name: string): string;
40
+ groupName(this: ResolverClient, name: string): string;
52
41
  /**
53
42
  * Resolves the property name a tag client is exposed under on the composed root SDK
54
43
  * (`new PetStore(config).pet`).
55
44
  */
56
- resolveClientPropertyName(this: ResolverClient, name: string): string;
45
+ propertyName(this: ResolverClient, name: string): string;
57
46
  };
58
47
  /**
59
48
  * The shared options surface for the client plugins. Deliberately small: there is one
@@ -134,7 +123,7 @@ type Options = OutputOptions & {
134
123
  /**
135
124
  * Override how names and file paths are built. Methods you omit fall back to the default resolver.
136
125
  */
137
- resolver?: Partial<ResolverClient> & ThisType<ResolverClient>;
126
+ resolver?: ResolverPatch<ResolverClient>;
138
127
  /**
139
128
  * Macros applied to each operation node before code is printed.
140
129
  */
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
2
  import path from "node:path";
3
- import { ast, defineGenerator, definePlugin, defineResolver } from "kubb/kit";
3
+ import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
4
  import { File, Function, jsxRenderer } from "kubb/jsx";
5
5
  import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
6
6
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
@@ -45,31 +45,6 @@ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
45
45
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
46
46
  }
47
47
  //#endregion
48
- //#region ../../internals/utils/src/fs.ts
49
- /**
50
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
51
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
52
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
53
- *
54
- * Empty segments are dropped before joining. They arise when the name starts with a dot
55
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
56
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
57
- * absolute path, letting generated files escape the configured output directory.
58
- *
59
- * @example Nested path from a dotted name
60
- * `toFilePath('pet.petId') // 'pet/petId'`
61
- *
62
- * @example PascalCase the final segment
63
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
64
- *
65
- * @example Suffix applied to the final segment only
66
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
67
- */
68
- function toFilePath(name, caseLast = camelCase) {
69
- const parts = name.split(/\.(?=[a-zA-Z])/);
70
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
71
- }
72
- //#endregion
73
48
  //#region ../../internals/utils/src/reserved.ts
74
49
  /**
75
50
  * JavaScript and Java reserved words.
@@ -193,6 +168,23 @@ function ensureValidVarName(name) {
193
168
  return `_${name}`;
194
169
  }
195
170
  //#endregion
171
+ //#region ../../internals/utils/src/codegen.ts
172
+ /**
173
+ * Builds a JSDoc comment block from an array of lines. Returns `fallback` when there are no
174
+ * comments.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * buildJSDoc(['@type string', '@example hello'])
179
+ * // '/**\n * @type string\n * @example hello\n *\/\n '
180
+ * ```
181
+ */
182
+ function buildJSDoc(comments, options = {}) {
183
+ const { indent = " * ", suffix = "\n ", fallback = " " } = options;
184
+ if (comments.length === 0) return fallback;
185
+ return `/**\n${comments.map((c) => `${indent}${c}`).join("\n")}\n */${suffix}`;
186
+ }
187
+ //#endregion
196
188
  //#region ../../internals/utils/src/url.ts
197
189
  function transformParam(raw, casing) {
198
190
  const param = isValidVarName(raw) ? raw : camelCase(raw);
@@ -341,17 +333,45 @@ function dedupeByCasedName(params) {
341
333
  return true;
342
334
  });
343
335
  }
336
+ function buildParamsMapping(originalParams, mappedParams) {
337
+ const mapping = {};
338
+ let hasChanged = false;
339
+ originalParams.forEach((param, i) => {
340
+ const mappedName = mappedParams[i]?.name ?? param.name;
341
+ mapping[param.name] = mappedName;
342
+ if (param.name !== mappedName) hasChanged = true;
343
+ });
344
+ return hasChanged ? mapping : null;
345
+ }
346
+ function toAccess(object, name) {
347
+ return isValidVarName(name) ? `${object}.${name}` : `${object}[${JSON.stringify(name)}]`;
348
+ }
349
+ /**
350
+ * Renders the object-literal expression that renames the camelCased keys of a grouped request
351
+ * option back to the names the OpenAPI document declares, guarded so an omitted optional group
352
+ * stays omitted. Shared by the client and cypress generators, which pass a `buildParamsMapping`
353
+ * result and the source expression to read the keys from.
354
+ *
355
+ * @example
356
+ * ```ts
357
+ * buildParamsRemapExpression({ source: 'config.query', mapping: { include_deleted: 'includeDeleted' } })
358
+ * // 'config.query ? { "include_deleted": config.query.includeDeleted } : config.query'
359
+ * ```
360
+ */
361
+ function buildParamsRemapExpression({ source, mapping }) {
362
+ return `${source} ? { ${Object.entries(mapping).map(([originalName, casedName]) => `${JSON.stringify(originalName)}: ${toAccess(source, casedName)}`).join(", ")} } : ${source}`;
363
+ }
344
364
  //#endregion
345
365
  //#region ../../internals/shared/src/operation.ts
346
366
  /**
347
367
  * Builds the `ResolverFileParams` every operation generator passes to
348
- * `resolver.resolveFile`: a file named `name`, tagged by the operation's first
368
+ * `resolver.file`: a file named `name`, tagged by the operation's first
349
369
  * tag (or `'default'`), at the operation's path. Centralizes the entry object
350
370
  * that was repeated at dozens of call sites across the client and query plugins.
351
371
  *
352
372
  * @example
353
373
  * ```ts
354
- * resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group })
374
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
355
375
  * ```
356
376
  */
357
377
  function operationFileEntry(node, name, extname = ".ts") {
@@ -574,7 +594,7 @@ function resolveResponseValidator(validator) {
574
594
  * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
575
595
  */
576
596
  function buildZodResponseParse(node, zodResolver) {
577
- const name = zodResolver.resolveResponseName?.(node);
597
+ const name = zodResolver.response.response(node);
578
598
  return name ? {
579
599
  expression: name,
580
600
  importNames: [name]
@@ -587,7 +607,7 @@ function buildZodResponseParse(node, zodResolver) {
587
607
  */
588
608
  function buildZodErrorParse(node, zodResolver) {
589
609
  if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
590
- const name = zodResolver.resolveErrorName?.(node);
610
+ const name = zodResolver.response.error?.(node);
591
611
  return name ? {
592
612
  expression: name,
593
613
  importNames: [name]
@@ -662,6 +682,38 @@ function buildSecurityMetadata({ security }) {
662
682
  return `[${security.map(serializeAuth).join(", ")}]`;
663
683
  }
664
684
  //#endregion
685
+ //#region ../../internals/client/src/builders/paramsRemap.ts
686
+ /**
687
+ * Builds the call-config entries that rename the camelCased `query` and `headers` keys back to the
688
+ * names the OpenAPI document declares, so the wire format follows the spec while the generated
689
+ * types keep camelCase keys. Returns an empty array when no name changes. Path parameters need no
690
+ * remap because the URL template placeholders are renamed in sync with the `path` keys. Emit the
691
+ * entries after the `...config` spread so they override the camelCased groups the caller passes in.
692
+ *
693
+ * @example
694
+ * ```ts
695
+ * // a query param named include_deleted in the spec
696
+ * buildParamsRemap({ node }) // ['query: config.query ? { "include_deleted": config.query.includeDeleted } : config.query']
697
+ * ```
698
+ */
699
+ function buildParamsRemap({ node }) {
700
+ if (!ast.isHttpOperationNode(node)) return [];
701
+ const original = getOperationParameters(node, { paramsCasing: "original" });
702
+ const cased = getOperationParameters(node);
703
+ const queryMapping = buildParamsMapping(original.query, cased.query);
704
+ const headerMapping = buildParamsMapping(original.header, cased.header);
705
+ const entries = [];
706
+ if (queryMapping) entries.push(`query: ${buildParamsRemapExpression({
707
+ source: "config.query",
708
+ mapping: queryMapping
709
+ })}`);
710
+ if (headerMapping) entries.push(`headers: ${buildParamsRemapExpression({
711
+ source: "config.headers",
712
+ mapping: headerMapping
713
+ })}`);
714
+ return entries;
715
+ }
716
+ //#endregion
665
717
  //#region ../../internals/client/src/builders/generics.ts
666
718
  /**
667
719
  * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
@@ -672,7 +724,7 @@ function buildSecurityMetadata({ security }) {
672
724
  * `buildRequestResultGenerics({ node, tsResolver }) // 'AddPetResponses, ThrowOnError'`
673
725
  */
674
726
  function buildRequestResultGenerics({ node, tsResolver }) {
675
- return `${tsResolver.resolveResponsesName(node)}, ThrowOnError`;
727
+ return `${tsResolver.response.responses(node)}, ThrowOnError`;
676
728
  }
677
729
  //#endregion
678
730
  //#region ../../internals/client/src/builders/returnStatement.ts
@@ -695,41 +747,42 @@ function buildReturnStatement({ node, tsResolver, callConfig }) {
695
747
  const declarationPrinter = functionPrinter({ mode: "declaration" });
696
748
  /**
697
749
  * Builds the grouped-options signature for one operation: a single `options` object whose `TData`
698
- * is the plugin-ts `<Name>RequestConfig` (carrying a literal `url`), and a `RequestResult` return type
750
+ * is the plugin-ts `<Name>Options` (carrying a literal `url`), and a `RequestResult` return type
699
751
  * keyed to the plugin-ts per-status responses record. There are no positional arguments.
700
752
  *
701
- * The generated file imports `<Name>RequestConfig` and `<Name>Responses` and uses them directly, so no
753
+ * The generated file imports `<Name>Options` and `<Name>Responses` and uses them directly, so no
702
754
  * per-operation input type has to be emitted.
703
755
  */
704
756
  function buildGroupedOptionsSignature({ node, tsResolver }) {
705
- const requestConfigName = tsResolver.resolveRequestConfigName(node);
706
- const responsesName = tsResolver.resolveResponsesName(node);
757
+ const optionsName = tsResolver.response.options(node);
758
+ const responsesName = tsResolver.response.responses(node);
707
759
  const resultGenerics = buildRequestResultGenerics({
708
760
  node,
709
761
  tsResolver
710
762
  });
711
763
  const { isOptional } = getRequestGroupOptionality(node);
712
764
  return {
713
- dataTypeName: requestConfigName,
765
+ dataTypeName: optionsName,
714
766
  paramsSignature: declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
715
767
  name: "options",
716
- type: `Options<${requestConfigName}, ThrowOnError>`,
768
+ type: `Options<${optionsName}, ThrowOnError>`,
717
769
  ...isOptional ? { default: "{}" } : {}
718
770
  })] })) ?? "",
719
771
  returnType: `Promise<RequestResult<${resultGenerics}>>`,
720
772
  generics: ["ThrowOnError extends boolean = true"],
721
- importedTypeNames: [requestConfigName, responsesName]
773
+ importedTypeNames: [optionsName, responsesName]
722
774
  };
723
775
  }
724
776
  //#endregion
725
777
  //#region ../../internals/client/src/builders/styles.ts
726
778
  /**
727
- * Renders a parameter name as an object-literal key, quoting it when the camelCased name is not a
728
- * bare identifier (for example a name that starts with a digit) so the emitted literal stays valid.
779
+ * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
780
+ * Path keys are camelCased to match the URL template placeholders. Query, header, and cookie keys
781
+ * keep the spec name, matching the remapped keys the runtime serializes.
729
782
  */
730
- function toKey(name) {
731
- const cased = camelCase(name);
732
- return isValidVarName(cased) ? cased : JSON.stringify(cased);
783
+ function toKey(name, location) {
784
+ const key = location === "path" ? camelCase(name) : name;
785
+ return isValidVarName(key) ? key : JSON.stringify(key);
733
786
  }
734
787
  /**
735
788
  * Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
@@ -743,8 +796,9 @@ function serializeParameter(parameter) {
743
796
  return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
744
797
  }
745
798
  /**
746
- * Builds the per-operation `styles` literal from the operation's parameters, grouped by location and
747
- * keyed by the camelCased parameter name to match the generated `path` / `query` / `headers` keys.
799
+ * Builds the per-operation `styles` literal from the operation's parameters, grouped by location.
800
+ * Path entries are keyed by the camelCased name to match the URL template placeholders; query,
801
+ * header, and cookie entries keep the spec name to match the keys the runtime serializes.
748
802
  * Only parameters whose source defines `style` or `explode` are emitted, so calls without
749
803
  * serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
750
804
  * when no parameter carries metadata.
@@ -766,7 +820,7 @@ function buildStyles({ node }) {
766
820
  for (const parameter of node.parameters) {
767
821
  const literal = serializeParameter(parameter);
768
822
  if (!literal) continue;
769
- groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
823
+ groups[parameter.in].push(`${toKey(parameter.name, parameter.in)}: ${literal}`);
770
824
  }
771
825
  const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
772
826
  if (locations.length === 0) return null;
@@ -782,7 +836,7 @@ function buildStyles({ node }) {
782
836
  function buildValidatorHooks({ node, validator, zodResolver }) {
783
837
  const importedZodNames = [];
784
838
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
785
- const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
839
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
786
840
  const request = zodRequestName ?? null;
787
841
  if (zodRequestName) importedZodNames.push(zodRequestName);
788
842
  const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
@@ -839,9 +893,10 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
839
893
  validatorLiteral,
840
894
  contentTypeLiteral,
841
895
  responseTypeLiteral,
842
- "...config"
896
+ "...config",
897
+ ...buildParamsRemap({ node })
843
898
  ].filter(Boolean).join(", ")} }`;
844
- const eventType = `SuccessOf<${tsResolver.resolveResponsesName(node)}>`;
899
+ const eventType = `SuccessOf<${tsResolver.response.responses(node)}>`;
845
900
  const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
846
901
  const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
847
902
  node,
@@ -892,7 +947,8 @@ function buildCallConfig({ node, validator, zodResolver, security }) {
892
947
  `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
893
948
  securityLiteral ? `security: ${securityLiteral}` : null,
894
949
  validatorLiteral,
895
- "...config"
950
+ "...config",
951
+ ...buildParamsRemap({ node })
896
952
  ].filter(Boolean).join(", ")} }`;
897
953
  }
898
954
  /**
@@ -918,7 +974,7 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, securi
918
974
  })
919
975
  });
920
976
  const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
921
- const jsdoc = ast.buildJSDoc(buildOperationComments(node, {
977
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
922
978
  link: "urlPath",
923
979
  linkPosition: "beforeDeprecated",
924
980
  splitLines: true
@@ -988,15 +1044,15 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
988
1044
  //#endregion
989
1045
  //#region ../../internals/client/src/generators/sdkGenerator.tsx
990
1046
  function resolveTypeImportNames(node, tsResolver) {
991
- return [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1047
+ return [tsResolver.response.options(node), tsResolver.response.responses(node)];
992
1048
  }
993
1049
  function resolveZodImportNames(node, zodResolver, validator) {
994
1050
  const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
995
1051
  return [
996
- resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1052
+ resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null,
997
1053
  resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
998
- resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
999
- resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
1054
+ resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.response.body(node) : null,
1055
+ resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.param.query(node, queryParams[0]) : null
1000
1056
  ].filter((n) => Boolean(n));
1001
1057
  }
1002
1058
  /**
@@ -1013,12 +1069,14 @@ function buildControllers(nodes, ctx) {
1013
1069
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1014
1070
  const document = ctx.adapter.document;
1015
1071
  function buildOperationData(node) {
1016
- const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1072
+ const typeFile = tsResolver.file({
1073
+ ...operationFileEntry(node, node.operationId),
1017
1074
  root,
1018
1075
  output: tsPluginOptions?.output ?? output,
1019
1076
  group: tsPluginOptions?.group
1020
1077
  });
1021
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1078
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.file({
1079
+ ...operationFileEntry(node, node.operationId),
1022
1080
  root,
1023
1081
  output: pluginZod.options?.output ?? output,
1024
1082
  group: pluginZod.options?.group ?? void 0
@@ -1030,7 +1088,7 @@ function buildControllers(nodes, ctx) {
1030
1088
  }) : void 0;
1031
1089
  return {
1032
1090
  node,
1033
- name: resolver.resolveName(node.operationId),
1091
+ name: resolver.name(node.operationId),
1034
1092
  tsResolver,
1035
1093
  zodResolver,
1036
1094
  typeFile,
@@ -1041,12 +1099,11 @@ function buildControllers(nodes, ctx) {
1041
1099
  return nodes.reduce((acc, operationNode) => {
1042
1100
  if (!ast.isHttpOperationNode(operationNode)) return acc;
1043
1101
  const tag = operationNode.tags[0];
1044
- const name = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveClassName("ApiClient");
1045
- const file = resolver.resolveFile({
1102
+ const name = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.groupName(tag) : resolver.className("ApiClient");
1103
+ const file = resolver.file({
1046
1104
  name,
1047
1105
  extname: ".ts",
1048
- tag
1049
- }, {
1106
+ tag,
1050
1107
  root,
1051
1108
  output,
1052
1109
  group: group ?? void 0
@@ -1100,7 +1157,7 @@ function createSdkGenerator() {
1100
1157
  if (!ctx.driver.getPlugin(pluginTsName) || !sdk) return null;
1101
1158
  const controllers = buildControllers(nodes, ctx);
1102
1159
  const clientPath = path.resolve(root, ".kubb/client.ts");
1103
- const banner = (file) => resolver.resolveBanner(ctx.meta, {
1160
+ const banner = (file) => resolver.default.banner(ctx.meta, {
1104
1161
  output,
1105
1162
  config,
1106
1163
  file: {
@@ -1108,7 +1165,7 @@ function createSdkGenerator() {
1108
1165
  baseName: file.baseName
1109
1166
  }
1110
1167
  });
1111
- const footer = (file) => resolver.resolveFooter(ctx.meta, {
1168
+ const footer = (file) => resolver.default.footer(ctx.meta, {
1112
1169
  output,
1113
1170
  config,
1114
1171
  file: {
@@ -1175,28 +1232,26 @@ function createSdkGenerator() {
1175
1232
  ]
1176
1233
  }, file.path);
1177
1234
  };
1178
- if (sdk.mode === "flat") return renderClassFile(resolver.resolveClassName(sdk.name ?? "sdk"), resolver.resolveFile({
1235
+ if (sdk.mode === "flat") return renderClassFile(resolver.className(sdk.name ?? "sdk"), resolver.file({
1179
1236
  name: sdk.name ?? "sdk",
1180
- extname: ".ts"
1181
- }, {
1237
+ extname: ".ts",
1182
1238
  root,
1183
1239
  output,
1184
1240
  group: group ?? void 0
1185
1241
  }), controllers.flatMap((controller) => controller.operations));
1186
1242
  const classFiles = controllers.map(({ name, file, operations: ops }) => renderClassFile(name, file, ops));
1187
1243
  if (!sdk.name) return /* @__PURE__ */ jsx(Fragment, { children: classFiles });
1188
- const sdkFile = resolver.resolveFile({
1244
+ const sdkFile = resolver.file({
1189
1245
  name: sdk.name,
1190
- extname: ".ts"
1191
- }, {
1246
+ extname: ".ts",
1192
1247
  root,
1193
1248
  output,
1194
1249
  group: group ?? void 0
1195
1250
  });
1196
- const facadeName = resolver.resolveClassName(sdk.name);
1251
+ const facadeName = resolver.className(sdk.name);
1197
1252
  const members = controllers.map(({ name, tag }) => ({
1198
1253
  className: name,
1199
- propName: resolver.resolveClientPropertyName(tag ?? name)
1254
+ propName: resolver.propertyName(tag ?? name)
1200
1255
  }));
1201
1256
  return /* @__PURE__ */ jsxs(Fragment, { children: [classFiles, /* @__PURE__ */ jsxs(File, {
1202
1257
  baseName: sdkFile.baseName,
@@ -1236,38 +1291,27 @@ const defaultMacros = [ast.macroSimplifyUnion];
1236
1291
  //#endregion
1237
1292
  //#region ../../internals/client/src/resolver.ts
1238
1293
  /**
1239
- * Default resolver shared by the client plugins. Functions and files use camelCase; classes and
1240
- * tag groups use PascalCase.
1294
+ * Default resolver shared by the client plugins. Functions and files inherit the built-in camelCase
1295
+ * `name` and `file`; classes and tag groups use PascalCase.
1241
1296
  *
1242
1297
  * @example
1243
1298
  * ```ts
1244
- * resolverClient.resolveName('show pet by id') // 'showPetById'
1245
- * resolverClient.resolveGroupName('pet') // 'PetClient'
1299
+ * resolverClient.name('show pet by id') // 'showPetById'
1300
+ * resolverClient.groupName('pet') // 'PetClient'
1246
1301
  * ```
1247
1302
  */
1248
- const resolverClient = defineResolver(() => ({
1249
- name: "default",
1303
+ const resolverClient = createResolver({
1250
1304
  pluginName: "plugin-contract-client",
1251
- default(name, type) {
1252
- if (type === "file") return toFilePath(name);
1253
- return ensureValidVarName(camelCase(name));
1254
- },
1255
- resolveName(name) {
1256
- return this.default(name, "function");
1257
- },
1258
- resolvePathName(name, type) {
1259
- return this.default(name, type);
1260
- },
1261
- resolveClassName(name) {
1305
+ className(name) {
1262
1306
  return ensureValidVarName(pascalCase(name));
1263
1307
  },
1264
- resolveGroupName(name) {
1308
+ groupName(name) {
1265
1309
  return ensureValidVarName(pascalCase(`${name} Client`));
1266
1310
  },
1267
- resolveClientPropertyName(name) {
1311
+ propertyName(name) {
1268
1312
  return ensureValidVarName(camelCase(name));
1269
1313
  }
1270
- }));
1314
+ });
1271
1315
  //#endregion
1272
1316
  //#region src/generators/clientGenerator.tsx
1273
1317
  /**
@@ -1288,25 +1332,28 @@ const clientGenerator = defineGenerator({
1288
1332
  const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(pluginZodName) : null;
1289
1333
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1290
1334
  const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1291
- const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
1335
+ const importedTypeNames = [tsResolver.response.options(node), tsResolver.response.responses(node)];
1292
1336
  const importedZodNames = zodResolver ? [
1293
- resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1337
+ resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
1294
1338
  resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1295
- resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
1339
+ resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
1296
1340
  ].filter((name) => Boolean(name)) : [];
1297
1341
  const meta = {
1298
- name: resolver.resolveName(node.operationId),
1299
- file: resolver.resolveFile(operationFileEntry(node, node.operationId), {
1342
+ name: resolver.name(node.operationId),
1343
+ file: resolver.file({
1344
+ ...operationFileEntry(node, node.operationId),
1300
1345
  root,
1301
1346
  output,
1302
1347
  group: group ?? void 0
1303
1348
  }),
1304
- fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1349
+ fileTs: tsResolver.file({
1350
+ ...operationFileEntry(node, node.operationId),
1305
1351
  root,
1306
1352
  output: pluginTs.options?.output ?? output,
1307
1353
  group: pluginTs.options?.group ?? void 0
1308
1354
  }),
1309
- fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1355
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.file({
1356
+ ...operationFileEntry(node, node.operationId),
1310
1357
  root,
1311
1358
  output: pluginZod.options.output ?? output,
1312
1359
  group: pluginZod.options?.group ?? void 0
@@ -1323,7 +1370,7 @@ const clientGenerator = defineGenerator({
1323
1370
  baseName: meta.file.baseName,
1324
1371
  path: meta.file.path,
1325
1372
  meta: meta.file.meta,
1326
- banner: resolver.resolveBanner(ctx.meta, {
1373
+ banner: resolver.default.banner(ctx.meta, {
1327
1374
  output,
1328
1375
  config,
1329
1376
  file: {
@@ -1331,7 +1378,7 @@ const clientGenerator = defineGenerator({
1331
1378
  baseName: meta.file.baseName
1332
1379
  }
1333
1380
  }),
1334
- footer: resolver.resolveFooter(ctx.meta, {
1381
+ footer: resolver.default.footer(ctx.meta, {
1335
1382
  output,
1336
1383
  config,
1337
1384
  file: {
@@ -1435,10 +1482,7 @@ const pluginFetch = definePlugin((options) => {
1435
1482
  mode: sdk.mode ?? "tag",
1436
1483
  name: sdk.name
1437
1484
  } : void 0,
1438
- resolver: userResolver ? {
1439
- ...resolverClient,
1440
- ...userResolver
1441
- } : resolverClient
1485
+ resolver: userResolver ? Resolver.merge(resolverClient, userResolver) : resolverClient
1442
1486
  };
1443
1487
  const selectedGenerators = resolved.sdk ? [createSdkGenerator()] : [clientGenerator];
1444
1488
  return {