@kubb/plugin-ts 5.0.0-alpha.24 → 5.0.0-alpha.25

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.
@@ -1,6 +1,4 @@
1
- import { jsStringEscape, stringify } from '@internals/utils'
2
- import { isStringType, narrowSchema, schemaTypes } from '@kubb/ast'
3
- import type { ArraySchemaNode, SchemaNode } from '@kubb/ast/types'
1
+ import { extractRefName, isStringType, narrowSchema, schemaTypes, syncSchemaRef } from '@kubb/ast'
4
2
  import type { PrinterFactoryOptions } from '@kubb/core'
5
3
  import { definePrinter } from '@kubb/core'
6
4
  import { safePrint } from '@kubb/fabric-core/parsers/typescript'
@@ -8,6 +6,7 @@ import type ts from 'typescript'
8
6
  import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
9
7
  import * as factory from '../factory.ts'
10
8
  import type { PluginTs, ResolverTs } from '../types.ts'
9
+ import { buildPropertyJSDocComments } from '../utils.ts'
11
10
 
12
11
  type TsOptions = {
13
12
  /**
@@ -65,140 +64,6 @@ type TsOptions = {
65
64
  */
66
65
  type TsPrinter = PrinterFactoryOptions<'typescript', TsOptions, ts.TypeNode, string>
67
66
 
68
- /**
69
- * Converts a primitive const value to a TypeScript literal type node.
70
- * Handles negative numbers via a prefix unary expression.
71
- */
72
- function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
73
- if (format === 'boolean') {
74
- return factory.createLiteralTypeNode(value === true ? factory.createTrue() : factory.createFalse())
75
- }
76
- if (format === 'number' && typeof value === 'number') {
77
- if (value < 0) {
78
- return factory.createLiteralTypeNode(factory.createPrefixUnaryExpression(factory.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value))))
79
- }
80
- return factory.createLiteralTypeNode(factory.createNumericLiteral(value))
81
- }
82
- return factory.createLiteralTypeNode(factory.createStringLiteral(String(value)))
83
- }
84
-
85
- /**
86
- * Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
87
- */
88
- function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
89
- return node.representation === 'date' ? factory.createTypeReferenceNode(factory.createIdentifier('Date')) : factory.keywordTypeNodes.string
90
- }
91
-
92
- /**
93
- * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
94
- */
95
- function buildMemberNodes(members: Array<SchemaNode> | undefined, print: (node: SchemaNode) => ts.TypeNode | null | undefined): Array<ts.TypeNode> {
96
- return (members ?? []).map(print).filter(Boolean)
97
- }
98
-
99
- /**
100
- * Builds a TypeScript tuple type node from an array schema's `items`,
101
- * applying min/max slice and optional/rest element rules.
102
- */
103
- function buildTupleNode(node: ArraySchemaNode, print: (node: SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
104
- let items = (node.items ?? []).map(print).filter(Boolean)
105
-
106
- const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
107
- const { min, max } = node
108
-
109
- if (max !== undefined) {
110
- items = items.slice(0, max)
111
- if (items.length < max && restNode) {
112
- items = [...items, ...Array(max - items.length).fill(restNode)]
113
- }
114
- }
115
-
116
- if (min !== undefined) {
117
- items = items.map((item, i) => (i >= min ? factory.createOptionalTypeNode(item) : item))
118
- }
119
-
120
- if (max === undefined && restNode) {
121
- items.push(factory.createRestTypeNode(factory.createArrayTypeNode(restNode)))
122
- }
123
-
124
- return factory.createTupleTypeNode(items)
125
- }
126
-
127
- /**
128
- * Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
129
- */
130
- function buildPropertyType(schema: SchemaNode, baseType: ts.TypeNode, optionalType: TsOptions['optionalType']): ts.TypeNode {
131
- const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
132
-
133
- let type = baseType
134
-
135
- if (schema.nullable) {
136
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
137
- }
138
-
139
- if ((schema.nullish || schema.optional) && addsUndefined) {
140
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
141
- }
142
-
143
- return type
144
- }
145
-
146
- /**
147
- * Collects JSDoc annotation strings (description, deprecated, min/max, pattern, default, example, type) for a schema node.
148
- */
149
- function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
150
- const isArray = schema.type === 'array'
151
-
152
- return [
153
- 'description' in schema && schema.description ? `@description ${jsStringEscape(schema.description)}` : undefined,
154
- 'deprecated' in schema && schema.deprecated ? '@deprecated' : undefined,
155
- // minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
156
- !isArray && 'min' in schema && schema.min !== undefined ? `@minLength ${schema.min}` : undefined,
157
- !isArray && 'max' in schema && schema.max !== undefined ? `@maxLength ${schema.max}` : undefined,
158
- 'pattern' in schema && schema.pattern ? `@pattern ${schema.pattern}` : undefined,
159
- 'default' in schema && schema.default !== undefined
160
- ? `@default ${'primitive' in schema && schema.primitive === 'string' ? stringify(schema.default as string) : schema.default}`
161
- : undefined,
162
- 'example' in schema && schema.example !== undefined ? `@example ${schema.example}` : undefined,
163
- 'primitive' in schema && schema.primitive
164
- ? [`@type ${schema.primitive || 'unknown'}`, 'optional' in schema && schema.optional ? ' | undefined' : undefined].filter(Boolean).join('')
165
- : undefined,
166
- ]
167
- }
168
-
169
- /**
170
- * Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
171
- */
172
- function buildIndexSignatures(
173
- node: { additionalProperties?: SchemaNode | boolean; patternProperties?: Record<string, SchemaNode> },
174
- propertyCount: number,
175
- print: (node: SchemaNode) => ts.TypeNode | null | undefined,
176
- ): Array<ts.TypeElement> {
177
- const elements: Array<ts.TypeElement> = []
178
-
179
- if (node.additionalProperties && node.additionalProperties !== true) {
180
- const additionalType = print(node.additionalProperties) ?? factory.keywordTypeNodes.unknown
181
-
182
- elements.push(factory.createIndexSignature(propertyCount > 0 ? factory.keywordTypeNodes.unknown : additionalType))
183
- } else if (node.additionalProperties === true) {
184
- elements.push(factory.createIndexSignature(factory.keywordTypeNodes.unknown))
185
- }
186
-
187
- if (node.patternProperties) {
188
- const first = Object.values(node.patternProperties)[0]
189
- if (first) {
190
- let patternType = print(first) ?? factory.keywordTypeNodes.unknown
191
-
192
- if (first.nullable) {
193
- patternType = factory.createUnionDeclaration({ nodes: [patternType, factory.keywordTypeNodes.null] })
194
- }
195
- elements.push(factory.createIndexSignature(patternType))
196
- }
197
- }
198
-
199
- return elements
200
- }
201
-
202
67
  /**
203
68
  * TypeScript type printer built with `definePrinter`.
204
69
  *
@@ -245,12 +110,14 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
245
110
  }
246
111
  return factory.keywordTypeNodes.string
247
112
  },
113
+ ipv4: () => factory.keywordTypeNodes.string,
114
+ ipv6: () => factory.keywordTypeNodes.string,
248
115
  datetime: () => factory.keywordTypeNodes.string,
249
116
  number: () => factory.keywordTypeNodes.number,
250
117
  integer: () => factory.keywordTypeNodes.number,
251
118
  bigint: () => factory.keywordTypeNodes.bigint,
252
- date: dateOrStringNode,
253
- time: dateOrStringNode,
119
+ date: factory.dateOrStringNode,
120
+ time: factory.dateOrStringNode,
254
121
  ref(node) {
255
122
  if (!node.name) {
256
123
  return undefined
@@ -259,7 +126,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
259
126
  // Use the canonical name from the $ref path — node.name may have been overridden
260
127
  // (e.g. by single-member allOf flatten using the property-derived child name).
261
128
  // Inline refs (without $ref) from utils already carry resolved type names.
262
- const refName = node.ref ? (node.ref.split('/').at(-1) ?? node.name) : node.name
129
+ const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
263
130
 
264
131
  // When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
265
132
  // so the reference matches what the enum file actually exports.
@@ -267,7 +134,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
267
134
  node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName)
268
135
 
269
136
  const name = isEnumRef
270
- ? this.options.resolver.resolveEnumKeyName({ name: refName } as SchemaNode, this.options.enumTypeSuffix!)
137
+ ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix!)
271
138
  : node.ref
272
139
  ? this.options.resolver.default(refName, 'type')
273
140
  : refName
@@ -279,8 +146,8 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
279
146
 
280
147
  if (this.options.enumType === 'inlineLiteral' || !node.name) {
281
148
  const literalNodes = values
282
- .filter((v): v is string | number | boolean => v !== null)
283
- .map((value) => constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
149
+ .filter((v): v is string | number | boolean => v !== null && v !== undefined)
150
+ .map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
284
151
  .filter(Boolean)
285
152
 
286
153
  return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
@@ -288,7 +155,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
288
155
 
289
156
  const resolvedName =
290
157
  ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix
291
- ? this.options.resolver.resolveEnumKeyName(node as unknown as SchemaNode, this.options.enumTypeSuffix)
158
+ ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix)
292
159
  : this.options.resolver.default(node.name, 'type')
293
160
 
294
161
  return factory.createTypeReferenceNode(resolvedName, undefined)
@@ -319,10 +186,10 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
319
186
  return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
320
187
  }
321
188
 
322
- return factory.createUnionDeclaration({ withParentheses: true, nodes: buildMemberNodes(members, this.transform) }) ?? undefined
189
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(members, this.transform) }) ?? undefined
323
190
  },
324
191
  intersection(node) {
325
- return factory.createIntersectionDeclaration({ withParentheses: true, nodes: buildMemberNodes(node.members, this.transform) }) ?? undefined
192
+ return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ?? undefined
326
193
  },
327
194
  array(node) {
328
195
  const itemNodes = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
@@ -330,7 +197,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
330
197
  return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? undefined
331
198
  },
332
199
  tuple(node) {
333
- return buildTupleNode(node, this.transform)
200
+ return factory.buildTupleNode(node, this.transform)
334
201
  },
335
202
  object(node) {
336
203
  const { transform, options } = this
@@ -339,19 +206,20 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
339
206
 
340
207
  const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
341
208
  const baseType = transform(prop.schema) ?? factory.keywordTypeNodes.unknown
342
- const type = buildPropertyType(prop.schema, baseType, options.optionalType)
209
+ const type = factory.buildPropertyType(prop.schema, baseType, options.optionalType)
210
+ const propMeta = syncSchemaRef(prop.schema)
343
211
 
344
212
  const propertyNode = factory.createPropertySignature({
345
213
  questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
346
214
  name: prop.name,
347
215
  type,
348
- readOnly: prop.schema.readOnly,
216
+ readOnly: propMeta?.readOnly,
349
217
  })
350
218
 
351
219
  return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
352
220
  })
353
221
 
354
- const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)]
222
+ const allElements = [...propertyNodes, ...factory.buildIndexSignatures(node, propertyNodes.length, transform)]
355
223
 
356
224
  if (!allElements.length) {
357
225
  return factory.keywordTypeNodes.object
@@ -361,50 +229,50 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
361
229
  },
362
230
  },
363
231
  print(node) {
364
- let type = this.transform(node)
232
+ const { name, syntaxType = 'type', description, keysToOmit } = this.options
365
233
 
366
- if (!type) {
367
- return null
368
- }
234
+ let base = this.transform(node)
235
+ if (!base) return null
236
+
237
+ // For ref nodes, structural metadata lives on node.schema rather than the ref node itself.
238
+ const meta = syncSchemaRef(node)
369
239
 
370
- // Apply top-level nullable / optional union modifiers.
371
- if (node.nullable) {
372
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
240
+ // Without name, apply modifiers inline and return.
241
+ if (!name) {
242
+ if (meta.nullable) {
243
+ base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.null] })
244
+ }
245
+ if ((meta.nullish || meta.optional) && addsUndefined) {
246
+ base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.undefined] })
247
+ }
248
+ return safePrint(base)
373
249
  }
374
250
 
375
- if ((node.nullish || node.optional) && addsUndefined) {
376
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
251
+ // When keysToOmit is present, wrap with Omit first, then apply nullable/optional
252
+ // modifiers so they are not swallowed by NonNullable inside createOmitDeclaration.
253
+ let inner: ts.TypeNode = keysToOmit?.length ? factory.createOmitDeclaration({ keys: keysToOmit, type: base, nonNullable: true }) : base
254
+
255
+ if (meta.nullable) {
256
+ inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.null] })
377
257
  }
378
258
 
379
- // Without name, return the type node as-is (no declaration wrapping).
380
- const { name, syntaxType = 'type', description, keysToOmit } = this.options
381
- if (!name) {
382
- return safePrint(type)
259
+ // For named type declarations (type aliases), optional/nullish always produces | undefined
260
+ // regardless of optionalType the questionToken ? modifier only applies to object properties.
261
+ if (meta.nullish || meta.optional) {
262
+ inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.undefined] })
383
263
  }
384
264
 
385
- const useTypeGeneration = syntaxType === 'type' || type.kind === factory.syntaxKind.union || !!keysToOmit?.length
265
+ const useTypeGeneration = syntaxType === 'type' || inner.kind === factory.syntaxKind.union || !!keysToOmit?.length
386
266
 
387
267
  const typeNode = factory.createTypeDeclaration({
388
268
  name,
389
269
  isExportable: true,
390
- type: keysToOmit?.length
391
- ? factory.createOmitDeclaration({
392
- keys: keysToOmit,
393
- type,
394
- nonNullable: true,
395
- })
396
- : type,
270
+ type: inner,
397
271
  syntax: useTypeGeneration ? 'type' : 'interface',
398
- comments: [
399
- node?.title ? jsStringEscape(node.title) : undefined,
400
- description ? `@description ${jsStringEscape(description)}` : undefined,
401
- node?.deprecated ? '@deprecated' : undefined,
402
- node && 'min' in node && node.min !== undefined ? `@minLength ${node.min}` : undefined,
403
- node && 'max' in node && node.max !== undefined ? `@maxLength ${node.max}` : undefined,
404
- node && 'pattern' in node && node.pattern ? `@pattern ${node.pattern}` : undefined,
405
- node?.default ? `@default ${node.default}` : undefined,
406
- node?.example ? `@example ${node.example}` : undefined,
407
- ],
272
+ comments: buildPropertyJSDocComments({
273
+ ...meta,
274
+ description,
275
+ }),
408
276
  })
409
277
 
410
278
  return safePrint(typeNode)
@@ -2,7 +2,7 @@ import { pascalCase } from '@internals/utils'
2
2
  import { defineResolver } from '@kubb/core'
3
3
  import type { PluginTs } from '../types.ts'
4
4
 
5
- function resolveName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string {
5
+ function toTypeName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string {
6
6
  return pascalCase(name, { isFile: type === 'file' })
7
7
  }
8
8
 
@@ -28,7 +28,7 @@ export const resolverTs = defineResolver<PluginTs>(() => {
28
28
  name: 'default',
29
29
  pluginName: 'plugin-ts',
30
30
  default(name, type) {
31
- return resolveName(name, type)
31
+ return toTypeName(name, type)
32
32
  },
33
33
  resolveName(name) {
34
34
  return this.default(name, 'function')
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OperationParamsResolver } from '@kubb/ast'
2
- import type { OperationNode, ParameterNode, SchemaNode, StatusCode, Visitor } from '@kubb/ast/types'
2
+ import type { OperationNode, ParameterNode, StatusCode, Visitor } from '@kubb/ast/types'
3
3
  import type {
4
4
  CompatibilityPreset,
5
5
  Exclude,
@@ -35,7 +35,9 @@ export type ResolverTs = Resolver &
35
35
  * resolver.resolvePathName('list pets', 'file') // → 'ListPets'
36
36
  */
37
37
  resolvePathName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string
38
- /** Resolves the request body type name (required on ResolverTs). */
38
+ /**
39
+ * Resolves the request body type name for an operation (required on ResolverTs).
40
+ */
39
41
  resolveDataName(node: OperationNode): string
40
42
 
41
43
  /**
@@ -76,7 +78,7 @@ export type ResolverTs = Resolver &
76
78
  * resolver.resolveEnumKeyName(node, 'Value') // → 'PetStatusValue'
77
79
  * resolver.resolveEnumKeyName(node, '') // → 'PetStatus'
78
80
  */
79
- resolveEnumKeyName(node: SchemaNode, enumTypeSuffix: string): string
81
+ resolveEnumKeyName(node: { name?: string | null }, enumTypeSuffix: string): string
80
82
  /**
81
83
  * Resolves the name for an operation's grouped path parameters type.
82
84
  *
@@ -169,7 +171,7 @@ type EnumTypeOptions =
169
171
  /**
170
172
  * Choose to use enum, asConst, asPascalConst, constEnum, literal, or inlineLiteral for enums.
171
173
  * - 'literal' generates literal union types.
172
- * - 'inlineLiteral' inlines enum values directly into the type (default in v5).
174
+ * - 'inlineLiteral' will inline enum values directly into the type (default in v5).
173
175
  * @default 'asConst'
174
176
  * @note In Kubb v5, 'inlineLiteral' becomes the default.
175
177
  */
package/src/utils.ts CHANGED
@@ -1,19 +1,47 @@
1
- import { createProperty, createSchema } from '@kubb/ast'
1
+ import { jsStringEscape, stringify } from '@internals/utils'
2
+ import { createProperty, createSchema, syncSchemaRef } from '@kubb/ast'
2
3
  import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
3
4
  import type { ResolverTs } from './types.ts'
4
5
 
6
+ /**
7
+ * Collects JSDoc annotation strings for a schema node.
8
+ *
9
+ * Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@default`, `@example`, `@type`.
10
+ * Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
11
+
12
+ */
13
+ export function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
14
+ const meta = syncSchemaRef(schema)
15
+
16
+ const isArray = meta?.primitive === 'array'
17
+
18
+ return [
19
+ meta && 'description' in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : undefined,
20
+ meta && 'deprecated' in meta && meta.deprecated ? '@deprecated' : undefined,
21
+ // minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
22
+ !isArray && meta && 'min' in meta && meta.min !== undefined ? `@minLength ${meta.min}` : undefined,
23
+ !isArray && meta && 'max' in meta && meta.max !== undefined ? `@maxLength ${meta.max}` : undefined,
24
+ meta && 'pattern' in meta && meta.pattern ? `@pattern ${meta.pattern}` : undefined,
25
+ meta && 'default' in meta && meta.default !== undefined
26
+ ? `@default ${'primitive' in meta && meta.primitive === 'string' ? stringify(meta.default as string) : meta.default}`
27
+ : undefined,
28
+ meta && 'example' in meta && meta.example !== undefined ? `@example ${meta.example}` : undefined,
29
+ meta && 'primitive' in meta && meta.primitive
30
+ ? [`@type ${meta.primitive}`, 'optional' in schema && schema.optional ? ' | undefined' : undefined].filter(Boolean).join('')
31
+ : undefined,
32
+ ].filter(Boolean)
33
+ }
34
+
5
35
  type BuildParamsSchemaOptions = {
6
36
  params: Array<ParameterNode>
7
- node: OperationNode
8
37
  resolver: ResolverTs
9
38
  }
10
39
 
11
40
  type BuildOperationSchemaOptions = {
12
- node: OperationNode
13
41
  resolver: ResolverTs
14
42
  }
15
43
 
16
- export function buildParams({ params, node, resolver }: BuildParamsSchemaOptions): SchemaNode {
44
+ export function buildParams(node: OperationNode, { params, resolver }: BuildParamsSchemaOptions): SchemaNode {
17
45
  return createSchema({
18
46
  type: 'object',
19
47
  properties: params.map((param) =>
@@ -29,7 +57,7 @@ export function buildParams({ params, node, resolver }: BuildParamsSchemaOptions
29
57
  })
30
58
  }
31
59
 
32
- export function buildData({ node, resolver }: BuildOperationSchemaOptions): SchemaNode {
60
+ export function buildData(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode {
33
61
  const pathParams = node.parameters.filter((p) => p.in === 'path')
34
62
  const queryParams = node.parameters.filter((p) => p.in === 'query')
35
63
  const headerParams = node.parameters.filter((p) => p.in === 'header')
@@ -42,26 +70,26 @@ export function buildData({ node, resolver }: BuildOperationSchemaOptions): Sche
42
70
  name: 'data',
43
71
  schema: node.requestBody?.schema
44
72
  ? createSchema({ type: 'ref', name: resolver.resolveDataName(node), optional: true })
45
- : createSchema({ type: 'never', optional: true }),
73
+ : createSchema({ type: 'never', primitive: undefined, optional: true }),
46
74
  }),
47
75
  createProperty({
48
76
  name: 'pathParams',
49
77
  required: pathParams.length > 0,
50
- schema: pathParams.length > 0 ? buildParams({ params: pathParams, node, resolver }) : createSchema({ type: 'never' }),
78
+ schema: pathParams.length > 0 ? buildParams(node, { params: pathParams, resolver }) : createSchema({ type: 'never', primitive: undefined }),
51
79
  }),
52
80
  createProperty({
53
81
  name: 'queryParams',
54
82
  schema:
55
83
  queryParams.length > 0
56
- ? createSchema({ ...buildParams({ params: queryParams, node, resolver }), optional: true })
57
- : createSchema({ type: 'never', optional: true }),
84
+ ? createSchema({ ...buildParams(node, { params: queryParams, resolver }), optional: true })
85
+ : createSchema({ type: 'never', primitive: undefined, optional: true }),
58
86
  }),
59
87
  createProperty({
60
88
  name: 'headerParams',
61
89
  schema:
62
90
  headerParams.length > 0
63
- ? createSchema({ ...buildParams({ params: headerParams, node, resolver }), optional: true })
64
- : createSchema({ type: 'never', optional: true }),
91
+ ? createSchema({ ...buildParams(node, { params: headerParams, resolver }), optional: true })
92
+ : createSchema({ type: 'never', primitive: undefined, optional: true }),
65
93
  }),
66
94
  createProperty({
67
95
  name: 'url',
@@ -72,7 +100,7 @@ export function buildData({ node, resolver }: BuildOperationSchemaOptions): Sche
72
100
  })
73
101
  }
74
102
 
75
- export function buildResponses({ node, resolver }: BuildOperationSchemaOptions): SchemaNode | null {
103
+ export function buildResponses(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode | null {
76
104
  if (node.responses.length === 0) {
77
105
  return null
78
106
  }
@@ -89,7 +117,7 @@ export function buildResponses({ node, resolver }: BuildOperationSchemaOptions):
89
117
  })
90
118
  }
91
119
 
92
- export function buildResponseUnion({ node, resolver }: BuildOperationSchemaOptions): SchemaNode | null {
120
+ export function buildResponseUnion(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode | null {
93
121
  const responsesWithSchema = node.responses.filter((res) => res.schema)
94
122
 
95
123
  if (responsesWithSchema.length === 0) {