@kubb/plugin-zod 5.0.0-beta.98 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,180 @@
1
1
  import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
2
- import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
2
+ import { Resolver, ast, containsCircularRef, createResolver, defineGenerator, definePlugin, extractRefName, syncSchemaRef } from "kubb/kit";
3
3
  import { Const, File, Type, jsxRenderer } from "kubb/jsx";
4
4
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
5
+ //#region ../../internals/shared/src/params.ts
6
+ /**
7
+ * Drops parameters that share the same name, keeping the first.
8
+ *
9
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
10
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
11
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
12
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
13
+ */
14
+ function dedupeParams(params) {
15
+ const seen = /* @__PURE__ */ new Set();
16
+ return params.filter((param) => {
17
+ if (seen.has(param.name)) return false;
18
+ seen.add(param.name);
19
+ return true;
20
+ });
21
+ }
22
+ //#endregion
23
+ //#region ../../internals/shared/src/operation.ts
24
+ /**
25
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
26
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
27
+ */
28
+ function getContentTypeSuffix(contentType) {
29
+ const baseType = contentType.split(";")[0].trim();
30
+ if (baseType === "application/json") return "Json";
31
+ if (baseType === "multipart/form-data") return "FormData";
32
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
33
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
34
+ if (parts.length === 0) return "Unknown";
35
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
36
+ }
37
+ /**
38
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
39
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
40
+ */
41
+ function getPerContentTypeName(baseName, suffix) {
42
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
43
+ return baseName + suffix;
44
+ }
45
+ /**
46
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
47
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
48
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
49
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
50
+ */
51
+ function resolveContentTypeVariants(entries, baseName) {
52
+ const usedNames = /* @__PURE__ */ new Set();
53
+ return entries.filter((entry) => entry.schema).map((entry) => {
54
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
55
+ let suffix = baseSuffix;
56
+ let name = getPerContentTypeName(baseName, suffix);
57
+ let counter = 2;
58
+ while (usedNames.has(name)) {
59
+ suffix = `${baseSuffix}${counter++}`;
60
+ name = getPerContentTypeName(baseName, suffix);
61
+ }
62
+ usedNames.add(name);
63
+ return {
64
+ name,
65
+ suffix,
66
+ schema: entry.schema,
67
+ keysToOmit: entry.keysToOmit,
68
+ contentType: entry.contentType
69
+ };
70
+ });
71
+ }
72
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
73
+ /**
74
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
75
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
76
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
77
+ * same parameters once per plugin.
78
+ */
79
+ function getOperationParameters(node) {
80
+ const cached = operationParameterGroupsByNode.get(node);
81
+ if (cached) return cached;
82
+ const groups = {
83
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
84
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
85
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
86
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
87
+ };
88
+ operationParameterGroupsByNode.set(node, groups);
89
+ return groups;
90
+ }
91
+ /**
92
+ * Builds the combined `{ body, path, query, headers }` options object schema for an operation,
93
+ * referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
94
+ * `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
95
+ * shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
96
+ * printer to emit `z.object(…)` rather than a record.
97
+ */
98
+ function buildOptionsSchema(node, resolver) {
99
+ const { path, query, header } = getOperationParameters(node);
100
+ const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
101
+ const createNever = () => ast.factory.createSchema({
102
+ type: "never",
103
+ primitive: void 0,
104
+ optional: true
105
+ });
106
+ const groups = [
107
+ {
108
+ name: "path",
109
+ params: path,
110
+ resolve: resolver.param.path
111
+ },
112
+ {
113
+ name: "query",
114
+ params: query,
115
+ resolve: resolver.param.query
116
+ },
117
+ {
118
+ name: "headers",
119
+ params: header,
120
+ resolve: resolver.param.headers
121
+ }
122
+ ];
123
+ return ast.factory.createSchema({
124
+ type: "object",
125
+ primitive: "object",
126
+ deprecated: node.deprecated,
127
+ properties: [ast.factory.createProperty({
128
+ name: "body",
129
+ required: hasBody,
130
+ schema: hasBody ? ast.factory.createSchema({
131
+ type: "ref",
132
+ name: resolver.response.body(node)
133
+ }) : createNever()
134
+ }), ...groups.map(({ name, params, resolve }) => {
135
+ const required = params.some((param) => param.required);
136
+ return ast.factory.createProperty({
137
+ name,
138
+ required,
139
+ schema: params.length > 0 ? ast.factory.createSchema({
140
+ type: "ref",
141
+ name: resolve.call(resolver.param, node, params[0]),
142
+ optional: !required
143
+ }) : createNever()
144
+ });
145
+ })]
146
+ });
147
+ }
148
+ function getStatusCodeNumber(statusCode) {
149
+ const code = Number(statusCode);
150
+ return Number.isNaN(code) ? null : code;
151
+ }
152
+ function isSuccessStatusCode(statusCode) {
153
+ const code = getStatusCodeNumber(statusCode);
154
+ return code !== null && code >= 200 && code < 300;
155
+ }
156
+ function getSuccessResponses(responses) {
157
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
158
+ }
159
+ //#endregion
160
+ //#region ../../internals/shared/src/adapter.ts
161
+ /**
162
+ * Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
163
+ * so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
164
+ *
165
+ * Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
166
+ * clear, actionable error at the point of use.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * const { dateType } = getOasAdapter(ctx.adapter).options
171
+ * ```
172
+ */
173
+ function getOasAdapter(adapter) {
174
+ if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
175
+ return adapter;
176
+ }
177
+ //#endregion
5
178
  //#region ../../internals/utils/src/casing.ts
6
179
  /**
7
180
  * Shared implementation for camelCase and PascalCase conversion.
@@ -346,106 +519,6 @@ function toFilePath(name, caseLast = camelCase) {
346
519
  return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
347
520
  }
348
521
  //#endregion
349
- //#region ../../internals/shared/src/params.ts
350
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
351
- /**
352
- * Applies camelCase to parameter names and returns a new array without mutating the input.
353
- *
354
- * Run it before handing parameters to schema builders so output property keys get the right casing
355
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
356
- * original array is returned unchanged. Results are cached per input array.
357
- */
358
- function caseParams(params, casing) {
359
- if (!casing) return params;
360
- const cached = caseParamsCache.get(params);
361
- if (cached) return cached;
362
- const result = params.map((param) => ({
363
- ...param,
364
- name: camelCase(param.name)
365
- }));
366
- caseParamsCache.set(params, result);
367
- return result;
368
- }
369
- //#endregion
370
- //#region ../../internals/shared/src/operation.ts
371
- /**
372
- * Maps a content type to the PascalCase suffix used to name per-content-type variants
373
- * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
374
- */
375
- function getContentTypeSuffix(contentType) {
376
- const baseType = contentType.split(";")[0].trim();
377
- if (baseType === "application/json") return "Json";
378
- if (baseType === "multipart/form-data") return "FormData";
379
- if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
380
- const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
381
- if (parts.length === 0) return "Unknown";
382
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
383
- }
384
- /**
385
- * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
386
- * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
387
- */
388
- function getPerContentTypeName(baseName, suffix) {
389
- if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
390
- return baseName + suffix;
391
- }
392
- /**
393
- * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
394
- * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
395
- * the final (possibly counter-augmented) value, so callers can derive parallel names in another
396
- * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
397
- */
398
- function resolveContentTypeVariants(entries, baseName) {
399
- const usedNames = /* @__PURE__ */ new Set();
400
- return entries.filter((entry) => entry.schema).map((entry) => {
401
- const baseSuffix = getContentTypeSuffix(entry.contentType);
402
- let suffix = baseSuffix;
403
- let name = getPerContentTypeName(baseName, suffix);
404
- let counter = 2;
405
- while (usedNames.has(name)) {
406
- suffix = `${baseSuffix}${counter++}`;
407
- name = getPerContentTypeName(baseName, suffix);
408
- }
409
- usedNames.add(name);
410
- return {
411
- name,
412
- suffix,
413
- schema: entry.schema,
414
- keysToOmit: entry.keysToOmit,
415
- contentType: entry.contentType
416
- };
417
- });
418
- }
419
- function getStatusCodeNumber(statusCode) {
420
- const code = Number(statusCode);
421
- return Number.isNaN(code) ? null : code;
422
- }
423
- function isSuccessStatusCode(statusCode) {
424
- const code = getStatusCodeNumber(statusCode);
425
- return code !== null && code >= 200 && code < 300;
426
- }
427
- function getSuccessResponses(responses) {
428
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
429
- }
430
- //#endregion
431
- //#region ../../internals/shared/src/adapter.ts
432
- /**
433
- * Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
434
- * so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
435
- *
436
- * Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
437
- * clear, actionable error at the point of use.
438
- *
439
- * @example
440
- * ```ts
441
- * const { dateType } = getOasAdapter(ctx.adapter).options
442
- * ```
443
- */
444
- function getOasAdapter(adapter) {
445
- if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
446
- return adapter;
447
- }
448
- //#endregion
449
522
  //#region ../../internals/shared/src/resolver.ts
450
523
  /**
451
524
  * Resolves a single operation parameter name with the
@@ -458,6 +531,29 @@ function operationParamName(node, param) {
458
531
  return this.name(`${node.operationId} ${param.in} ${param.name}`);
459
532
  }
460
533
  /**
534
+ * Builds the shared `param` namespace. Spread the result into `createResolver`
535
+ * and override individual methods next to it when a plugin deviates.
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * createResolver<PluginTs>({ param: createOperationParamResolver(), ... })
540
+ * ```
541
+ */
542
+ function createOperationParamResolver() {
543
+ return {
544
+ name: operationParamName,
545
+ path(node) {
546
+ return this.name(`${node.operationId} Path`);
547
+ },
548
+ query(node) {
549
+ return this.name(`${node.operationId} Query`);
550
+ },
551
+ headers(node) {
552
+ return this.name(`${node.operationId} Headers`);
553
+ }
554
+ };
555
+ }
556
+ /**
461
557
  * Builds the shared `response` namespace. Spread the result into
462
558
  * `createResolver` and add plugin-specific methods (`options`, `error`) next
463
559
  * to it.
@@ -505,7 +601,7 @@ function createCasedFile(caseLast) {
505
601
  * `resolver.imports` would resolve file paths that are then discarded.
506
602
  */
507
603
  function collectRefNames(schema) {
508
- return ast.collect(schema, { schema: (node) => {
604
+ return ast.collectSync(schema, { schema: (node) => {
509
605
  const refNode = ast.narrowSchema(node, "ref");
510
606
  if (!refNode?.ref) return null;
511
607
  return ast.resolveRefName(refNode);
@@ -543,6 +639,45 @@ function createGroupConfig(group) {
543
639
  };
544
640
  }
545
641
  //#endregion
642
+ //#region ../../internals/shared/src/schemaTraversal.ts
643
+ /**
644
+ * Maps each property of an object schema to its transformed output. Pairs every result with the
645
+ * original property so the printer keeps full control over modifiers, getters, and key syntax.
646
+ *
647
+ * @example
648
+ * ```ts
649
+ * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
650
+ * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
651
+ * ```
652
+ */
653
+ function mapSchemaProperties(node, transform) {
654
+ return node.properties.map((property) => ({
655
+ name: property.name,
656
+ property,
657
+ output: transform(property.schema)
658
+ }));
659
+ }
660
+ /**
661
+ * Maps each member of a union or intersection schema to its transformed output, pairing every
662
+ * result with the original member.
663
+ */
664
+ function mapSchemaMembers(node, transform) {
665
+ return (node.members ?? []).map((schema) => ({
666
+ schema,
667
+ output: transform(schema)
668
+ }));
669
+ }
670
+ /**
671
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
672
+ * the original item.
673
+ */
674
+ function mapSchemaItems(node, transform) {
675
+ return (node.items ?? []).map((schema) => ({
676
+ schema,
677
+ output: transform(schema)
678
+ }));
679
+ }
680
+ //#endregion
546
681
  //#region src/components/Zod.tsx
547
682
  function Zod({ name, node, printer, inferTypeName, cyclic }) {
548
683
  const output = printer.print(node);
@@ -626,12 +761,12 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
626
761
  if (hasCodec(node)) return true;
627
762
  if (node.type === "ref") {
628
763
  if (!node.ref) return false;
629
- const refName = ast.extractRefName(node.ref);
764
+ const refName = extractRefName(node.ref);
630
765
  if (refName) {
631
766
  if (seen.has(refName)) return false;
632
767
  seen.add(refName);
633
768
  }
634
- const resolved = ast.syncSchemaRef(node);
769
+ const resolved = syncSchemaRef(node);
635
770
  if (resolved.type === "ref") return false;
636
771
  return containsCodec(resolved, seen);
637
772
  }
@@ -647,7 +782,7 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
647
782
  * them to their input (encode) variant.
648
783
  */
649
784
  function collectCodecRefNames(node) {
650
- return ast.collect(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.resolveRefName(n) ?? void 0 : void 0 });
785
+ return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.resolveRefName(n) ?? void 0 : void 0 });
651
786
  }
652
787
  /**
653
788
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
@@ -670,7 +805,7 @@ function isObjectSchemaNode(node, cyclicSchemas) {
670
805
  if (node.type === "ref") {
671
806
  const refName = ast.resolveRefName(node);
672
807
  if (refName && cyclicSchemas?.has(refName)) return false;
673
- const resolved = ast.syncSchemaRef(node);
808
+ const resolved = syncSchemaRef(node);
674
809
  return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
675
810
  }
676
811
  if (node.type === "union") {
@@ -746,6 +881,20 @@ function buildEnum(values) {
746
881
  return `z.union([${literals.join(", ")}])`;
747
882
  }
748
883
  /**
884
+ * Digit pattern for a `type: 'string'` schema that carries an integer `format`, the way ProtoJSON
885
+ * encodes 64-bit integers. Returns `undefined` for every other format, and a `pattern` from the
886
+ * spec takes precedence over this fallback.
887
+ *
888
+ * @example
889
+ * ```ts
890
+ * integerFormatPattern('uint64') // '^\\d+$'
891
+ * ```
892
+ */
893
+ function integerFormatPattern(format) {
894
+ if (format === "int32" || format === "int64") return "^-?\\d+$";
895
+ if (format === "uint64") return "^\\d+$";
896
+ }
897
+ /**
749
898
  * Map a `regexType` to the `func` argument of `toRegExpString`: `'constructor'` emits
750
899
  * `new RegExp(...)`, while `'literal'` (the default) emits a regex literal.
751
900
  */
@@ -870,6 +1019,22 @@ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaul
870
1019
  const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
871
1020
  return literal !== null ? `z._default(${withModifier}, ${literal})` : withModifier;
872
1021
  }
1022
+ /**
1023
+ * Builds an `object` schema node grouping the given parameter nodes.
1024
+ * The `primitive: 'object'` marker ensures the Zod printer emits `z.object(…)` rather than a record.
1025
+ */
1026
+ function buildGroupedParamsSchema({ params, optional }) {
1027
+ return ast.factory.createSchema({
1028
+ type: "object",
1029
+ optional,
1030
+ primitive: "object",
1031
+ properties: params.map((param) => ast.factory.createProperty({
1032
+ name: param.name,
1033
+ required: param.required,
1034
+ schema: param.schema
1035
+ }))
1036
+ });
1037
+ }
873
1038
  //#endregion
874
1039
  //#region src/printers/printerZod.ts
875
1040
  function strictOneOfMember$1(member, node, cyclicSchemas) {
@@ -878,7 +1043,7 @@ function strictOneOfMember$1(member, node, cyclicSchemas) {
878
1043
  if (member.startsWith("z.lazy(")) return member;
879
1044
  const refName = ast.resolveRefName(node);
880
1045
  if (refName && cyclicSchemas?.has(refName)) return member;
881
- const schema = ast.syncSchemaRef(node);
1046
+ const schema = syncSchemaRef(node);
882
1047
  if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
883
1048
  if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
884
1049
  }
@@ -903,8 +1068,8 @@ function getMemberConstraint({ member, regexType }) {
903
1068
  function buildZodObjectShape(ctx, node) {
904
1069
  const objectNode = ast.narrowSchema(node, "object");
905
1070
  if (!objectNode) return "{}";
906
- const isCyclic = (schema) => ctx.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
907
- return buildObject(ast.mapSchemaProperties(objectNode, (schema) => {
1071
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1072
+ return buildObject(mapSchemaProperties(objectNode, (schema) => {
908
1073
  const hasSelfRef = isCyclic(schema);
909
1074
  const savedCyclicSchemas = ctx.options.cyclicSchemas;
910
1075
  if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
@@ -913,7 +1078,7 @@ function buildZodObjectShape(ctx, node) {
913
1078
  return baseOutput;
914
1079
  }).map(({ name: propName, property, output: baseOutput }) => {
915
1080
  const { schema } = property;
916
- const meta = ast.syncSchemaRef(schema);
1081
+ const meta = syncSchemaRef(schema);
917
1082
  const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
918
1083
  const value = applyModifiers({
919
1084
  value: baseOutput,
@@ -956,8 +1121,11 @@ const printerZod = ast.createPrinter((options) => {
956
1121
  boolean: () => "z.boolean()",
957
1122
  null: () => "z.null()",
958
1123
  string(node) {
959
- return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints({
1124
+ const base = shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()";
1125
+ const pattern = node.pattern ?? integerFormatPattern(node.format);
1126
+ return `${base}${lengthConstraints({
960
1127
  ...node,
1128
+ pattern,
961
1129
  regexType: this.options.regexType
962
1130
  })}`;
963
1131
  },
@@ -1056,18 +1224,18 @@ const printerZod = ast.createPrinter((options) => {
1056
1224
  })();
1057
1225
  },
1058
1226
  array(node) {
1059
- const base = `z.array(${ast.mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthConstraints({
1227
+ const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthConstraints({
1060
1228
  ...node,
1061
1229
  regexType: this.options.regexType
1062
1230
  })}`;
1063
1231
  return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
1064
1232
  },
1065
1233
  tuple(node) {
1066
- return `z.tuple(${buildList(ast.mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
1234
+ return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
1067
1235
  },
1068
1236
  union(node) {
1069
1237
  const nodeMembers = node.members ?? [];
1070
- const members = ast.mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1238
+ const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
1071
1239
  if (members.length === 0) return "";
1072
1240
  if (members.length === 1) return members[0];
1073
1241
  const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
@@ -1098,7 +1266,7 @@ const printerZod = ast.createPrinter((options) => {
1098
1266
  const { keysToOmit } = this.options;
1099
1267
  const transformed = this.transform(node);
1100
1268
  if (!transformed) return null;
1101
- const meta = ast.syncSchemaRef(node);
1269
+ const meta = syncSchemaRef(node);
1102
1270
  return applyModifiers({
1103
1271
  value: (() => {
1104
1272
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
@@ -1143,8 +1311,8 @@ function getMemberConstraintMini({ member, regexType }) {
1143
1311
  function buildZodMiniObjectShape(ctx, node) {
1144
1312
  const objectNode = ast.narrowSchema(node, "object");
1145
1313
  if (!objectNode) return "{}";
1146
- const isCyclic = (schema) => ctx.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1147
- return buildObject(ast.mapSchemaProperties(objectNode, (schema) => {
1314
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1315
+ return buildObject(mapSchemaProperties(objectNode, (schema) => {
1148
1316
  const hasSelfRef = isCyclic(schema);
1149
1317
  const savedCyclicSchemas = ctx.options.cyclicSchemas;
1150
1318
  if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
@@ -1153,7 +1321,7 @@ function buildZodMiniObjectShape(ctx, node) {
1153
1321
  return baseOutput;
1154
1322
  }).map(({ name: propName, property, output: baseOutput }) => {
1155
1323
  const { schema } = property;
1156
- const meta = ast.syncSchemaRef(schema);
1324
+ const meta = syncSchemaRef(schema);
1157
1325
  const value = applyMiniModifiers({
1158
1326
  value: baseOutput,
1159
1327
  schema,
@@ -1193,8 +1361,10 @@ const printerZodMini = ast.createPrinter((options) => {
1193
1361
  boolean: () => "z.boolean()",
1194
1362
  null: () => "z.null()",
1195
1363
  string(node) {
1364
+ const pattern = node.pattern ?? integerFormatPattern(node.format);
1196
1365
  return `z.string()${lengthChecksMini({
1197
1366
  ...node,
1367
+ pattern,
1198
1368
  regexType: this.options.regexType
1199
1369
  })}`;
1200
1370
  },
@@ -1282,18 +1452,18 @@ const printerZodMini = ast.createPrinter((options) => {
1282
1452
  return objectBase;
1283
1453
  },
1284
1454
  array(node) {
1285
- const base = `z.array(${ast.mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini({
1455
+ const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini({
1286
1456
  ...node,
1287
1457
  regexType: this.options.regexType
1288
1458
  })}`;
1289
1459
  return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
1290
1460
  },
1291
1461
  tuple(node) {
1292
- return `z.tuple(${buildList(ast.mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
1462
+ return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
1293
1463
  },
1294
1464
  union(node) {
1295
1465
  const nodeMembers = node.members ?? [];
1296
- const members = ast.mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
1466
+ const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
1297
1467
  if (members.length === 0) return "";
1298
1468
  if (members.length === 1) return members[0];
1299
1469
  const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
@@ -1324,7 +1494,7 @@ const printerZodMini = ast.createPrinter((options) => {
1324
1494
  const { keysToOmit } = this.options;
1325
1495
  const transformed = this.transform(node);
1326
1496
  if (!transformed) return null;
1327
- const meta = ast.syncSchemaRef(node);
1497
+ const meta = syncSchemaRef(node);
1328
1498
  return applyMiniModifiers({
1329
1499
  value: (() => {
1330
1500
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
@@ -1521,7 +1691,6 @@ const zodGenerator = defineGenerator({
1521
1691
  const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
1522
1692
  const dateType = getOasAdapter(adapter).options.dateType;
1523
1693
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
1524
- const params = caseParams(node.parameters, "camelcase");
1525
1694
  const meta = { file: resolver.file({
1526
1695
  name: node.operationId,
1527
1696
  extname: ".ts",
@@ -1627,7 +1796,7 @@ const zodGenerator = defineGenerator({
1627
1796
  name
1628
1797
  });
1629
1798
  }
1630
- const paramSchemas = params.map((param) => renderSchemaEntry({
1799
+ const paramSchemas = node.parameters.map((param) => renderSchemaEntry({
1631
1800
  schema: param.schema,
1632
1801
  name: resolver.param.name(node, param),
1633
1802
  direction: "input"
@@ -1676,6 +1845,30 @@ const zodGenerator = defineGenerator({
1676
1845
  description: node.requestBody.description ?? schema.description
1677
1846
  }), "input");
1678
1847
  })();
1848
+ const { path, query, header } = getOperationParameters(node);
1849
+ const paramGroupSchemas = inferred ? [
1850
+ {
1851
+ kind: "path",
1852
+ params: path
1853
+ },
1854
+ {
1855
+ kind: "query",
1856
+ params: query
1857
+ },
1858
+ {
1859
+ kind: "headers",
1860
+ params: header
1861
+ }
1862
+ ].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
1863
+ schema: buildGroupedParamsSchema({ params }),
1864
+ name: resolver.param[kind](node, params[0]),
1865
+ direction: "input"
1866
+ })) : [];
1867
+ const optionsSchema = inferred ? renderSchemaEntry({
1868
+ schema: buildOptionsSchema(node, resolver),
1869
+ name: resolver.name(`${node.operationId} Options`),
1870
+ direction: "input"
1871
+ }) : null;
1679
1872
  return /* @__PURE__ */ jsxs(File, {
1680
1873
  baseName: meta.file.baseName,
1681
1874
  path: meta.file.path,
@@ -1706,7 +1899,9 @@ const zodGenerator = defineGenerator({
1706
1899
  responseSchemas,
1707
1900
  responseUnionSchema,
1708
1901
  errorUnionSchema,
1709
- requestSchema
1902
+ requestSchema,
1903
+ paramGroupSchemas,
1904
+ optionsSchema
1710
1905
  ]
1711
1906
  });
1712
1907
  }
@@ -1748,22 +1943,14 @@ const resolverZod = createResolver({
1748
1943
  return this.schema.typeName(`${name} input`);
1749
1944
  }
1750
1945
  },
1751
- param: {
1752
- name: operationParamName,
1753
- path(node, param) {
1754
- return this.param.name(node, param);
1755
- },
1756
- query(node, param) {
1757
- return this.param.name(node, param);
1758
- },
1759
- headers(node, param) {
1760
- return this.param.name(node, param);
1761
- }
1762
- },
1946
+ param: createOperationParamResolver(),
1763
1947
  response: {
1764
1948
  ...createOperationResponseResolver(),
1765
1949
  error(node) {
1766
1950
  return this.name(`${node.operationId} Error`);
1951
+ },
1952
+ options(node) {
1953
+ return this.schema.type(this.name(`${node.operationId} Options`));
1767
1954
  }
1768
1955
  }
1769
1956
  });