@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.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
2
  import { Exclude, Group, Include, Output, OutputOptions, Override, PluginFactoryOptions, Resolver, ResolverPatch, ast } from "kubb/kit";
3
+ import "@kubb/plugin-ts";
4
+ import "kubb/jsx";
5
+ import "@kubb/plugin-zod";
3
6
  //#region ../../internals/client/src/types.d.ts
4
7
  /**
5
8
  * Validator applied to request and response bodies using schemas from `@kubb/plugin-zod`.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
2
  import path from "node:path";
3
3
  import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
- import { File, Function, jsxRenderer } from "kubb/jsx";
5
4
  import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
+ import { File, Function, jsxRenderer } from "kubb/jsx";
6
6
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
7
  import { pluginZodName } from "@kubb/plugin-zod";
8
8
  import { fileURLToPath } from "node:url";
@@ -202,33 +202,17 @@ function buildJSDoc(comments, options = {}) {
202
202
  }
203
203
  //#endregion
204
204
  //#region ../../internals/utils/src/url.ts
205
- function transformParam(raw, casing) {
206
- const param = isValidVarName(raw) ? raw : camelCase(raw);
207
- return casing === "camelcase" ? camelCase(param) : param;
208
- }
209
- function toParamsObject(path, { replacer, casing } = {}) {
210
- const params = {};
211
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
212
- const param = transformParam(match[1], casing);
213
- const key = replacer ? replacer(param) : param;
214
- params[key] = key;
215
- }
216
- return Object.keys(params).length > 0 ? params : null;
205
+ /**
206
+ * Keeps the OpenAPI parameter name as-is when it is already a valid JS identifier, and
207
+ * camelCases it only enough to become one otherwise (for example a hyphenated path segment).
208
+ */
209
+ function transformParam(raw) {
210
+ return isValidVarName(raw) ? raw : camelCase(raw);
217
211
  }
218
212
  /**
219
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
213
+ * Helpers for OpenAPI/Swagger paths.
220
214
  */
221
215
  var Url = class Url {
222
- /**
223
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
224
- *
225
- * @example
226
- * Url.canParse('https://petstore.swagger.io/v2') // true
227
- * Url.canParse('/pet/{petId}') // false
228
- */
229
- static canParse(url, base) {
230
- return URL.canParse(url, base);
231
- }
232
216
  /**
233
217
  * Converts an OpenAPI/Swagger path to Express-style colon syntax.
234
218
  *
@@ -244,15 +228,14 @@ var Url = class Url {
244
228
  * key.
245
229
  *
246
230
  * @example
247
- * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
231
+ * Url.toSafeTemplate('/user/{monetary-account-id}') // '/user/{monetaryAccountId}'
248
232
  */
249
- static toCasedTemplate(path, { casing } = {}) {
250
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
233
+ static toSafeTemplate(path) {
234
+ return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name)}}`);
251
235
  }
252
236
  /**
253
237
  * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
254
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
255
- * and `casing` controls parameter identifier casing.
238
+ * `prefix` is prepended inside the literal, and `replacer` transforms each parameter name.
256
239
  *
257
240
  * @example
258
241
  * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
@@ -260,10 +243,10 @@ var Url = class Url {
260
243
  * @example
261
244
  * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
262
245
  */
263
- static toTemplateString(path, { prefix, replacer, casing } = {}) {
246
+ static toTemplateString(path, { prefix, replacer } = {}) {
264
247
  const result = path.split(/\{([^}]+)\}/).map((part, i) => {
265
248
  if (i % 2 === 0) return part;
266
- const param = transformParam(part, casing);
249
+ const param = transformParam(part);
267
250
  return `\${${replacer ? replacer(param) : param}}`;
268
251
  }).join("");
269
252
  return `\`${prefix ?? ""}${result}\``;
@@ -271,8 +254,8 @@ var Url = class Url {
271
254
  /**
272
255
  * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
273
256
  * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
274
- * names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
275
- * literal. Shared by the client and cypress generators that pass a grouped `path` object.
257
+ * names match the generated `path` type, and `prefix` is prepended inside the literal. Shared by
258
+ * the client and cypress generators that pass a grouped `path` object.
276
259
  *
277
260
  * @example
278
261
  * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
@@ -280,103 +263,28 @@ var Url = class Url {
280
263
  static toGroupedTemplateString(path, { prefix } = {}) {
281
264
  return Url.toTemplateString(path, {
282
265
  prefix,
283
- casing: "camelcase",
284
266
  replacer: (name) => `path.${name}`
285
267
  });
286
268
  }
287
- /**
288
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
289
- * expression when `stringify` is set.
290
- *
291
- * @example
292
- * Url.toObject('/pet/{petId}')
293
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
294
- */
295
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
296
- const object = {
297
- url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
298
- replacer,
299
- casing
300
- }),
301
- params: toParamsObject(path, {
302
- replacer,
303
- casing
304
- })
305
- };
306
- if (stringify) {
307
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
308
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
309
- return `{ url: '${object.url}' }`;
310
- }
311
- return object;
312
- }
313
269
  };
314
270
  //#endregion
315
271
  //#region ../../internals/shared/src/params.ts
316
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
317
- /**
318
- * Applies camelCase to parameter names and returns a new array without mutating the input.
319
- *
320
- * Run it before handing parameters to schema builders so output property keys get the right casing
321
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
322
- * original array is returned unchanged. Results are cached per input array.
323
- */
324
- function caseParams(params, casing) {
325
- if (!casing) return params;
326
- const cached = caseParamsCache.get(params);
327
- if (cached) return cached;
328
- const result = params.map((param) => ({
329
- ...param,
330
- name: camelCase(param.name)
331
- }));
332
- caseParamsCache.set(params, result);
333
- return result;
334
- }
335
272
  /**
336
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
273
+ * Drops parameters that share the same name, keeping the first.
337
274
  *
338
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
339
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
340
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
341
- * identity so the resulting group is collision-free regardless of the names each caller carries.
275
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
276
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
277
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
278
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
342
279
  */
343
- function dedupeByCasedName(params) {
280
+ function dedupeParams(params) {
344
281
  const seen = /* @__PURE__ */ new Set();
345
282
  return params.filter((param) => {
346
- const key = camelCase(param.name);
347
- if (seen.has(key)) return false;
348
- seen.add(key);
283
+ if (seen.has(param.name)) return false;
284
+ seen.add(param.name);
349
285
  return true;
350
286
  });
351
287
  }
352
- function buildParamsMapping(originalParams, mappedParams) {
353
- const mapping = {};
354
- let hasChanged = false;
355
- originalParams.forEach((param, i) => {
356
- const mappedName = mappedParams[i]?.name ?? param.name;
357
- mapping[param.name] = mappedName;
358
- if (param.name !== mappedName) hasChanged = true;
359
- });
360
- return hasChanged ? mapping : null;
361
- }
362
- function toAccess(object, name) {
363
- return isValidVarName(name) ? `${object}.${name}` : `${object}[${JSON.stringify(name)}]`;
364
- }
365
- /**
366
- * Renders the object-literal expression that renames the camelCased keys of a grouped request
367
- * option back to the names the OpenAPI document declares, guarded so an omitted optional group
368
- * stays omitted. Shared by the client and cypress generators, which pass a `buildParamsMapping`
369
- * result and the source expression to read the keys from.
370
- *
371
- * @example
372
- * ```ts
373
- * buildParamsRemapExpression({ source: 'config.query', mapping: { include_deleted: 'includeDeleted' } })
374
- * // 'config.query ? { "include_deleted": config.query.includeDeleted } : config.query'
375
- * ```
376
- */
377
- function buildParamsRemapExpression({ source, mapping }) {
378
- return `${source} ? { ${Object.entries(mapping).map(([originalName, casedName]) => `${JSON.stringify(originalName)}: ${toAccess(source, casedName)}`).join(", ")} } : ${source}`;
379
- }
380
288
  //#endregion
381
289
  //#region ../../internals/shared/src/operation.ts
382
290
  /**
@@ -510,13 +418,15 @@ function buildOperationComments(node, options = {}) {
510
418
  if (!splitLines) return filteredComments;
511
419
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
512
420
  }
513
- function getOperationParameters(node, options = {}) {
514
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
421
+ function getOperationParameters(node) {
515
422
  return {
516
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
517
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
518
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
519
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
423
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
424
+ ...param,
425
+ name: camelCase(param.name)
426
+ })),
427
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
428
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
429
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
520
430
  };
521
431
  }
522
432
  function getStatusCodeNumber(statusCode) {
@@ -568,64 +478,33 @@ function createGroupConfig(group) {
568
478
  };
569
479
  }
570
480
  //#endregion
571
- //#region ../../internals/client/src/builders/validatorOptions.ts
572
- /**
573
- * Returns `true` when any direction of the validator uses zod (used for dependency checks).
574
- */
575
- function isValidatorEnabled(validator) {
576
- if (!validator) return false;
577
- if (validator === "zod") return true;
578
- return Boolean(validator.request || validator.response);
579
- }
580
- /**
581
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
582
- * `'zod'` validates the response only, so it does not enable request parsing.
583
- */
584
- function resolveRequestValidator(validator) {
585
- if (!validator || validator === "zod") return null;
586
- return validator.request ?? null;
587
- }
588
- /**
589
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
590
- * `{ request: 'zod' }` enables it.
591
- */
592
- function resolveQueryParamsValidator(validator) {
593
- if (!validator || validator === "zod") return null;
594
- return validator.request ?? null;
595
- }
596
- /**
597
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
598
- * maps to response parsing.
599
- */
600
- function resolveResponseValidator(validator) {
601
- if (!validator) return null;
602
- if (validator === "zod") return "zod";
603
- return validator.response ?? null;
604
- }
481
+ //#region ../../internals/client/src/builders/generics.ts
605
482
  /**
606
- * Resolves the zod expression a generated client validates a success response with. Only success
607
- * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
608
- * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
483
+ * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
484
+ * record plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
485
+ * runtime, so this only names the record and threads `ThrowOnError`.
486
+ *
487
+ * @example
488
+ * `buildRequestResultGenerics({ node, tsResolver }) // 'AddPetResponses, ThrowOnError'`
609
489
  */
610
- function buildZodResponseParse(node, zodResolver) {
611
- const name = zodResolver.response.response(node);
612
- return name ? {
613
- expression: name,
614
- importNames: [name]
615
- } : null;
490
+ function buildRequestResultGenerics({ node, tsResolver }) {
491
+ return `${tsResolver.response.responses(node)}, ThrowOnError`;
616
492
  }
493
+ //#endregion
494
+ //#region ../../internals/client/src/builders/returnStatement.ts
617
495
  /**
618
- * Resolves the zod expression a generated client validates an error body with on the non-throw path.
619
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
620
- * operation documents no error responses with a schema.
496
+ * Builds the return statement of a generated operation function. The runtime call already resolves
497
+ * to `{ data, error, request, response }`; the generated code forwards that result and casts it to
498
+ * the operation's `RequestResult`, which carries the `throwOnError` discrimination.
499
+ *
500
+ * @example
501
+ * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
621
502
  */
622
- function buildZodErrorParse(node, zodResolver) {
623
- if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
624
- const name = zodResolver.response.error?.(node);
625
- return name ? {
626
- expression: name,
627
- importNames: [name]
628
- } : null;
503
+ function buildReturnStatement({ node, tsResolver, callConfig }) {
504
+ return `return request(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
505
+ node,
506
+ tsResolver
507
+ })}>>`;
629
508
  }
630
509
  //#endregion
631
510
  //#region ../../internals/client/src/builders/security.ts
@@ -696,67 +575,6 @@ function buildSecurityMetadata({ security }) {
696
575
  return `[${security.map(serializeAuth).join(", ")}]`;
697
576
  }
698
577
  //#endregion
699
- //#region ../../internals/client/src/builders/paramsRemap.ts
700
- /**
701
- * Builds the call-config entries that rename the camelCased `query` and `headers` keys back to the
702
- * names the OpenAPI document declares, so the wire format follows the spec while the generated
703
- * types keep camelCase keys. Returns an empty array when no name changes. Path parameters need no
704
- * remap because the URL template placeholders are renamed in sync with the `path` keys. Emit the
705
- * entries after the `...config` spread so they override the camelCased groups the caller passes in.
706
- *
707
- * @example
708
- * ```ts
709
- * // a query param named include_deleted in the spec
710
- * buildParamsRemap({ node }) // ['query: config.query ? { "include_deleted": config.query.includeDeleted } : config.query']
711
- * ```
712
- */
713
- function buildParamsRemap({ node }) {
714
- if (!ast.isHttpOperationNode(node)) return [];
715
- const original = getOperationParameters(node, { paramsCasing: "original" });
716
- const cased = getOperationParameters(node);
717
- const queryMapping = buildParamsMapping(original.query, cased.query);
718
- const headerMapping = buildParamsMapping(original.header, cased.header);
719
- const entries = [];
720
- if (queryMapping) entries.push(`query: ${buildParamsRemapExpression({
721
- source: "config.query",
722
- mapping: queryMapping
723
- })}`);
724
- if (headerMapping) entries.push(`headers: ${buildParamsRemapExpression({
725
- source: "config.headers",
726
- mapping: headerMapping
727
- })}`);
728
- return entries;
729
- }
730
- //#endregion
731
- //#region ../../internals/client/src/builders/generics.ts
732
- /**
733
- * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
734
- * record plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
735
- * runtime, so this only names the record and threads `ThrowOnError`.
736
- *
737
- * @example
738
- * `buildRequestResultGenerics({ node, tsResolver }) // 'AddPetResponses, ThrowOnError'`
739
- */
740
- function buildRequestResultGenerics({ node, tsResolver }) {
741
- return `${tsResolver.response.responses(node)}, ThrowOnError`;
742
- }
743
- //#endregion
744
- //#region ../../internals/client/src/builders/returnStatement.ts
745
- /**
746
- * Builds the return statement of a generated operation function. The runtime call already resolves
747
- * to `{ data, error, request, response }`; the generated code forwards that result and casts it to
748
- * the operation's `RequestResult`, which carries the `throwOnError` discrimination.
749
- *
750
- * @example
751
- * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
752
- */
753
- function buildReturnStatement({ node, tsResolver, callConfig }) {
754
- return `return request(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
755
- node,
756
- tsResolver
757
- })}>>`;
758
- }
759
- //#endregion
760
578
  //#region ../../internals/client/src/builders/signature.ts
761
579
  const declarationPrinter = functionPrinter({ mode: "declaration" });
762
580
  /**
@@ -788,6 +606,151 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
788
606
  };
789
607
  }
790
608
  //#endregion
609
+ //#region ../../internals/client/src/builders/validatorOptions.ts
610
+ /**
611
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
612
+ */
613
+ function isValidatorEnabled(validator) {
614
+ if (!validator) return false;
615
+ if (validator === "zod") return true;
616
+ return Boolean(validator.request || validator.response);
617
+ }
618
+ /**
619
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
620
+ * `'zod'` validates the response only, so it does not enable request parsing.
621
+ */
622
+ function resolveRequestValidator(validator) {
623
+ if (!validator || validator === "zod") return null;
624
+ return validator.request ?? null;
625
+ }
626
+ /**
627
+ * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
628
+ * `{ request: 'zod' }` enables it.
629
+ */
630
+ function resolveQueryParamsValidator(validator) {
631
+ if (!validator || validator === "zod") return null;
632
+ return validator.request ?? null;
633
+ }
634
+ /**
635
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
636
+ * maps to response parsing.
637
+ */
638
+ function resolveResponseValidator(validator) {
639
+ if (!validator) return null;
640
+ if (validator === "zod") return "zod";
641
+ return validator.response ?? null;
642
+ }
643
+ /**
644
+ * Resolves the zod expression a generated client validates a success response with. Only success
645
+ * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
646
+ * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
647
+ */
648
+ function buildZodResponseParse(node, zodResolver) {
649
+ const name = zodResolver.response.response(node);
650
+ return name ? {
651
+ expression: name,
652
+ importNames: [name]
653
+ } : null;
654
+ }
655
+ /**
656
+ * Resolves the zod expression a generated client validates an error body with on the non-throw path.
657
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
658
+ * operation documents no error responses with a schema.
659
+ */
660
+ function buildZodErrorParse(node, zodResolver) {
661
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
662
+ const name = zodResolver.response.error?.(node);
663
+ return name ? {
664
+ expression: name,
665
+ importNames: [name]
666
+ } : null;
667
+ }
668
+ //#endregion
669
+ //#region ../../internals/client/src/builders/validator.ts
670
+ /**
671
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
672
+ * response validation runs on the success body only. Returns `null` references when the matching
673
+ * direction is disabled or the schema is absent.
674
+ */
675
+ function buildValidatorHooks({ node, validator, zodResolver }) {
676
+ const importedZodNames = [];
677
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
678
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
679
+ const request = zodRequestName ?? null;
680
+ if (zodRequestName) importedZodNames.push(zodRequestName);
681
+ const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
682
+ const response = responseParse ? responseParse.expression : null;
683
+ if (responseParse) importedZodNames.push(...responseParse.importNames);
684
+ const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
685
+ const error = errorParse ? errorParse.expression : null;
686
+ if (errorParse) importedZodNames.push(...errorParse.importNames);
687
+ return {
688
+ request,
689
+ response,
690
+ error,
691
+ importedZodNames
692
+ };
693
+ }
694
+ //#endregion
695
+ //#region ../../internals/client/src/builders/sdkMethod.ts
696
+ /**
697
+ * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
698
+ * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
699
+ * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
700
+ */
701
+ function buildCallConfig({ node, validator, zodResolver, security }) {
702
+ const validators = buildValidatorHooks({
703
+ node,
704
+ validator,
705
+ zodResolver
706
+ });
707
+ const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
708
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
709
+ const securityLiteral = buildSecurityMetadata({ security });
710
+ return `{ ${[
711
+ `method: '${node.method.toUpperCase()}'`,
712
+ `url: '${Url.toSafeTemplate(node.path)}'`,
713
+ securityLiteral ? `security: ${securityLiteral}` : null,
714
+ validatorLiteral,
715
+ "...config"
716
+ ].filter(Boolean).join(", ")} }`;
717
+ }
718
+ /**
719
+ * Builds a single instance method for a generated SDK class. The body forwards the single grouped
720
+ * `options` object to the instance's own client (`this.client`, built once in the constructor) and
721
+ * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
722
+ * one operation can be routed to a different environment without a new instance.
723
+ */
724
+ function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
725
+ if (!ast.isHttpOperationNode(node)) return "";
726
+ const signature = buildGroupedOptionsSignature({
727
+ node,
728
+ tsResolver
729
+ });
730
+ const returnStatement = buildReturnStatement({
731
+ node,
732
+ tsResolver,
733
+ callConfig: buildCallConfig({
734
+ node,
735
+ validator,
736
+ zodResolver,
737
+ security
738
+ })
739
+ });
740
+ const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
741
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
742
+ link: "urlPath",
743
+ linkPosition: "beforeDeprecated",
744
+ splitLines: true
745
+ }));
746
+ const methodBody = [
747
+ "const { client: request = this.client, ...config } = options",
748
+ "",
749
+ returnStatement
750
+ ].map((line) => line ? ` ${line}` : "").join("\n");
751
+ return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
752
+ }
753
+ //#endregion
791
754
  //#region ../../internals/client/src/builders/styles.ts
792
755
  /**
793
756
  * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
@@ -841,32 +804,6 @@ function buildStyles({ node }) {
841
804
  return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
842
805
  }
843
806
  //#endregion
844
- //#region ../../internals/client/src/builders/validator.ts
845
- /**
846
- * Builds the validator-hook references for one operation. Request validation runs before the send;
847
- * response validation runs on the success body only. Returns `null` references when the matching
848
- * direction is disabled or the schema is absent.
849
- */
850
- function buildValidatorHooks({ node, validator, zodResolver }) {
851
- const importedZodNames = [];
852
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
853
- const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
854
- const request = zodRequestName ?? null;
855
- if (zodRequestName) importedZodNames.push(zodRequestName);
856
- const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
857
- const response = responseParse ? responseParse.expression : null;
858
- if (responseParse) importedZodNames.push(...responseParse.importNames);
859
- const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
860
- const error = errorParse ? errorParse.expression : null;
861
- if (errorParse) importedZodNames.push(...errorParse.importNames);
862
- return {
863
- request,
864
- response,
865
- error,
866
- importedZodNames
867
- };
868
- }
869
- //#endregion
870
807
  //#region ../../internals/client/src/components/Operation.tsx
871
808
  /**
872
809
  * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
@@ -901,14 +838,13 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
901
838
  const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
902
839
  const callConfig = `{ ${[
903
840
  `method: '${node.method.toUpperCase()}'`,
904
- `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
841
+ `url: '${Url.toSafeTemplate(node.path)}'`,
905
842
  securityLiteral ? `security: ${securityLiteral}` : null,
906
843
  stylesLiteral ? `styles: ${stylesLiteral}` : null,
907
844
  validatorLiteral,
908
845
  contentTypeLiteral,
909
846
  responseTypeLiteral,
910
- "...config",
911
- ...buildParamsRemap({ node })
847
+ "...config"
912
848
  ].filter(Boolean).join(", ")} }`;
913
849
  const eventType = `SuccessOf<${tsResolver.response.responses(node)}>`;
914
850
  const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
@@ -941,66 +877,6 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
941
877
  });
942
878
  }
943
879
  //#endregion
944
- //#region ../../internals/client/src/builders/sdkMethod.ts
945
- /**
946
- * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
947
- * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
948
- * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
949
- */
950
- function buildCallConfig({ node, validator, zodResolver, security }) {
951
- const validators = buildValidatorHooks({
952
- node,
953
- validator,
954
- zodResolver
955
- });
956
- const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
957
- const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
958
- const securityLiteral = buildSecurityMetadata({ security });
959
- return `{ ${[
960
- `method: '${node.method.toUpperCase()}'`,
961
- `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
962
- securityLiteral ? `security: ${securityLiteral}` : null,
963
- validatorLiteral,
964
- "...config",
965
- ...buildParamsRemap({ node })
966
- ].filter(Boolean).join(", ")} }`;
967
- }
968
- /**
969
- * Builds a single instance method for a generated SDK class. The body forwards the single grouped
970
- * `options` object to the instance's own client (`this.client`, built once in the constructor) and
971
- * returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
972
- * one operation can be routed to a different environment without a new instance.
973
- */
974
- function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
975
- if (!ast.isHttpOperationNode(node)) return "";
976
- const signature = buildGroupedOptionsSignature({
977
- node,
978
- tsResolver
979
- });
980
- const returnStatement = buildReturnStatement({
981
- node,
982
- tsResolver,
983
- callConfig: buildCallConfig({
984
- node,
985
- validator,
986
- zodResolver,
987
- security
988
- })
989
- });
990
- const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
991
- const jsdoc = buildJSDoc(buildOperationComments(node, {
992
- link: "urlPath",
993
- linkPosition: "beforeDeprecated",
994
- splitLines: true
995
- }));
996
- const methodBody = [
997
- "const { client: request = this.client, ...config } = options",
998
- "",
999
- returnStatement
1000
- ].map((line) => line ? ` ${line}` : "").join("\n");
1001
- return `${jsdoc} public ${name}${generics}(${signature.paramsSignature}): ${signature.returnType} {\n${methodBody}\n }`;
1002
- }
1003
- //#endregion
1004
880
  //#region ../../internals/client/src/components/SdkClient.tsx
1005
881
  /**
1006
882
  * Renders one instance class per tag with one method per operation. The constructor takes a client
@@ -1061,7 +937,7 @@ function resolveTypeImportNames(node, tsResolver) {
1061
937
  return [tsResolver.response.options(node), tsResolver.response.responses(node)];
1062
938
  }
1063
939
  function resolveZodImportNames(node, zodResolver, validator) {
1064
- const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
940
+ const { query: queryParams } = getOperationParameters(node);
1065
941
  return [
1066
942
  resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null,
1067
943
  resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,