@kubb/plugin-zod 5.0.0-alpha.24 → 5.0.0-alpha.26

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.
Files changed (47) hide show
  1. package/dist/index.cjs +1673 -88
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +317 -2
  4. package/dist/index.js +1646 -88
  5. package/dist/index.js.map +1 -1
  6. package/package.json +5 -33
  7. package/src/components/Operations.tsx +22 -15
  8. package/src/components/Zod.tsx +18 -118
  9. package/src/components/ZodMini.tsx +41 -0
  10. package/src/constants.ts +5 -0
  11. package/src/generators/zodGenerator.tsx +165 -158
  12. package/src/generators/zodGeneratorLegacy.tsx +401 -0
  13. package/src/index.ts +11 -1
  14. package/src/plugin.ts +105 -129
  15. package/src/presets.ts +25 -0
  16. package/src/printers/printerZod.ts +271 -0
  17. package/src/printers/printerZodMini.ts +246 -0
  18. package/src/resolvers/resolverZod.ts +71 -0
  19. package/src/resolvers/resolverZodLegacy.ts +60 -0
  20. package/src/types.ts +121 -92
  21. package/src/utils.ts +248 -0
  22. package/dist/components-DW4Q2yVq.js +0 -868
  23. package/dist/components-DW4Q2yVq.js.map +0 -1
  24. package/dist/components-qFUGs0vU.cjs +0 -916
  25. package/dist/components-qFUGs0vU.cjs.map +0 -1
  26. package/dist/components.cjs +0 -4
  27. package/dist/components.d.ts +0 -56
  28. package/dist/components.js +0 -2
  29. package/dist/generators-C9BCTLXg.cjs +0 -301
  30. package/dist/generators-C9BCTLXg.cjs.map +0 -1
  31. package/dist/generators-maqx12yN.js +0 -290
  32. package/dist/generators-maqx12yN.js.map +0 -1
  33. package/dist/generators.cjs +0 -4
  34. package/dist/generators.d.ts +0 -12
  35. package/dist/generators.js +0 -2
  36. package/dist/templates/ToZod.source.cjs +0 -7
  37. package/dist/templates/ToZod.source.cjs.map +0 -1
  38. package/dist/templates/ToZod.source.d.ts +0 -7
  39. package/dist/templates/ToZod.source.js +0 -6
  40. package/dist/templates/ToZod.source.js.map +0 -1
  41. package/dist/types-CClg-ikj.d.ts +0 -172
  42. package/src/components/index.ts +0 -2
  43. package/src/generators/index.ts +0 -2
  44. package/src/generators/operationsGenerator.tsx +0 -50
  45. package/src/parser.ts +0 -952
  46. package/src/templates/ToZod.source.ts +0 -4
  47. package/templates/ToZod.ts +0 -61
package/src/types.ts CHANGED
@@ -1,18 +1,90 @@
1
- import type { Output, PluginFactoryOptions, ResolveNameParams, UserGroup } from '@kubb/core'
2
- import type { contentType, Oas, SchemaObject } from '@kubb/oas'
3
- import type { Exclude, Include, Override, ResolvePathOptions, Schema } from '@kubb/plugin-oas'
4
- import type { Generator } from '@kubb/plugin-oas/generators'
1
+ import type { OperationParamsResolver } from '@kubb/ast'
2
+ import type { OperationNode, ParameterNode, SchemaNode, StatusCode, Visitor } from '@kubb/ast/types'
3
+ import type {
4
+ CompatibilityPreset,
5
+ Exclude,
6
+ Generator,
7
+ Group,
8
+ Include,
9
+ Output,
10
+ Override,
11
+ PluginFactoryOptions,
12
+ ResolvePathOptions,
13
+ Resolver,
14
+ UserGroup,
15
+ } from '@kubb/core'
16
+
17
+ /**
18
+ * The concrete resolver type for `@kubb/plugin-zod`.
19
+ * Extends the base `Resolver` with zod-specific naming helpers.
20
+ */
21
+ export type ResolverZod = Resolver &
22
+ OperationParamsResolver & {
23
+ /**
24
+ * Resolves a camelCase schema function name with a `Schema` suffix.
25
+ */
26
+ resolveName(name: string): string
27
+ /**
28
+ * Resolves the name for a `z.infer<typeof ...>` type export from an already-resolved function name.
29
+ *
30
+ * @example
31
+ * resolver.resolveInferName('petSchema') // → 'PetSchema'
32
+ * resolver.resolveInferName('addPet200Schema') // → 'AddPet200Schema'
33
+ */
34
+ resolveInferName(name: string): string
35
+ /**
36
+ * Resolves a PascalCase path/file name for the generated output.
37
+ */
38
+ resolvePathName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string
39
+ /**
40
+ * Resolves the name for an operation response by status code.
41
+ *
42
+ * @example
43
+ * resolver.resolveResponseStatusName(node, 200) // → 'listPetsStatus200Schema'
44
+ */
45
+ resolveResponseStatusName(node: OperationNode, statusCode: StatusCode): string
46
+ /**
47
+ * Resolves the name for the collection of all operation responses.
48
+ *
49
+ * @example
50
+ * resolver.resolveResponsesName(node) // → 'listPetsResponsesSchema'
51
+ */
52
+ resolveResponsesName(node: OperationNode): string
53
+ /**
54
+ * Resolves the name for the union of all operation responses.
55
+ *
56
+ * @example
57
+ * resolver.resolveResponseName(node) // → 'listPetsResponseSchema'
58
+ */
59
+ resolveResponseName(node: OperationNode): string
60
+ /**
61
+ * Resolves the name for an operation's grouped path parameters type.
62
+ *
63
+ * @example
64
+ * resolver.resolvePathParamsName(node, param) // → 'deletePetPathPetIdSchema'
65
+ */
66
+ resolvePathParamsName(node: OperationNode, param: ParameterNode): string
67
+ /**
68
+ * Resolves the name for an operation's grouped query parameters type.
69
+ *
70
+ * @example
71
+ * resolver.resolveQueryParamsName(node, param) // → 'findPetsByStatusQueryStatusSchema'
72
+ */
73
+ resolveQueryParamsName(node: OperationNode, param: ParameterNode): string
74
+ /**
75
+ * Resolves the name for an operation's grouped header parameters type.
76
+ *
77
+ * @example
78
+ * resolver.resolveHeaderParamsName(node, param) // → 'deletePetHeaderApiKeySchema'
79
+ */
80
+ resolveHeaderParamsName(node: OperationNode, param: ParameterNode): string
81
+ }
5
82
 
6
83
  export type Options = {
7
84
  /**
8
85
  * @default 'zod'
9
86
  */
10
- output?: Output<Oas>
11
- /**
12
- * Define which contentType should be used.
13
- * By default, the first JSON valid mediaType is used
14
- */
15
- contentType?: contentType
87
+ output?: Output
16
88
  /**
17
89
  * Group the Zod schemas based on the provided name.
18
90
  */
@@ -36,8 +108,7 @@ export type Options = {
36
108
  * Path is used as-is; relative paths are based on the generated file location.
37
109
  * @default 'zod'
38
110
  */
39
- importPath?: string
40
-
111
+ importPath?: 'zod' | 'zod/mini' | (string & {})
41
112
  /**
42
113
  * Choose to use date or datetime as JavaScript Date instead of string.
43
114
  * - false falls back to a simple z.string() format.
@@ -46,33 +117,8 @@ export type Options = {
46
117
  * - 'stringLocal' uses z.string().datetime({ local: true }) for local datetime validation.
47
118
  * - 'date' uses z.date() for JavaScript Date objects.
48
119
  * @default 'string'
49
- * @note 'stringOffset' will become the default in Kubb v3.
50
120
  */
51
121
  dateType?: false | 'string' | 'stringOffset' | 'stringLocal' | 'date'
52
- /**
53
- * Choose to use `number` or `bigint` for integer fields with `int64` format.
54
- * - 'number' uses the JavaScript `number` type (matches JSON.parse() runtime behavior).
55
- * - 'bigint' uses the JavaScript `bigint` type (accurate for values exceeding Number.MAX_SAFE_INTEGER).
56
- * @note in v5 of Kubb 'bigint' will become the default to better align with OpenAPI's int64 specification.
57
- * @default 'number'
58
- */
59
- integerType?: 'number' | 'bigint'
60
- /**
61
- * Which type to use when the Swagger/OpenAPI file is not providing more information.
62
- * - 'any' allows any value.
63
- * - 'unknown' requires type narrowing before use.
64
- * - 'void' represents no value.
65
- * @default 'any'
66
- */
67
- unknownType?: 'any' | 'unknown' | 'void'
68
- /**
69
- * Which type to use for empty schema values.
70
- * - 'any' allows any value.
71
- * - 'unknown' requires type narrowing before use.
72
- * - 'void' represents no value.
73
- * @default `unknownType`
74
- */
75
- emptySchemaType?: 'any' | 'unknown' | 'void'
76
122
  /**
77
123
  * Use TypeScript(`@kubb/plugin-ts`) to add type annotation.
78
124
  */
@@ -82,91 +128,74 @@ export type Options = {
82
128
  */
83
129
  inferred?: boolean
84
130
  /**
85
- * Use of z.coerce.string() instead of z.string()
86
- * can also be an object to enable coercion for dates, strings, and numbers
131
+ * Use of z.coerce.string() instead of z.string().
132
+ * Can also be an object to enable coercion for dates, strings, and numbers.
87
133
  */
88
- coercion?:
89
- | boolean
90
- | {
91
- dates?: boolean
92
- strings?: boolean
93
- numbers?: boolean
94
- }
95
- operations?: boolean
96
- mapper?: Record<string, string>
97
- transformers?: {
98
- /**
99
- * Customize the names based on the type that is provided by the plugin.
100
- */
101
- name?: (name: ResolveNameParams['name'], type?: ResolveNameParams['type']) => string
102
- /**
103
- * Receive schema and baseName(propertyName) and return FakerMeta array
104
- * TODO TODO add docs
105
- * @beta
106
- */
107
- schema?: (
108
- props: {
109
- schema: SchemaObject | null
110
- name: string | null
111
- parentName: string | null
112
- },
113
- defaultSchemas: Schema[],
114
- ) => Schema[] | undefined
115
- }
134
+ coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
116
135
  /**
117
- * Which version of Zod should be used.
118
- * - '3' uses Zod v3.x syntax and features.
119
- * - '4' uses Zod v4.x syntax and features.
120
- * @default '3'
136
+ * Generate operation-level schemas (grouped by operationId).
121
137
  */
122
- version?: '3' | '4'
138
+ operations?: boolean
123
139
  /**
124
140
  * Which Zod GUID validator to use for OpenAPI `format: uuid`.
125
141
  * - 'uuid' uses UUID validation.
126
- * - 'guid' uses GUID validation (Zod v4 only).
142
+ * - 'guid' uses GUID validation.
127
143
  * @default 'uuid'
128
144
  */
129
145
  guidType?: 'uuid' | 'guid'
130
146
  /**
131
147
  * Use Zod Mini's functional API for better tree-shaking support.
132
- * When enabled, generates functional syntax (e.g., `z.optional(z.string())`) instead of chainable methods (e.g., `z.string().optional()`).
133
- * Requires Zod v4 or later. When `mini: true`, `version` is set to '4' and `importPath` will default to 'zod/mini'.
148
+ * When enabled, generates functional syntax (e.g., `z.optional(z.string())`)
149
+ * instead of chainable methods (e.g., `z.string().optional()`).
150
+ * When `mini: true`, `importPath` will default to 'zod/mini'.
134
151
  * @default false
135
152
  */
136
153
  mini?: boolean
137
154
  /**
138
- * Callback function to wrap the output of the generated zod schema
155
+ * Callback function to wrap the output of the generated zod schema.
139
156
  *
140
- * This is useful for edge case scenarios where you might leverage something like `z.object({ ... }).openapi({ example: { some: "complex-example" }})`
141
- * or `extendApi(z.object({ ... }), { example: { some: "complex-example", ...otherOpenApiProperties }})`
142
- * while going from openapi -> zod -> openapi
157
+ * Useful for edge cases like adding `.openapi()` metadata or wrapping
158
+ * schemas with extension helpers (openapi -> zod -> openapi round-trips).
159
+ */
160
+ wrapOutput?: (arg: { output: string; schema: SchemaNode }) => string | undefined
161
+ /**
162
+ * How to style your params, by default no casing is applied
163
+ * - 'camelcase' uses camelCase for pathParams, queryParams and headerParams property names
164
+ * @default undefined
143
165
  */
144
- wrapOutput?: (arg: { output: string; schema: SchemaObject }) => string | undefined
166
+ paramsCasing?: 'camelcase'
145
167
  /**
146
- * Define some generators next to the zod generators
168
+ * Define additional generators next to the zod generators.
147
169
  */
148
170
  generators?: Array<Generator<PluginZod>>
171
+ /**
172
+ * Compatibility preset to ease migration from previous Kubb versions.
173
+ */
174
+ compatibilityPreset?: CompatibilityPreset
175
+ /**
176
+ * Custom resolver instances for zod-specific name resolution.
177
+ */
178
+ resolvers?: Array<ResolverZod>
179
+ /**
180
+ * AST visitor transformers applied during code generation.
181
+ */
182
+ transformers?: Array<Visitor>
149
183
  }
150
184
 
151
185
  type ResolvedOptions = {
152
- output: Output<Oas>
153
- group: Options['group']
154
- override: NonNullable<Options['override']>
155
- transformers: NonNullable<Options['transformers']>
186
+ output: Output
187
+ group: Group | undefined
156
188
  dateType: NonNullable<Options['dateType']>
157
- integerType: NonNullable<Options['integerType']>
158
- unknownType: NonNullable<Options['unknownType']>
159
- emptySchemaType: NonNullable<Options['emptySchemaType']>
160
189
  typed: NonNullable<Options['typed']>
161
190
  inferred: NonNullable<Options['inferred']>
162
- mapper: NonNullable<Options['mapper']>
163
191
  importPath: NonNullable<Options['importPath']>
164
192
  coercion: NonNullable<Options['coercion']>
165
193
  operations: NonNullable<Options['operations']>
166
- wrapOutput: Options['wrapOutput']
167
- version: NonNullable<Options['version']>
168
194
  guidType: NonNullable<Options['guidType']>
169
195
  mini: NonNullable<Options['mini']>
196
+ wrapOutput: Options['wrapOutput']
197
+ paramsCasing: Options['paramsCasing']
198
+ transformers: Array<Visitor>
170
199
  }
171
200
 
172
- export type PluginZod = PluginFactoryOptions<'plugin-zod', Options, ResolvedOptions, never, ResolvePathOptions>
201
+ export type PluginZod = PluginFactoryOptions<'plugin-zod', Options, ResolvedOptions, never, ResolvePathOptions, ResolverZod>
package/src/utils.ts ADDED
@@ -0,0 +1,248 @@
1
+ import { stringify, toRegExpString } from '@internals/utils'
2
+ import { createProperty, createSchema, extractRefName } from '@kubb/ast'
3
+ import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
4
+ import type { PluginZod, ResolverZod } from './types.ts'
5
+
6
+ /**
7
+ * Returns `true` when the given coercion option enables coercion for the specified type.
8
+ */
9
+ export function shouldCoerce(coercion: PluginZod['resolvedOptions']['coercion'] | undefined, type: 'dates' | 'strings' | 'numbers'): boolean {
10
+ if (coercion === undefined || coercion === false) return false
11
+ if (coercion === true) return true
12
+
13
+ return !!coercion[type]
14
+ }
15
+
16
+ /**
17
+ * Collects all resolved schema names for an operation's parameters and responses
18
+ * into a single lookup object, useful for building imports and type references.
19
+ */
20
+ export function buildSchemaNames(node: OperationNode, { params, resolver }: { params: Array<ParameterNode>; resolver: ResolverZod }) {
21
+ const pathParam = params.find((p) => p.in === 'path')
22
+ const queryParam = params.find((p) => p.in === 'query')
23
+ const headerParam = params.find((p) => p.in === 'header')
24
+
25
+ const responses: Record<number | string, string> = {}
26
+ const errors: Record<number | string, string> = {}
27
+
28
+ for (const res of node.responses) {
29
+ const name = resolver.resolveResponseStatusName(node, res.statusCode)
30
+ const statusNum = Number(res.statusCode)
31
+
32
+ if (!Number.isNaN(statusNum)) {
33
+ responses[statusNum] = name
34
+ if (statusNum >= 400) {
35
+ errors[statusNum] = name
36
+ }
37
+ }
38
+ }
39
+
40
+ responses['default'] = resolver.resolveResponseName(node)
41
+
42
+ return {
43
+ request: node.requestBody?.schema ? resolver.resolveDataName(node) : undefined,
44
+ parameters: {
45
+ path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : undefined,
46
+ query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : undefined,
47
+ header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : undefined,
48
+ },
49
+ responses,
50
+ errors,
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Format a default value as a code-level literal.
56
+ * Objects become `{}`, primitives become their string representation, strings are quoted.
57
+ */
58
+ export function formatDefault(value: unknown): string {
59
+ if (typeof value === 'string') return stringify(value)
60
+ if (typeof value === 'object' && value !== null) return '{}'
61
+
62
+ return String(value ?? '')
63
+ }
64
+
65
+ /**
66
+ * Format a primitive enum/literal value.
67
+ * Strings are quoted; numbers and booleans are emitted raw.
68
+ */
69
+ export function formatLiteral(v: string | number | boolean): string {
70
+ if (typeof v === 'string') return stringify(v)
71
+
72
+ return String(v)
73
+ }
74
+
75
+ export type NumericConstraints = {
76
+ min?: number
77
+ max?: number
78
+ exclusiveMinimum?: number
79
+ exclusiveMaximum?: number
80
+ multipleOf?: number
81
+ }
82
+
83
+ export type LengthConstraints = {
84
+ min?: number
85
+ max?: number
86
+ pattern?: string
87
+ }
88
+
89
+ export type ModifierOptions = {
90
+ value: string
91
+ nullable?: boolean
92
+ optional?: boolean
93
+ nullish?: boolean
94
+ defaultValue?: unknown
95
+ description?: string
96
+ }
97
+
98
+ /**
99
+ * Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
100
+ * using the standard chainable Zod v4 API.
101
+ */
102
+ export function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }: NumericConstraints): string {
103
+ return [
104
+ min !== undefined ? `.min(${min})` : '',
105
+ max !== undefined ? `.max(${max})` : '',
106
+ exclusiveMinimum !== undefined ? `.gt(${exclusiveMinimum})` : '',
107
+ exclusiveMaximum !== undefined ? `.lt(${exclusiveMaximum})` : '',
108
+ multipleOf !== undefined ? `.multipleOf(${multipleOf})` : '',
109
+ ].join('')
110
+ }
111
+
112
+ /**
113
+ * Build `.min()` / `.max()` / `.regex()` chains for strings/arrays
114
+ * using the standard chainable Zod v4 API.
115
+ */
116
+ export function lengthConstraints({ min, max, pattern }: LengthConstraints): string {
117
+ return [
118
+ min !== undefined ? `.min(${min})` : '',
119
+ max !== undefined ? `.max(${max})` : '',
120
+ pattern !== undefined ? `.regex(${toRegExpString(pattern, null)})` : '',
121
+ ].join('')
122
+ }
123
+
124
+ /**
125
+ * Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
126
+ */
127
+ export function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }: NumericConstraints): string {
128
+ const checks: string[] = []
129
+ if (min !== undefined) checks.push(`z.minimum(${min})`)
130
+ if (max !== undefined) checks.push(`z.maximum(${max})`)
131
+ if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
132
+ if (exclusiveMaximum !== undefined) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`)
133
+ if (multipleOf !== undefined) checks.push(`z.multipleOf(${multipleOf})`)
134
+ return checks.length ? `.check(${checks.join(', ')})` : ''
135
+ }
136
+
137
+ /**
138
+ * Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
139
+ */
140
+ export function lengthChecksMini({ min, max, pattern }: LengthConstraints): string {
141
+ const checks: string[] = []
142
+ if (min !== undefined) checks.push(`z.minLength(${min})`)
143
+ if (max !== undefined) checks.push(`z.maxLength(${max})`)
144
+ if (pattern !== undefined) checks.push(`z.regex(${toRegExpString(pattern, null)})`)
145
+ return checks.length ? `.check(${checks.join(', ')})` : ''
146
+ }
147
+
148
+ /**
149
+ * Apply nullable / optional / nullish modifiers and an optional `.describe()` call
150
+ * to a schema value string using the chainable Zod v4 API.
151
+ */
152
+ export function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }: ModifierOptions): string {
153
+ let result = value
154
+ if (nullish || (nullable && optional)) {
155
+ result = `${result}.nullish()`
156
+ } else if (optional) {
157
+ result = `${result}.optional()`
158
+ } else if (nullable) {
159
+ result = `${result}.nullable()`
160
+ }
161
+ if (defaultValue !== undefined) {
162
+ result = `${result}.default(${formatDefault(defaultValue)})`
163
+ }
164
+ if (description) {
165
+ result = `${result}.describe(${stringify(description)})`
166
+ }
167
+ return result
168
+ }
169
+
170
+ /**
171
+ * Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
172
+ * (`z.nullable()`, `z.optional()`, `z.nullish()`).
173
+ */
174
+ export function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }: Omit<ModifierOptions, 'description'>): string {
175
+ let result = value
176
+ if (nullish) {
177
+ result = `z.nullish(${result})`
178
+ } else {
179
+ if (nullable) {
180
+ result = `z.nullable(${result})`
181
+ }
182
+ if (optional) {
183
+ result = `z.optional(${result})`
184
+ }
185
+ }
186
+ if (defaultValue !== undefined) {
187
+ result = `z._default(${result}, ${formatDefault(defaultValue)})`
188
+ }
189
+ return result
190
+ }
191
+
192
+ /**
193
+ * Returns true when the schema tree contains a self-referential `$ref`
194
+ * whose resolved name matches `schemaName`.
195
+ *
196
+ * A `visited` set prevents infinite recursion on circular schema graphs.
197
+ */
198
+ export function containsSelfRef(
199
+ node: SchemaNode,
200
+ { schemaName, resolver, visited = new Set() }: { schemaName: string; resolver: ResolverZod | undefined; visited?: Set<SchemaNode> },
201
+ ): boolean {
202
+ if (visited.has(node)) return false
203
+ visited.add(node)
204
+
205
+ if (node.type === 'ref' && node.ref) {
206
+ const rawName = extractRefName(node.ref) ?? node.name
207
+ const resolved = rawName ? (resolver?.default(rawName, 'function') ?? rawName) : node.name
208
+ return resolved === schemaName
209
+ }
210
+ if (node.type === 'object') {
211
+ if (node.properties?.some((p) => containsSelfRef(p.schema, { schemaName, resolver, visited }))) return true
212
+ if (node.additionalProperties && node.additionalProperties !== true) {
213
+ return containsSelfRef(node.additionalProperties, { schemaName, resolver, visited })
214
+ }
215
+ return false
216
+ }
217
+ if (node.type === 'array' || node.type === 'tuple') {
218
+ return node.items?.some((item) => containsSelfRef(item, { schemaName, resolver, visited })) ?? false
219
+ }
220
+ if (node.type === 'union' || node.type === 'intersection') {
221
+ return node.members?.some((m) => containsSelfRef(m, { schemaName, resolver, visited })) ?? false
222
+ }
223
+ return false
224
+ }
225
+
226
+ type BuildGroupedParamsSchemaOptions = {
227
+ params: Array<ParameterNode>
228
+ optional?: boolean
229
+ }
230
+
231
+ /**
232
+ * Builds an `object` schema node grouping the given parameter nodes.
233
+ * The `primitive: 'object'` marker ensures the Zod printer emits `z.object(…)` rather than a record.
234
+ */
235
+ export function buildGroupedParamsSchema({ params, optional }: BuildGroupedParamsSchemaOptions): SchemaNode {
236
+ return createSchema({
237
+ type: 'object',
238
+ optional,
239
+ primitive: 'object',
240
+ properties: params.map((param) =>
241
+ createProperty({
242
+ name: param.name,
243
+ required: param.required,
244
+ schema: param.schema,
245
+ }),
246
+ ),
247
+ })
248
+ }