@kubb/plugin-fetch 5.0.0-beta.95 → 5.0.0-beta.99

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
@@ -27,8 +27,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  let node_path = require("node:path");
28
28
  node_path = __toESM(node_path, 1);
29
29
  let kubb_kit = require("kubb/kit");
30
- let kubb_jsx = require("kubb/jsx");
31
30
  let _kubb_plugin_ts = require("@kubb/plugin-ts");
31
+ let kubb_jsx = require("kubb/jsx");
32
32
  let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
33
33
  let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
34
  let node_url = require("node:url");
@@ -228,33 +228,17 @@ function buildJSDoc(comments, options = {}) {
228
228
  }
229
229
  //#endregion
230
230
  //#region ../../internals/utils/src/url.ts
231
- function transformParam(raw, casing) {
232
- const param = isValidVarName(raw) ? raw : camelCase(raw);
233
- return casing === "camelcase" ? camelCase(param) : param;
234
- }
235
- function toParamsObject(path, { replacer, casing } = {}) {
236
- const params = {};
237
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
238
- const param = transformParam(match[1], casing);
239
- const key = replacer ? replacer(param) : param;
240
- params[key] = key;
241
- }
242
- return Object.keys(params).length > 0 ? params : null;
231
+ /**
232
+ * Keeps the OpenAPI parameter name as-is when it is already a valid JS identifier, and
233
+ * camelCases it only enough to become one otherwise (for example a hyphenated path segment).
234
+ */
235
+ function transformParam(raw) {
236
+ return isValidVarName(raw) ? raw : camelCase(raw);
243
237
  }
244
238
  /**
245
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
239
+ * Helpers for OpenAPI/Swagger paths.
246
240
  */
247
241
  var Url = class Url {
248
- /**
249
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
250
- *
251
- * @example
252
- * Url.canParse('https://petstore.swagger.io/v2') // true
253
- * Url.canParse('/pet/{petId}') // false
254
- */
255
- static canParse(url, base) {
256
- return URL.canParse(url, base);
257
- }
258
242
  /**
259
243
  * Converts an OpenAPI/Swagger path to Express-style colon syntax.
260
244
  *
@@ -270,15 +254,14 @@ var Url = class Url {
270
254
  * key.
271
255
  *
272
256
  * @example
273
- * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
257
+ * Url.toSafeTemplate('/user/{monetary-account-id}') // '/user/{monetaryAccountId}'
274
258
  */
275
- static toCasedTemplate(path, { casing } = {}) {
276
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
259
+ static toSafeTemplate(path) {
260
+ return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name)}}`);
277
261
  }
278
262
  /**
279
263
  * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
280
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
281
- * and `casing` controls parameter identifier casing.
264
+ * `prefix` is prepended inside the literal, and `replacer` transforms each parameter name.
282
265
  *
283
266
  * @example
284
267
  * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
@@ -286,10 +269,10 @@ var Url = class Url {
286
269
  * @example
287
270
  * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
288
271
  */
289
- static toTemplateString(path, { prefix, replacer, casing } = {}) {
272
+ static toTemplateString(path, { prefix, replacer } = {}) {
290
273
  const result = path.split(/\{([^}]+)\}/).map((part, i) => {
291
274
  if (i % 2 === 0) return part;
292
- const param = transformParam(part, casing);
275
+ const param = transformParam(part);
293
276
  return `\${${replacer ? replacer(param) : param}}`;
294
277
  }).join("");
295
278
  return `\`${prefix ?? ""}${result}\``;
@@ -297,8 +280,8 @@ var Url = class Url {
297
280
  /**
298
281
  * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
299
282
  * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
300
- * names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
301
- * literal. Shared by the client and cypress generators that pass a grouped `path` object.
283
+ * names match the generated `path` type, and `prefix` is prepended inside the literal. Shared by
284
+ * the client and cypress generators that pass a grouped `path` object.
302
285
  *
303
286
  * @example
304
287
  * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
@@ -306,103 +289,28 @@ var Url = class Url {
306
289
  static toGroupedTemplateString(path, { prefix } = {}) {
307
290
  return Url.toTemplateString(path, {
308
291
  prefix,
309
- casing: "camelcase",
310
292
  replacer: (name) => `path.${name}`
311
293
  });
312
294
  }
313
- /**
314
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
315
- * expression when `stringify` is set.
316
- *
317
- * @example
318
- * Url.toObject('/pet/{petId}')
319
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
320
- */
321
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
322
- const object = {
323
- url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
324
- replacer,
325
- casing
326
- }),
327
- params: toParamsObject(path, {
328
- replacer,
329
- casing
330
- })
331
- };
332
- if (stringify) {
333
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
334
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
335
- return `{ url: '${object.url}' }`;
336
- }
337
- return object;
338
- }
339
295
  };
340
296
  //#endregion
341
297
  //#region ../../internals/shared/src/params.ts
342
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
343
- /**
344
- * Applies camelCase to parameter names and returns a new array without mutating the input.
345
- *
346
- * Run it before handing parameters to schema builders so output property keys get the right casing
347
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
348
- * original array is returned unchanged. Results are cached per input array.
349
- */
350
- function caseParams(params, casing) {
351
- if (!casing) return params;
352
- const cached = caseParamsCache.get(params);
353
- if (cached) return cached;
354
- const result = params.map((param) => ({
355
- ...param,
356
- name: camelCase(param.name)
357
- }));
358
- caseParamsCache.set(params, result);
359
- return result;
360
- }
361
298
  /**
362
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
299
+ * Drops parameters that share the same name, keeping the first.
363
300
  *
364
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
365
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
366
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
367
- * identity so the resulting group is collision-free regardless of the names each caller carries.
301
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
302
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
303
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
304
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
368
305
  */
369
- function dedupeByCasedName(params) {
306
+ function dedupeParams(params) {
370
307
  const seen = /* @__PURE__ */ new Set();
371
308
  return params.filter((param) => {
372
- const key = camelCase(param.name);
373
- if (seen.has(key)) return false;
374
- seen.add(key);
309
+ if (seen.has(param.name)) return false;
310
+ seen.add(param.name);
375
311
  return true;
376
312
  });
377
313
  }
378
- function buildParamsMapping(originalParams, mappedParams) {
379
- const mapping = {};
380
- let hasChanged = false;
381
- originalParams.forEach((param, i) => {
382
- const mappedName = mappedParams[i]?.name ?? param.name;
383
- mapping[param.name] = mappedName;
384
- if (param.name !== mappedName) hasChanged = true;
385
- });
386
- return hasChanged ? mapping : null;
387
- }
388
- function toAccess(object, name) {
389
- return isValidVarName(name) ? `${object}.${name}` : `${object}[${JSON.stringify(name)}]`;
390
- }
391
- /**
392
- * Renders the object-literal expression that renames the camelCased keys of a grouped request
393
- * option back to the names the OpenAPI document declares, guarded so an omitted optional group
394
- * stays omitted. Shared by the client and cypress generators, which pass a `buildParamsMapping`
395
- * result and the source expression to read the keys from.
396
- *
397
- * @example
398
- * ```ts
399
- * buildParamsRemapExpression({ source: 'config.query', mapping: { include_deleted: 'includeDeleted' } })
400
- * // 'config.query ? { "include_deleted": config.query.includeDeleted } : config.query'
401
- * ```
402
- */
403
- function buildParamsRemapExpression({ source, mapping }) {
404
- return `${source} ? { ${Object.entries(mapping).map(([originalName, casedName]) => `${JSON.stringify(originalName)}: ${toAccess(source, casedName)}`).join(", ")} } : ${source}`;
405
- }
406
314
  //#endregion
407
315
  //#region ../../internals/shared/src/operation.ts
408
316
  /**
@@ -536,13 +444,15 @@ function buildOperationComments(node, options = {}) {
536
444
  if (!splitLines) return filteredComments;
537
445
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
538
446
  }
539
- function getOperationParameters(node, options = {}) {
540
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
447
+ function getOperationParameters(node) {
541
448
  return {
542
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
543
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
544
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
545
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
449
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
450
+ ...param,
451
+ name: camelCase(param.name)
452
+ })),
453
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
454
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
455
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
546
456
  };
547
457
  }
548
458
  function getStatusCodeNumber(statusCode) {
@@ -594,64 +504,33 @@ function createGroupConfig(group) {
594
504
  };
595
505
  }
596
506
  //#endregion
597
- //#region ../../internals/client/src/builders/validatorOptions.ts
598
- /**
599
- * Returns `true` when any direction of the validator uses zod (used for dependency checks).
600
- */
601
- function isValidatorEnabled(validator) {
602
- if (!validator) return false;
603
- if (validator === "zod") return true;
604
- return Boolean(validator.request || validator.response);
605
- }
606
- /**
607
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
608
- * `'zod'` validates the response only, so it does not enable request parsing.
609
- */
610
- function resolveRequestValidator(validator) {
611
- if (!validator || validator === "zod") return null;
612
- return validator.request ?? null;
613
- }
614
- /**
615
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
616
- * `{ request: 'zod' }` enables it.
617
- */
618
- function resolveQueryParamsValidator(validator) {
619
- if (!validator || validator === "zod") return null;
620
- return validator.request ?? null;
621
- }
622
- /**
623
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
624
- * maps to response parsing.
625
- */
626
- function resolveResponseValidator(validator) {
627
- if (!validator) return null;
628
- if (validator === "zod") return "zod";
629
- return validator.response ?? null;
630
- }
507
+ //#region ../../internals/client/src/builders/generics.ts
631
508
  /**
632
- * Resolves the zod expression a generated client validates a success response with. Only success
633
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
634
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
509
+ * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
510
+ * record plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
511
+ * runtime, so this only names the record and threads `ThrowOnError`.
512
+ *
513
+ * @example
514
+ * `buildRequestResultGenerics({ node, tsResolver }) // 'AddPetResponses, ThrowOnError'`
635
515
  */
636
- function buildZodResponseParse(node, zodResolver) {
637
- const name = zodResolver.response.response(node);
638
- return name ? {
639
- expression: name,
640
- importNames: [name]
641
- } : null;
516
+ function buildRequestResultGenerics({ node, tsResolver }) {
517
+ return `${tsResolver.response.responses(node)}, ThrowOnError`;
642
518
  }
519
+ //#endregion
520
+ //#region ../../internals/client/src/builders/returnStatement.ts
643
521
  /**
644
- * Resolves the zod expression a generated client validates an error body with on the non-throw path.
645
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
646
- * operation documents no error responses with a schema.
522
+ * Builds the return statement of a generated operation function. The runtime call already resolves
523
+ * to `{ data, error, request, response }`; the generated code forwards that result and casts it to
524
+ * the operation's `RequestResult`, which carries the `throwOnError` discrimination.
525
+ *
526
+ * @example
527
+ * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
647
528
  */
648
- function buildZodErrorParse(node, zodResolver) {
649
- if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
650
- const name = zodResolver.response.error?.(node);
651
- return name ? {
652
- expression: name,
653
- importNames: [name]
654
- } : null;
529
+ function buildReturnStatement({ node, tsResolver, callConfig }) {
530
+ return `return request(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
531
+ node,
532
+ tsResolver
533
+ })}>>`;
655
534
  }
656
535
  //#endregion
657
536
  //#region ../../internals/client/src/builders/security.ts
@@ -722,67 +601,6 @@ function buildSecurityMetadata({ security }) {
722
601
  return `[${security.map(serializeAuth).join(", ")}]`;
723
602
  }
724
603
  //#endregion
725
- //#region ../../internals/client/src/builders/paramsRemap.ts
726
- /**
727
- * Builds the call-config entries that rename the camelCased `query` and `headers` keys back to the
728
- * names the OpenAPI document declares, so the wire format follows the spec while the generated
729
- * types keep camelCase keys. Returns an empty array when no name changes. Path parameters need no
730
- * remap because the URL template placeholders are renamed in sync with the `path` keys. Emit the
731
- * entries after the `...config` spread so they override the camelCased groups the caller passes in.
732
- *
733
- * @example
734
- * ```ts
735
- * // a query param named include_deleted in the spec
736
- * buildParamsRemap({ node }) // ['query: config.query ? { "include_deleted": config.query.includeDeleted } : config.query']
737
- * ```
738
- */
739
- function buildParamsRemap({ node }) {
740
- if (!kubb_kit.ast.isHttpOperationNode(node)) return [];
741
- const original = getOperationParameters(node, { paramsCasing: "original" });
742
- const cased = getOperationParameters(node);
743
- const queryMapping = buildParamsMapping(original.query, cased.query);
744
- const headerMapping = buildParamsMapping(original.header, cased.header);
745
- const entries = [];
746
- if (queryMapping) entries.push(`query: ${buildParamsRemapExpression({
747
- source: "config.query",
748
- mapping: queryMapping
749
- })}`);
750
- if (headerMapping) entries.push(`headers: ${buildParamsRemapExpression({
751
- source: "config.headers",
752
- mapping: headerMapping
753
- })}`);
754
- return entries;
755
- }
756
- //#endregion
757
- //#region ../../internals/client/src/builders/generics.ts
758
- /**
759
- * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
760
- * record plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
761
- * runtime, so this only names the record and threads `ThrowOnError`.
762
- *
763
- * @example
764
- * `buildRequestResultGenerics({ node, tsResolver }) // 'AddPetResponses, ThrowOnError'`
765
- */
766
- function buildRequestResultGenerics({ node, tsResolver }) {
767
- return `${tsResolver.response.responses(node)}, ThrowOnError`;
768
- }
769
- //#endregion
770
- //#region ../../internals/client/src/builders/returnStatement.ts
771
- /**
772
- * Builds the return statement of a generated operation function. The runtime call already resolves
773
- * to `{ data, error, request, response }`; the generated code forwards that result and casts it to
774
- * the operation's `RequestResult`, which carries the `throwOnError` discrimination.
775
- *
776
- * @example
777
- * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
778
- */
779
- function buildReturnStatement({ node, tsResolver, callConfig }) {
780
- return `return request(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
781
- node,
782
- tsResolver
783
- })}>>`;
784
- }
785
- //#endregion
786
604
  //#region ../../internals/client/src/builders/signature.ts
787
605
  const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
788
606
  /**
@@ -814,6 +632,151 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
814
632
  };
815
633
  }
816
634
  //#endregion
635
+ //#region ../../internals/client/src/builders/validatorOptions.ts
636
+ /**
637
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
638
+ */
639
+ function isValidatorEnabled(validator) {
640
+ if (!validator) return false;
641
+ if (validator === "zod") return true;
642
+ return Boolean(validator.request || validator.response);
643
+ }
644
+ /**
645
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
646
+ * `'zod'` validates the response only, so it does not enable request parsing.
647
+ */
648
+ function resolveRequestValidator(validator) {
649
+ if (!validator || validator === "zod") return null;
650
+ return validator.request ?? null;
651
+ }
652
+ /**
653
+ * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
654
+ * `{ request: 'zod' }` enables it.
655
+ */
656
+ function resolveQueryParamsValidator(validator) {
657
+ if (!validator || validator === "zod") return null;
658
+ return validator.request ?? null;
659
+ }
660
+ /**
661
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
662
+ * maps to response parsing.
663
+ */
664
+ function resolveResponseValidator(validator) {
665
+ if (!validator) return null;
666
+ if (validator === "zod") return "zod";
667
+ return validator.response ?? null;
668
+ }
669
+ /**
670
+ * Resolves the zod expression a generated client validates a success response with. Only success
671
+ * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
672
+ * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
673
+ */
674
+ function buildZodResponseParse(node, zodResolver) {
675
+ const name = zodResolver.response.response(node);
676
+ return name ? {
677
+ expression: name,
678
+ importNames: [name]
679
+ } : null;
680
+ }
681
+ /**
682
+ * Resolves the zod expression a generated client validates an error body with on the non-throw path.
683
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
684
+ * operation documents no error responses with a schema.
685
+ */
686
+ function buildZodErrorParse(node, zodResolver) {
687
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
688
+ const name = zodResolver.response.error?.(node);
689
+ return name ? {
690
+ expression: name,
691
+ importNames: [name]
692
+ } : null;
693
+ }
694
+ //#endregion
695
+ //#region ../../internals/client/src/builders/validator.ts
696
+ /**
697
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
698
+ * response validation runs on the success body only. Returns `null` references when the matching
699
+ * direction is disabled or the schema is absent.
700
+ */
701
+ function buildValidatorHooks({ node, validator, zodResolver }) {
702
+ const importedZodNames = [];
703
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
704
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
705
+ const request = zodRequestName ?? null;
706
+ if (zodRequestName) importedZodNames.push(zodRequestName);
707
+ const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
708
+ const response = responseParse ? responseParse.expression : null;
709
+ if (responseParse) importedZodNames.push(...responseParse.importNames);
710
+ const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
711
+ const error = errorParse ? errorParse.expression : null;
712
+ if (errorParse) importedZodNames.push(...errorParse.importNames);
713
+ return {
714
+ request,
715
+ response,
716
+ error,
717
+ importedZodNames
718
+ };
719
+ }
720
+ //#endregion
721
+ //#region ../../internals/client/src/builders/sdkMethod.ts
722
+ /**
723
+ * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
724
+ * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
725
+ * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
726
+ */
727
+ function buildCallConfig({ node, validator, zodResolver, security }) {
728
+ const validators = buildValidatorHooks({
729
+ node,
730
+ validator,
731
+ zodResolver
732
+ });
733
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
734
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
735
+ const securityLiteral = buildSecurityMetadata({ security });
736
+ return `{ ${[
737
+ `method: '${node.method.toUpperCase()}'`,
738
+ `url: '${Url.toSafeTemplate(node.path)}'`,
739
+ securityLiteral ? `security: ${securityLiteral}` : null,
740
+ validatorLiteral,
741
+ "...config"
742
+ ].filter(Boolean).join(", ")} }`;
743
+ }
744
+ /**
745
+ * Builds a single instance method for a generated SDK class. The body forwards the single grouped
746
+ * `options` object to the instance's own client (`this.client`, built once in the constructor) and
747
+ * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
748
+ * one operation can be routed to a different environment without a new instance.
749
+ */
750
+ function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
751
+ if (!kubb_kit.ast.isHttpOperationNode(node)) return "";
752
+ const signature = buildGroupedOptionsSignature({
753
+ node,
754
+ tsResolver
755
+ });
756
+ const returnStatement = buildReturnStatement({
757
+ node,
758
+ tsResolver,
759
+ callConfig: buildCallConfig({
760
+ node,
761
+ validator,
762
+ zodResolver,
763
+ security
764
+ })
765
+ });
766
+ const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
767
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
768
+ link: "urlPath",
769
+ linkPosition: "beforeDeprecated",
770
+ splitLines: true
771
+ }));
772
+ const methodBody = [
773
+ "const { client: request = this.client, ...config } = options",
774
+ "",
775
+ returnStatement
776
+ ].map((line) => line ? ` ${line}` : "").join("\n");
777
+ return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
778
+ }
779
+ //#endregion
817
780
  //#region ../../internals/client/src/builders/styles.ts
818
781
  /**
819
782
  * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
@@ -867,32 +830,6 @@ function buildStyles({ node }) {
867
830
  return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
868
831
  }
869
832
  //#endregion
870
- //#region ../../internals/client/src/builders/validator.ts
871
- /**
872
- * Builds the validator-hook references for one operation. Request validation runs before the send;
873
- * response validation runs on the success body only. Returns `null` references when the matching
874
- * direction is disabled or the schema is absent.
875
- */
876
- function buildValidatorHooks({ node, validator, zodResolver }) {
877
- const importedZodNames = [];
878
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
879
- const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
880
- const request = zodRequestName ?? null;
881
- if (zodRequestName) importedZodNames.push(zodRequestName);
882
- const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
883
- const response = responseParse ? responseParse.expression : null;
884
- if (responseParse) importedZodNames.push(...responseParse.importNames);
885
- const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
886
- const error = errorParse ? errorParse.expression : null;
887
- if (errorParse) importedZodNames.push(...errorParse.importNames);
888
- return {
889
- request,
890
- response,
891
- error,
892
- importedZodNames
893
- };
894
- }
895
- //#endregion
896
833
  //#region ../../internals/client/src/components/Operation.tsx
897
834
  /**
898
835
  * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
@@ -927,14 +864,13 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
927
864
  const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
928
865
  const callConfig = `{ ${[
929
866
  `method: '${node.method.toUpperCase()}'`,
930
- `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
867
+ `url: '${Url.toSafeTemplate(node.path)}'`,
931
868
  securityLiteral ? `security: ${securityLiteral}` : null,
932
869
  stylesLiteral ? `styles: ${stylesLiteral}` : null,
933
870
  validatorLiteral,
934
871
  contentTypeLiteral,
935
872
  responseTypeLiteral,
936
- "...config",
937
- ...buildParamsRemap({ node })
873
+ "...config"
938
874
  ].filter(Boolean).join(", ")} }`;
939
875
  const eventType = `SuccessOf<${tsResolver.response.responses(node)}>`;
940
876
  const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
@@ -967,66 +903,6 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
967
903
  });
968
904
  }
969
905
  //#endregion
970
- //#region ../../internals/client/src/builders/sdkMethod.ts
971
- /**
972
- * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
973
- * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
974
- * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
975
- */
976
- function buildCallConfig({ node, validator, zodResolver, security }) {
977
- const validators = buildValidatorHooks({
978
- node,
979
- validator,
980
- zodResolver
981
- });
982
- const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
983
- const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
984
- const securityLiteral = buildSecurityMetadata({ security });
985
- return `{ ${[
986
- `method: '${node.method.toUpperCase()}'`,
987
- `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
988
- securityLiteral ? `security: ${securityLiteral}` : null,
989
- validatorLiteral,
990
- "...config",
991
- ...buildParamsRemap({ node })
992
- ].filter(Boolean).join(", ")} }`;
993
- }
994
- /**
995
- * Builds a single instance method for a generated SDK class. The body forwards the single grouped
996
- * `options` object to the instance's own client (`this.client`, built once in the constructor) and
997
- * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
998
- * one operation can be routed to a different environment without a new instance.
999
- */
1000
- function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
1001
- if (!kubb_kit.ast.isHttpOperationNode(node)) return "";
1002
- const signature = buildGroupedOptionsSignature({
1003
- node,
1004
- tsResolver
1005
- });
1006
- const returnStatement = buildReturnStatement({
1007
- node,
1008
- tsResolver,
1009
- callConfig: buildCallConfig({
1010
- node,
1011
- validator,
1012
- zodResolver,
1013
- security
1014
- })
1015
- });
1016
- const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
1017
- const jsdoc = buildJSDoc(buildOperationComments(node, {
1018
- link: "urlPath",
1019
- linkPosition: "beforeDeprecated",
1020
- splitLines: true
1021
- }));
1022
- const methodBody = [
1023
- "const { client: request = this.client, ...config } = options",
1024
- "",
1025
- returnStatement
1026
- ].map((line) => line ? ` ${line}` : "").join("\n");
1027
- return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
1028
- }
1029
- //#endregion
1030
906
  //#region ../../internals/client/src/components/SdkClient.tsx
1031
907
  /**
1032
908
  * Renders one instance class per tag with one method per operation. The constructor takes a client
@@ -1087,7 +963,7 @@ function resolveTypeImportNames(node, tsResolver) {
1087
963
  return [tsResolver.response.options(node), tsResolver.response.responses(node)];
1088
964
  }
1089
965
  function resolveZodImportNames(node, zodResolver, validator) {
1090
- const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
966
+ const { query: queryParams } = getOperationParameters(node);
1091
967
  return [
1092
968
  resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null,
1093
969
  resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,