@kubb/plugin-zod 5.0.0-beta.99 → 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.d.ts CHANGED
@@ -238,21 +238,21 @@ type ResolverZod = Resolver & {
238
238
  * Resolves the name for an operation's grouped path parameters schema.
239
239
  *
240
240
  * @example Path parameters names
241
- * `resolver.param.path(node, param) // → 'deletePetPathPetIdSchema'`
241
+ * `resolver.param.path(node, param) // → 'deletePetPathSchema'`
242
242
  */
243
243
  path(node: ast.OperationNode, param: ast.ParameterNode): string;
244
244
  /**
245
245
  * Resolves the name for an operation's grouped query parameters schema.
246
246
  *
247
247
  * @example Query parameters names
248
- * `resolver.param.query(node, param) // → 'findPetsByStatusQueryStatusSchema'`
248
+ * `resolver.param.query(node, param) // → 'findPetsByStatusQuerySchema'`
249
249
  */
250
250
  query(node: ast.OperationNode, param: ast.ParameterNode): string;
251
251
  /**
252
252
  * Resolves the name for an operation's grouped header parameters schema.
253
253
  *
254
254
  * @example Header parameters names
255
- * `resolver.param.headers(node, param) // → 'deletePetHeaderApiKeySchema'`
255
+ * `resolver.param.headers(node, param) // → 'deletePetHeadersSchema'`
256
256
  */
257
257
  headers(node: ast.OperationNode, param: ast.ParameterNode): string;
258
258
  };
@@ -297,6 +297,15 @@ type ResolverZod = Resolver & {
297
297
  * `resolver.response.error(node) // → 'listPetsErrorSchema'`
298
298
  */
299
299
  error(node: ast.OperationNode): string;
300
+ /**
301
+ * Resolves the inferred type name for an operation's combined `{ body, path, query, headers }`
302
+ * options object. Only meaningful when `inferred: true`, since the schema and type this name
303
+ * points to are generated only in that case.
304
+ *
305
+ * @example Options type names
306
+ * `resolver.response.options(node) // → 'ListPetsOptionsSchemaType'`
307
+ */
308
+ options(node: ast.OperationNode): string;
300
309
  };
301
310
  };
302
311
  /**
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,85 +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/operation.ts
350
- /**
351
- * Maps a content type to the PascalCase suffix used to name per-content-type variants
352
- * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
353
- */
354
- function getContentTypeSuffix(contentType) {
355
- const baseType = contentType.split(";")[0].trim();
356
- if (baseType === "application/json") return "Json";
357
- if (baseType === "multipart/form-data") return "FormData";
358
- if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
359
- const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
360
- if (parts.length === 0) return "Unknown";
361
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
362
- }
363
- /**
364
- * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
365
- * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
366
- */
367
- function getPerContentTypeName(baseName, suffix) {
368
- if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
369
- return baseName + suffix;
370
- }
371
- /**
372
- * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
373
- * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
374
- * the final (possibly counter-augmented) value, so callers can derive parallel names in another
375
- * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
376
- */
377
- function resolveContentTypeVariants(entries, baseName) {
378
- const usedNames = /* @__PURE__ */ new Set();
379
- return entries.filter((entry) => entry.schema).map((entry) => {
380
- const baseSuffix = getContentTypeSuffix(entry.contentType);
381
- let suffix = baseSuffix;
382
- let name = getPerContentTypeName(baseName, suffix);
383
- let counter = 2;
384
- while (usedNames.has(name)) {
385
- suffix = `${baseSuffix}${counter++}`;
386
- name = getPerContentTypeName(baseName, suffix);
387
- }
388
- usedNames.add(name);
389
- return {
390
- name,
391
- suffix,
392
- schema: entry.schema,
393
- keysToOmit: entry.keysToOmit,
394
- contentType: entry.contentType
395
- };
396
- });
397
- }
398
- function getStatusCodeNumber(statusCode) {
399
- const code = Number(statusCode);
400
- return Number.isNaN(code) ? null : code;
401
- }
402
- function isSuccessStatusCode(statusCode) {
403
- const code = getStatusCodeNumber(statusCode);
404
- return code !== null && code >= 200 && code < 300;
405
- }
406
- function getSuccessResponses(responses) {
407
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
408
- }
409
- //#endregion
410
- //#region ../../internals/shared/src/adapter.ts
411
- /**
412
- * Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
413
- * so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
414
- *
415
- * Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
416
- * clear, actionable error at the point of use.
417
- *
418
- * @example
419
- * ```ts
420
- * const { dateType } = getOasAdapter(ctx.adapter).options
421
- * ```
422
- */
423
- function getOasAdapter(adapter) {
424
- if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
425
- return adapter;
426
- }
427
- //#endregion
428
522
  //#region ../../internals/shared/src/resolver.ts
429
523
  /**
430
524
  * Resolves a single operation parameter name with the
@@ -437,6 +531,29 @@ function operationParamName(node, param) {
437
531
  return this.name(`${node.operationId} ${param.in} ${param.name}`);
438
532
  }
439
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
+ /**
440
557
  * Builds the shared `response` namespace. Spread the result into
441
558
  * `createResolver` and add plugin-specific methods (`options`, `error`) next
442
559
  * to it.
@@ -484,7 +601,7 @@ function createCasedFile(caseLast) {
484
601
  * `resolver.imports` would resolve file paths that are then discarded.
485
602
  */
486
603
  function collectRefNames(schema) {
487
- return ast.collect(schema, { schema: (node) => {
604
+ return ast.collectSync(schema, { schema: (node) => {
488
605
  const refNode = ast.narrowSchema(node, "ref");
489
606
  if (!refNode?.ref) return null;
490
607
  return ast.resolveRefName(refNode);
@@ -644,12 +761,12 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
644
761
  if (hasCodec(node)) return true;
645
762
  if (node.type === "ref") {
646
763
  if (!node.ref) return false;
647
- const refName = ast.extractRefName(node.ref);
764
+ const refName = extractRefName(node.ref);
648
765
  if (refName) {
649
766
  if (seen.has(refName)) return false;
650
767
  seen.add(refName);
651
768
  }
652
- const resolved = ast.syncSchemaRef(node);
769
+ const resolved = syncSchemaRef(node);
653
770
  if (resolved.type === "ref") return false;
654
771
  return containsCodec(resolved, seen);
655
772
  }
@@ -665,7 +782,7 @@ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
665
782
  * them to their input (encode) variant.
666
783
  */
667
784
  function collectCodecRefNames(node) {
668
- 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 });
669
786
  }
670
787
  /**
671
788
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
@@ -688,7 +805,7 @@ function isObjectSchemaNode(node, cyclicSchemas) {
688
805
  if (node.type === "ref") {
689
806
  const refName = ast.resolveRefName(node);
690
807
  if (refName && cyclicSchemas?.has(refName)) return false;
691
- const resolved = ast.syncSchemaRef(node);
808
+ const resolved = syncSchemaRef(node);
692
809
  return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
693
810
  }
694
811
  if (node.type === "union") {
@@ -764,6 +881,20 @@ function buildEnum(values) {
764
881
  return `z.union([${literals.join(", ")}])`;
765
882
  }
766
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
+ /**
767
898
  * Map a `regexType` to the `func` argument of `toRegExpString`: `'constructor'` emits
768
899
  * `new RegExp(...)`, while `'literal'` (the default) emits a regex literal.
769
900
  */
@@ -888,6 +1019,22 @@ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaul
888
1019
  const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
889
1020
  return literal !== null ? `z._default(${withModifier}, ${literal})` : withModifier;
890
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
+ }
891
1038
  //#endregion
892
1039
  //#region src/printers/printerZod.ts
893
1040
  function strictOneOfMember$1(member, node, cyclicSchemas) {
@@ -896,7 +1043,7 @@ function strictOneOfMember$1(member, node, cyclicSchemas) {
896
1043
  if (member.startsWith("z.lazy(")) return member;
897
1044
  const refName = ast.resolveRefName(node);
898
1045
  if (refName && cyclicSchemas?.has(refName)) return member;
899
- const schema = ast.syncSchemaRef(node);
1046
+ const schema = syncSchemaRef(node);
900
1047
  if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
901
1048
  if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
902
1049
  }
@@ -921,7 +1068,7 @@ function getMemberConstraint({ member, regexType }) {
921
1068
  function buildZodObjectShape(ctx, node) {
922
1069
  const objectNode = ast.narrowSchema(node, "object");
923
1070
  if (!objectNode) return "{}";
924
- const isCyclic = (schema) => ctx.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1071
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
925
1072
  return buildObject(mapSchemaProperties(objectNode, (schema) => {
926
1073
  const hasSelfRef = isCyclic(schema);
927
1074
  const savedCyclicSchemas = ctx.options.cyclicSchemas;
@@ -931,7 +1078,7 @@ function buildZodObjectShape(ctx, node) {
931
1078
  return baseOutput;
932
1079
  }).map(({ name: propName, property, output: baseOutput }) => {
933
1080
  const { schema } = property;
934
- const meta = ast.syncSchemaRef(schema);
1081
+ const meta = syncSchemaRef(schema);
935
1082
  const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
936
1083
  const value = applyModifiers({
937
1084
  value: baseOutput,
@@ -974,8 +1121,11 @@ const printerZod = ast.createPrinter((options) => {
974
1121
  boolean: () => "z.boolean()",
975
1122
  null: () => "z.null()",
976
1123
  string(node) {
977
- 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({
978
1127
  ...node,
1128
+ pattern,
979
1129
  regexType: this.options.regexType
980
1130
  })}`;
981
1131
  },
@@ -1116,7 +1266,7 @@ const printerZod = ast.createPrinter((options) => {
1116
1266
  const { keysToOmit } = this.options;
1117
1267
  const transformed = this.transform(node);
1118
1268
  if (!transformed) return null;
1119
- const meta = ast.syncSchemaRef(node);
1269
+ const meta = syncSchemaRef(node);
1120
1270
  return applyModifiers({
1121
1271
  value: (() => {
1122
1272
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
@@ -1161,7 +1311,7 @@ function getMemberConstraintMini({ member, regexType }) {
1161
1311
  function buildZodMiniObjectShape(ctx, node) {
1162
1312
  const objectNode = ast.narrowSchema(node, "object");
1163
1313
  if (!objectNode) return "{}";
1164
- const isCyclic = (schema) => ctx.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1314
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1165
1315
  return buildObject(mapSchemaProperties(objectNode, (schema) => {
1166
1316
  const hasSelfRef = isCyclic(schema);
1167
1317
  const savedCyclicSchemas = ctx.options.cyclicSchemas;
@@ -1171,7 +1321,7 @@ function buildZodMiniObjectShape(ctx, node) {
1171
1321
  return baseOutput;
1172
1322
  }).map(({ name: propName, property, output: baseOutput }) => {
1173
1323
  const { schema } = property;
1174
- const meta = ast.syncSchemaRef(schema);
1324
+ const meta = syncSchemaRef(schema);
1175
1325
  const value = applyMiniModifiers({
1176
1326
  value: baseOutput,
1177
1327
  schema,
@@ -1211,8 +1361,10 @@ const printerZodMini = ast.createPrinter((options) => {
1211
1361
  boolean: () => "z.boolean()",
1212
1362
  null: () => "z.null()",
1213
1363
  string(node) {
1364
+ const pattern = node.pattern ?? integerFormatPattern(node.format);
1214
1365
  return `z.string()${lengthChecksMini({
1215
1366
  ...node,
1367
+ pattern,
1216
1368
  regexType: this.options.regexType
1217
1369
  })}`;
1218
1370
  },
@@ -1342,7 +1494,7 @@ const printerZodMini = ast.createPrinter((options) => {
1342
1494
  const { keysToOmit } = this.options;
1343
1495
  const transformed = this.transform(node);
1344
1496
  if (!transformed) return null;
1345
- const meta = ast.syncSchemaRef(node);
1497
+ const meta = syncSchemaRef(node);
1346
1498
  return applyMiniModifiers({
1347
1499
  value: (() => {
1348
1500
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
@@ -1693,6 +1845,30 @@ const zodGenerator = defineGenerator({
1693
1845
  description: node.requestBody.description ?? schema.description
1694
1846
  }), "input");
1695
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;
1696
1872
  return /* @__PURE__ */ jsxs(File, {
1697
1873
  baseName: meta.file.baseName,
1698
1874
  path: meta.file.path,
@@ -1723,7 +1899,9 @@ const zodGenerator = defineGenerator({
1723
1899
  responseSchemas,
1724
1900
  responseUnionSchema,
1725
1901
  errorUnionSchema,
1726
- requestSchema
1902
+ requestSchema,
1903
+ paramGroupSchemas,
1904
+ optionsSchema
1727
1905
  ]
1728
1906
  });
1729
1907
  }
@@ -1765,22 +1943,14 @@ const resolverZod = createResolver({
1765
1943
  return this.schema.typeName(`${name} input`);
1766
1944
  }
1767
1945
  },
1768
- param: {
1769
- name: operationParamName,
1770
- path(node, param) {
1771
- return this.param.name(node, param);
1772
- },
1773
- query(node, param) {
1774
- return this.param.name(node, param);
1775
- },
1776
- headers(node, param) {
1777
- return this.param.name(node, param);
1778
- }
1779
- },
1946
+ param: createOperationParamResolver(),
1780
1947
  response: {
1781
1948
  ...createOperationResponseResolver(),
1782
1949
  error(node) {
1783
1950
  return this.name(`${node.operationId} Error`);
1951
+ },
1952
+ options(node) {
1953
+ return this.schema.type(this.name(`${node.operationId} Options`));
1784
1954
  }
1785
1955
  }
1786
1956
  });