@kubb/ast 5.0.0-beta.61 → 5.0.0-beta.63

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 (95) hide show
  1. package/dist/{defineMacro-BLIR6k-j.d.ts → defineMacro-BATi7xoC.d.ts} +11 -20
  2. package/dist/{defineMacro-Bv9R_9a2.cjs → defineMacro-CEZHaCXE.cjs} +13 -21
  3. package/dist/defineMacro-CEZHaCXE.cjs.map +1 -0
  4. package/dist/{defineMacro-BTXvS8nI.js → defineMacro-Shz8f6SG.js} +12 -19
  5. package/dist/defineMacro-Shz8f6SG.js.map +1 -0
  6. package/dist/factory.cjs +88 -28
  7. package/dist/factory.cjs.map +1 -0
  8. package/dist/factory.d.ts +6 -41
  9. package/dist/factory.js +58 -3
  10. package/dist/factory.js.map +1 -0
  11. package/dist/{index-BzjwdK2M.d.ts → index-B9cc8MBS.d.ts} +164 -194
  12. package/dist/index.cjs +144 -274
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +43 -37
  15. package/dist/index.js +109 -235
  16. package/dist/index.js.map +1 -1
  17. package/dist/macros.cjs +41 -28
  18. package/dist/macros.cjs.map +1 -1
  19. package/dist/macros.d.ts +14 -12
  20. package/dist/macros.js +34 -21
  21. package/dist/macros.js.map +1 -1
  22. package/dist/{operationParams-BZ07xDm0.d.ts → operationParams-k5CKwSWZ.d.ts} +9 -2
  23. package/dist/{visitor-DJ6ZEJvq.js → refs-BjNDuCBD.js} +116 -159
  24. package/dist/refs-BjNDuCBD.js.map +1 -0
  25. package/dist/{visitor-DpKZ9Tk0.cjs → refs-u5SDdyV7.cjs} +140 -195
  26. package/dist/refs-u5SDdyV7.cjs.map +1 -0
  27. package/dist/{response-hnSw2NKE.cjs → schema-BkvrrOAr.cjs} +496 -231
  28. package/dist/schema-BkvrrOAr.cjs.map +1 -0
  29. package/dist/{response-KUdWiDWw.js → schema-Cbnxmz4b.js} +472 -220
  30. package/dist/schema-Cbnxmz4b.js.map +1 -0
  31. package/dist/{types-DyDzizSf.d.ts → types-BB_xgRJ3.d.ts} +49 -123
  32. package/dist/types.d.ts +5 -6
  33. package/dist/{utils-BLJwyza-.cjs → utils-CEepwqmb.cjs} +76 -69
  34. package/dist/utils-CEepwqmb.cjs.map +1 -0
  35. package/dist/{utils-CF_-Pn_c.js → utils-DaXkewb1.js} +58 -51
  36. package/dist/utils-DaXkewb1.js.map +1 -0
  37. package/dist/utils.cjs +11 -11
  38. package/dist/utils.d.ts +53 -30
  39. package/dist/utils.js +3 -3
  40. package/package.json +1 -1
  41. package/src/constants.ts +3 -36
  42. package/src/defineMacro.ts +20 -13
  43. package/src/{node.ts → defineNode.ts} +12 -38
  44. package/src/{printer.ts → definePrinter.ts} +5 -3
  45. package/src/dialect.ts +52 -19
  46. package/src/factory.ts +7 -94
  47. package/src/index.ts +7 -9
  48. package/src/infer.ts +1 -1
  49. package/src/macros/macroDiscriminatorEnum.ts +7 -1
  50. package/src/macros/macroEnumName.ts +10 -2
  51. package/src/macros/macroSimplifyUnion.ts +29 -19
  52. package/src/nodes/code.ts +40 -80
  53. package/src/nodes/content.ts +2 -7
  54. package/src/nodes/file.ts +107 -18
  55. package/src/nodes/function.ts +46 -46
  56. package/src/nodes/index.ts +2 -3
  57. package/src/nodes/input.ts +13 -9
  58. package/src/nodes/operation.ts +8 -5
  59. package/src/nodes/output.ts +1 -1
  60. package/src/nodes/parameter.ts +5 -5
  61. package/src/nodes/property.ts +5 -5
  62. package/src/nodes/requestBody.ts +6 -13
  63. package/src/nodes/response.ts +93 -8
  64. package/src/nodes/schema.ts +5 -5
  65. package/src/optionality.ts +15 -0
  66. package/src/registry.ts +1 -1
  67. package/src/types.ts +4 -6
  68. package/src/utils/codegen.ts +6 -7
  69. package/src/utils/extractStringsFromNodes.ts +1 -0
  70. package/src/utils/fileMerge.ts +7 -8
  71. package/src/utils/operationParams.ts +9 -9
  72. package/src/utils/refs.ts +14 -18
  73. package/src/utils/schemaGraph.ts +41 -33
  74. package/src/utils/schemaMerge.ts +2 -2
  75. package/src/visitor.ts +3 -14
  76. package/dist/defineMacro-BTXvS8nI.js.map +0 -1
  77. package/dist/defineMacro-Bv9R_9a2.cjs.map +0 -1
  78. package/dist/extractStringsFromNodes-Cja-xxx5.js +0 -29
  79. package/dist/extractStringsFromNodes-Cja-xxx5.js.map +0 -1
  80. package/dist/extractStringsFromNodes-DKgDjFO0.cjs +0 -34
  81. package/dist/extractStringsFromNodes-DKgDjFO0.cjs.map +0 -1
  82. package/dist/extractStringsFromNodes-p4mX1TQD.d.ts +0 -14
  83. package/dist/factory-CZNOGI-N.js +0 -283
  84. package/dist/factory-CZNOGI-N.js.map +0 -1
  85. package/dist/factory-DG1CVkEb.cjs +0 -300
  86. package/dist/factory-DG1CVkEb.cjs.map +0 -1
  87. package/dist/response-KUdWiDWw.js.map +0 -1
  88. package/dist/response-hnSw2NKE.cjs.map +0 -1
  89. package/dist/utils-BLJwyza-.cjs.map +0 -1
  90. package/dist/utils-CF_-Pn_c.js.map +0 -1
  91. package/dist/visitor-DJ6ZEJvq.js.map +0 -1
  92. package/dist/visitor-DpKZ9Tk0.cjs.map +0 -1
  93. package/src/dedupe.ts +0 -239
  94. package/src/mocks.ts +0 -56
  95. package/src/nodes/http.ts +0 -85
package/src/nodes/file.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { defineNode } from '../node.ts'
1
+ import { hash } from 'node:crypto'
2
+ import path from 'node:path'
3
+ import { trimExtName } from '@internals/utils'
4
+ import { defineNode } from '../defineNode.ts'
5
+ import { extractStringsFromNodes } from '../utils/extractStringsFromNodes.ts'
6
+ import { combineExports, combineImports, combineSources } from '../utils/fileMerge.ts'
2
7
  import type { BaseNode } from './base.ts'
3
8
  import type { CodeNode } from './code.ts'
4
9
 
@@ -192,13 +197,14 @@ export type FileNode<TMeta extends object = object> = BaseNode & {
192
197
  id: string
193
198
  /**
194
199
  * File name without extension, derived from `baseName`.
195
- * @link https://nodejs.org/api/path.html#pathformatpathobject
200
+ *
201
+ * @see https://nodejs.org/api/path.html#pathformatpathobject
196
202
  */
197
203
  name: string
198
204
  /**
199
- * File base name, including extension.
200
- * Based on UNIX basename: `${name}${extname}`
201
- * @link https://nodejs.org/api/path.html#pathbasenamepath-suffix
205
+ * File base name, including extension, shaped like `${name}${extname}`.
206
+ *
207
+ * @see https://nodejs.org/api/path.html#pathbasenamepath-suffix
202
208
  */
203
209
  baseName: `${string}.${string}`
204
210
  /**
@@ -242,6 +248,22 @@ export type FileNode<TMeta extends object = object> = BaseNode & {
242
248
  */
243
249
  export const importDef = defineNode<ImportNode>({ kind: 'Import' })
244
250
 
251
+ /**
252
+ * Definition for the {@link ExportNode}.
253
+ */
254
+ export const exportDef = defineNode<ExportNode>({ kind: 'Export' })
255
+
256
+ /**
257
+ * Definition for the {@link SourceNode}.
258
+ */
259
+ export const sourceDef = defineNode<SourceNode>({ kind: 'Source' })
260
+
261
+ /**
262
+ * Definition for the {@link FileNode}. The fully resolved builder lives in
263
+ * `createFile`, so this definition only supplies the guard.
264
+ */
265
+ export const fileDef = defineNode<FileNode>({ kind: 'File' })
266
+
245
267
  /**
246
268
  * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
247
269
  *
@@ -253,11 +275,6 @@ export const importDef = defineNode<ImportNode>({ kind: 'Import' })
253
275
  */
254
276
  export const createImport = importDef.create
255
277
 
256
- /**
257
- * Definition for the {@link ExportNode}.
258
- */
259
- export const exportDef = defineNode<ExportNode>({ kind: 'Export' })
260
-
261
278
  /**
262
279
  * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
263
280
  *
@@ -269,11 +286,6 @@ export const exportDef = defineNode<ExportNode>({ kind: 'Export' })
269
286
  */
270
287
  export const createExport = exportDef.create
271
288
 
272
- /**
273
- * Definition for the {@link SourceNode}.
274
- */
275
- export const sourceDef = defineNode<SourceNode>({ kind: 'Source' })
276
-
277
289
  /**
278
290
  * Creates a `SourceNode` representing a fragment of source code within a file.
279
291
  *
@@ -285,7 +297,84 @@ export const sourceDef = defineNode<SourceNode>({ kind: 'Source' })
285
297
  export const createSource = sourceDef.create
286
298
 
287
299
  /**
288
- * Definition for the {@link FileNode}. The fully resolved builder lives in
289
- * `createFile`, so this definition only supplies the guard.
300
+ * Input descriptor for {@link createFile}, before `id`, `name`, and `extname` are computed
301
+ * and `imports`/`exports`/`sources` are deduplicated.
290
302
  */
291
- export const fileDef = defineNode<FileNode>({ kind: 'File' })
303
+ export type UserFileNode<TMeta extends object = object> = Omit<FileNode<TMeta>, 'kind' | 'id' | 'name' | 'extname' | 'imports' | 'exports' | 'sources'> &
304
+ Pick<Partial<FileNode<TMeta>>, 'imports' | 'exports' | 'sources'>
305
+
306
+ /**
307
+ * Creates a fully resolved `FileNode` from a file input descriptor.
308
+ *
309
+ * Computes:
310
+ * - `id` SHA256 hash of the file path
311
+ * - `name` `baseName` without extension
312
+ * - `extname` extension extracted from `baseName`
313
+ *
314
+ * Deduplicates:
315
+ * - `sources` via `combineSources`
316
+ * - `exports` via `combineExports`
317
+ * - `imports` via `combineImports` (also filters unused imports)
318
+ *
319
+ * @throws {Error} when `baseName` has no extension.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * const file = createFile({
324
+ * baseName: 'petStore.ts',
325
+ * path: 'src/models/petStore.ts',
326
+ * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
327
+ * imports: [createImport({ name: ['z'], path: 'zod' })],
328
+ * exports: [createExport({ name: ['Pet'], path: './petStore' })],
329
+ * })
330
+ * // file.id = SHA256 hash of 'src/models/petStore.ts'
331
+ * // file.name = 'petStore'
332
+ * // file.extname = '.ts'
333
+ * ```
334
+ */
335
+ export function createFile<TMeta extends object = object>(input: UserFileNode<TMeta>): FileNode<TMeta> {
336
+ const rawExtname = path.extname(input.baseName)
337
+ // Handle dotfile basename like '.ts' where path.extname returns ''
338
+ const extname = (rawExtname || (input.baseName.startsWith('.') ? input.baseName : '')) as `.${string}`
339
+ if (!extname) {
340
+ throw new Error(`No extname found for ${input.baseName}`)
341
+ }
342
+
343
+ const source = (input.sources ?? [])
344
+ .flatMap((item) => item.nodes ?? [])
345
+ .map((node) => extractStringsFromNodes([node]))
346
+ .filter(Boolean)
347
+ .join('\n\n')
348
+ const resolvedExports = input.exports?.length ? combineExports(input.exports) : []
349
+ const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || undefined) : []
350
+ const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name): name is string => Boolean(name)))
351
+ const nameOf = (item: string | { propertyName: string; name?: string }): string => (typeof item === 'string' ? item : (item.name ?? item.propertyName))
352
+ // Drop self-imports. Consolidating output (`mode: 'file'`) can place a symbol's
353
+ // definition and a cross-file import of it in the same file. The first pass catches imports that
354
+ // resolve to this file's own path. The second drops imports of names the file already defines,
355
+ // the case consolidation produces when the import path no longer matches `input.path`. Sources
356
+ // stay intact, so the local definition remains. Bare specifiers like `'zod'` never match a path.
357
+ const resolvedImports = combinedImports
358
+ .filter((imp) => imp.path !== input.path)
359
+ .flatMap((imp) => {
360
+ if (!Array.isArray(imp.name)) {
361
+ return typeof imp.name === 'string' && localNames.has(imp.name) ? [] : [imp]
362
+ }
363
+ const kept = imp.name.filter((item) => !localNames.has(nameOf(item)))
364
+ if (!kept.length) return []
365
+ return [kept.length === imp.name.length ? imp : { ...imp, name: kept }]
366
+ })
367
+ const resolvedSources = input.sources?.length ? combineSources(input.sources) : []
368
+
369
+ return {
370
+ kind: 'File',
371
+ ...input,
372
+ id: hash('sha256', input.path, 'hex'),
373
+ name: trimExtName(input.baseName),
374
+ extname,
375
+ imports: resolvedImports,
376
+ exports: resolvedExports,
377
+ sources: resolvedSources,
378
+ meta: input.meta ?? ({} as TMeta),
379
+ }
380
+ }
@@ -1,4 +1,4 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
3
 
4
4
  /**
@@ -43,11 +43,11 @@ export type TypeLiteralNode = BaseNode & {
43
43
 
44
44
  /**
45
45
  * AST node for a single field accessed from a named group type.
46
- * TypeScript renders as `objectType['indexType']`.
46
+ * TypeScript renders as `target['key']`.
47
47
  *
48
48
  * @example
49
49
  * ```ts
50
- * createIndexedAccessType({ objectType: 'GetPetPathParams', indexType: 'petId' })
50
+ * createIndexedAccessType({ target: 'GetPetPathParams', key: 'petId' })
51
51
  * // GetPetPathParams['petId']
52
52
  * ```
53
53
  */
@@ -56,11 +56,11 @@ export type IndexedAccessTypeNode = BaseNode & {
56
56
  /**
57
57
  * Name of the type being indexed, e.g. `'GetPetPathParams'`.
58
58
  */
59
- objectType: string
59
+ target: string
60
60
  /**
61
61
  * Field key to access, e.g. `'petId'`.
62
62
  */
63
- indexType: string
63
+ key: string
64
64
  }
65
65
 
66
66
  /**
@@ -168,49 +168,16 @@ export type FunctionParamKind = FunctionParamNode['kind']
168
168
  */
169
169
  export const typeLiteralDef = defineNode<TypeLiteralNode, Pick<TypeLiteralNode, 'members'>>({ kind: 'TypeLiteral' })
170
170
 
171
- /**
172
- * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
173
- *
174
- * @example
175
- * ```ts
176
- * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
177
- * // { petId: string }
178
- * ```
179
- */
180
- export const createTypeLiteral = typeLiteralDef.create
181
-
182
171
  /**
183
172
  * Definition for the {@link IndexedAccessTypeNode}.
184
173
  */
185
174
  export const indexedAccessTypeDef = defineNode<IndexedAccessTypeNode, Omit<IndexedAccessTypeNode, 'kind'>>({ kind: 'IndexedAccessType' })
186
175
 
187
- /**
188
- * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
189
- *
190
- * @example
191
- * ```ts
192
- * createIndexedAccessType({ objectType: 'DeletePetPathParams', indexType: 'petId' })
193
- * // DeletePetPathParams['petId']
194
- * ```
195
- */
196
- export const createIndexedAccessType = indexedAccessTypeDef.create
197
-
198
176
  /**
199
177
  * Definition for the {@link ObjectBindingPatternNode}.
200
178
  */
201
179
  export const objectBindingPatternDef = defineNode<ObjectBindingPatternNode, Pick<ObjectBindingPatternNode, 'elements'>>({ kind: 'ObjectBindingPattern' })
202
180
 
203
- /**
204
- * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
205
- *
206
- * @example
207
- * ```ts
208
- * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
209
- * // { id, name }
210
- * ```
211
- */
212
- export const createObjectBindingPattern = objectBindingPatternDef.create
213
-
214
181
  /**
215
182
  * Plain property descriptor for a destructured group built by {@link createFunctionParameter}.
216
183
  */
@@ -244,6 +211,47 @@ export const functionParameterDef = defineNode<FunctionParameterNode, FunctionPa
244
211
  },
245
212
  })
246
213
 
214
+ /**
215
+ * Definition for the {@link FunctionParametersNode}.
216
+ */
217
+ export const functionParametersDef = defineNode<FunctionParametersNode, Partial<Omit<FunctionParametersNode, 'kind'>>>({
218
+ kind: 'FunctionParameters',
219
+ defaults: { params: [] },
220
+ })
221
+
222
+ /**
223
+ * Creates a {@link TypeLiteralNode} representing an inline anonymous object type.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * createTypeLiteral({ members: [{ name: 'petId', type: 'string', optional: false }] })
228
+ * // { petId: string }
229
+ * ```
230
+ */
231
+ export const createTypeLiteral = typeLiteralDef.create
232
+
233
+ /**
234
+ * Creates an {@link IndexedAccessTypeNode} representing a single field accessed from a named type.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * createIndexedAccessType({ target: 'DeletePetPathParams', key: 'petId' })
239
+ * // DeletePetPathParams['petId']
240
+ * ```
241
+ */
242
+ export const createIndexedAccessType = indexedAccessTypeDef.create
243
+
244
+ /**
245
+ * Creates an {@link ObjectBindingPatternNode} for a destructured parameter binding.
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * createObjectBindingPattern({ elements: [{ name: 'id' }, { name: 'name' }] })
250
+ * // { id, name }
251
+ * ```
252
+ */
253
+ export const createObjectBindingPattern = objectBindingPatternDef.create
254
+
247
255
  /**
248
256
  * Creates a `FunctionParameterNode`. `optional` defaults to `false`.
249
257
  *
@@ -261,14 +269,6 @@ export const functionParameterDef = defineNode<FunctionParameterNode, FunctionPa
261
269
  */
262
270
  export const createFunctionParameter = functionParameterDef.create
263
271
 
264
- /**
265
- * Definition for the {@link FunctionParametersNode}.
266
- */
267
- export const functionParametersDef = defineNode<FunctionParametersNode, Partial<Omit<FunctionParametersNode, 'kind'>>>({
268
- kind: 'FunctionParameters',
269
- defaults: { params: [] },
270
- })
271
-
272
272
  /**
273
273
  * Creates a `FunctionParametersNode` from an ordered list of parameters.
274
274
  *
@@ -14,7 +14,7 @@ import type { SchemaNode } from './schema.ts'
14
14
  export type { NodeKind } from './base.ts'
15
15
  export type { ArrowFunctionNode, BreakNode, CodeNode, ConstNode, FunctionNode, JSDocNode, JsxNode, TextNode, TypeNode } from './code.ts'
16
16
  export type { ContentNode } from './content.ts'
17
- export type { ExportNode, FileNode, ImportNode, SourceNode } from './file.ts'
17
+ export type { ExportNode, FileNode, ImportNode, SourceNode, UserFileNode } from './file.ts'
18
18
  export type {
19
19
  FunctionParamKind,
20
20
  FunctionParameterNode,
@@ -25,14 +25,13 @@ export type {
25
25
  TypeExpression,
26
26
  TypeLiteralNode,
27
27
  } from './function.ts'
28
- export type { StatusCode } from './http.ts'
29
28
  export type { InputMeta, InputNode } from './input.ts'
30
29
  export type { GenericOperationNode, HttpMethod, HttpOperationNode, OperationNode } from './operation.ts'
31
30
  export type { OutputNode } from './output.ts'
32
31
  export type { ParameterLocation, ParameterNode } from './parameter.ts'
33
32
  export type { PropertyNode } from './property.ts'
34
33
  export type { RequestBodyNode } from './requestBody.ts'
35
- export type { ResponseNode } from './response.ts'
34
+ export type { ResponseNode, StatusCode } from './response.ts'
36
35
  export type {
37
36
  ArraySchemaNode,
38
37
  DateSchemaNode,
@@ -1,5 +1,5 @@
1
1
  import type { Streamable } from '@internals/utils'
2
- import { defineNode } from '../node.ts'
2
+ import { defineNode } from '../defineNode.ts'
3
3
  import type { BaseNode } from './base.ts'
4
4
  import type { OperationNode } from './operation.ts'
5
5
  import type { SchemaNode } from './schema.ts'
@@ -68,8 +68,7 @@ export type InputMeta = {
68
68
  * Produced by the adapter and consumed by all Kubb plugins.
69
69
  *
70
70
  * `Stream` switches `schemas` and `operations` between eager `Array`s (the default) and lazy
71
- * `AsyncIterable`s. The streaming variant `InputNode<true>` yields nodes one at a time and makes
72
- * `meta` optional, since the adapter can emit metadata before the first node is parsed.
71
+ * `AsyncIterable`s. The streaming variant `InputNode<true>` yields nodes one at a time.
73
72
  *
74
73
  * @example
75
74
  * ```ts
@@ -101,7 +100,11 @@ export type InputNode<Stream extends boolean = false> = BaseNode & {
101
100
  * All operation nodes in the document.
102
101
  */
103
102
  operations: Streamable<OperationNode, Stream>
104
- } & (Stream extends true ? { meta?: InputMeta } : { meta: InputMeta })
103
+ /**
104
+ * Document metadata populated by the adapter.
105
+ */
106
+ meta: InputMeta
107
+ }
105
108
 
106
109
  /**
107
110
  * Definition for the {@link InputNode}.
@@ -115,8 +118,8 @@ export const inputDef = defineNode<InputNode, Partial<Omit<InputNode, 'kind'>>>(
115
118
 
116
119
  /**
117
120
  * Creates an `InputNode`. Pass `stream: true` for the streaming variant whose `schemas` and
118
- * `operations` are `AsyncIterable` sources and whose `meta` is optional. Otherwise it builds the
119
- * eager variant with array `schemas`/`operations` and the defaulted `meta`.
121
+ * `operations` are `AsyncIterable` sources. Otherwise it builds the eager variant with array
122
+ * `schemas`/`operations`. Both variants get the defaulted `meta`.
120
123
  *
121
124
  * @example Eager
122
125
  * ```ts
@@ -131,10 +134,11 @@ export const inputDef = defineNode<InputNode, Partial<Omit<InputNode, 'kind'>>>(
131
134
  */
132
135
  export function createInput<Stream extends boolean = false>(options: Partial<Omit<InputNode<Stream>, 'kind'>> & { stream?: Stream } = {}): InputNode<Stream> {
133
136
  const { stream, ...overrides } = options
134
- // Streaming inputs carry AsyncIterable sources, so skip the array/meta defaults that
135
- // inputDef.create applies for the eager variant.
137
+ // Streaming inputs carry AsyncIterable sources, so skip the array defaults that
138
+ // inputDef.create applies for the eager variant. Keep the meta default.
136
139
  if (stream) {
137
- return { kind: 'Input', ...overrides } as InputNode<Stream>
140
+ return { kind: 'Input', meta: { circularNames: [], enumNames: [] }, ...overrides } as InputNode<Stream>
138
141
  }
142
+
139
143
  return inputDef.create(overrides as Partial<Omit<InputNode, 'kind'>>) as InputNode<Stream>
140
144
  }
@@ -1,9 +1,12 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
3
  import type { ParameterNode } from './parameter.ts'
4
- import { createRequestBody, type RequestBodyNode, type UserRequestBody } from './requestBody.ts'
4
+ import { createRequestBody, type RequestBodyNode } from './requestBody.ts'
5
5
  import type { ResponseNode } from './response.ts'
6
6
 
7
+ /**
8
+ * HTTP method an operation responds to.
9
+ */
7
10
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE'
8
11
 
9
12
  /**
@@ -111,7 +114,7 @@ type OperationInput = {
111
114
  operationId: string
112
115
  method?: HttpOperationNode['method']
113
116
  path?: HttpOperationNode['path']
114
- requestBody?: UserRequestBody
117
+ requestBody?: Omit<RequestBodyNode, 'kind'>
115
118
  [key: string]: unknown
116
119
  }
117
120
 
@@ -151,13 +154,13 @@ export const operationDef = defineNode<OperationNode, OperationInput>({
151
154
  export function createOperation(
152
155
  props: Pick<HttpOperationNode, 'operationId' | 'method' | 'path'> &
153
156
  Partial<Omit<HttpOperationNode, 'kind' | 'operationId' | 'method' | 'path' | 'requestBody'>> & {
154
- requestBody?: UserRequestBody
157
+ requestBody?: Omit<RequestBodyNode, 'kind'>
155
158
  },
156
159
  ): HttpOperationNode
157
160
  export function createOperation(
158
161
  props: Pick<GenericOperationNode, 'operationId'> &
159
162
  Partial<Omit<GenericOperationNode, 'kind' | 'operationId' | 'requestBody'>> & {
160
- requestBody?: UserRequestBody
163
+ requestBody?: Omit<RequestBodyNode, 'kind'>
161
164
  },
162
165
  ): GenericOperationNode
163
166
  export function createOperation(props: OperationInput): OperationNode {
@@ -1,4 +1,4 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
3
  import type { FileNode } from './file.ts'
4
4
 
@@ -1,4 +1,5 @@
1
- import { defineNode, syncOptionality } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
+ import { optionality } from '../optionality.ts'
2
3
  import type { BaseNode } from './base.ts'
3
4
  import type { SchemaNode } from './schema.ts'
4
5
 
@@ -41,18 +42,17 @@ export type ParameterNode = BaseNode & {
41
42
  type UserParameterNode = Pick<ParameterNode, 'name' | 'in' | 'schema'> & Partial<Omit<ParameterNode, 'kind' | 'name' | 'in' | 'schema'>>
42
43
 
43
44
  /**
44
- * Definition for the {@link ParameterNode}. `required` defaults to `false` and the
45
- * schema's `optional`/`nullish` flags are kept in sync with it.
45
+ * Definition for the {@link ParameterNode}. `required` defaults to `false`, and the schema's
46
+ * `optional`/`nullish` flags are derived from it through {@link optionality}.
46
47
  */
47
48
  export const parameterDef = defineNode<ParameterNode, UserParameterNode>({
48
49
  kind: 'Parameter',
49
50
  build: (props) => {
50
51
  const required = props.required ?? false
51
- return { ...props, required, schema: syncOptionality(props.schema, required) }
52
+ return { ...props, required, schema: optionality(props.schema, required) }
52
53
  },
53
54
  children: ['schema'],
54
55
  visitorKey: 'parameter',
55
- rebuild: true,
56
56
  })
57
57
 
58
58
  /**
@@ -1,4 +1,5 @@
1
- import { defineNode, syncOptionality } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
+ import { optionality } from '../optionality.ts'
2
3
  import type { BaseNode } from './base.ts'
3
4
  import type { SchemaNode } from './schema.ts'
4
5
 
@@ -37,18 +38,17 @@ export type PropertyNode = BaseNode & {
37
38
  export type UserPropertyNode = Pick<PropertyNode, 'name' | 'schema'> & Partial<Omit<PropertyNode, 'kind' | 'name' | 'schema'>>
38
39
 
39
40
  /**
40
- * Definition for the {@link PropertyNode}. `required` defaults to `false` and the
41
- * schema's `optional`/`nullish` flags are kept in sync with it.
41
+ * Definition for the {@link PropertyNode}. `required` defaults to `false`, and the schema's
42
+ * `optional`/`nullish` flags are derived from it through {@link optionality}.
42
43
  */
43
44
  export const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
44
45
  kind: 'Property',
45
46
  build: (props) => {
46
47
  const required = props.required ?? false
47
- return { ...props, required, schema: syncOptionality(props.schema, required) }
48
+ return { ...props, required, schema: optionality(props.schema, required) }
48
49
  },
49
50
  children: ['schema'],
50
51
  visitorKey: 'property',
51
- rebuild: true,
52
52
  })
53
53
 
54
54
  /**
@@ -1,6 +1,6 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
- import { type ContentNode, createContent, type UserContent } from './content.ts'
3
+ import type { ContentNode } from './content.ts'
4
4
 
5
5
  /**
6
6
  * AST node representing an operation request body.
@@ -40,22 +40,15 @@ export type RequestBodyNode = BaseNode & {
40
40
  }
41
41
 
42
42
  /**
43
- * Loosely-typed request body accepted by `createOperation`, normalized into a {@link RequestBodyNode}.
43
+ * Definition for the {@link RequestBodyNode}. Content entries are built upfront with
44
+ * {@link createContent}, mirroring how `parameters` and `responses` take prebuilt nodes.
44
45
  */
45
- export type UserRequestBody = Omit<RequestBodyNode, 'kind' | 'content'> & {
46
- content?: Array<UserContent>
47
- }
48
-
49
- /**
50
- * Definition for the {@link RequestBodyNode}, normalizing each content entry into a `ContentNode`.
51
- */
52
- export const requestBodyDef = defineNode<RequestBodyNode, UserRequestBody>({
46
+ export const requestBodyDef = defineNode<RequestBodyNode>({
53
47
  kind: 'RequestBody',
54
- build: (props) => ({ ...props, content: props.content?.map(createContent) }),
55
48
  children: ['content'],
56
49
  })
57
50
 
58
51
  /**
59
- * Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
52
+ * Creates a `RequestBodyNode`.
60
53
  */
61
54
  export const createRequestBody = requestBodyDef.create
@@ -1,9 +1,94 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
- import { type ContentNode, createContent, type UserContent } from './content.ts'
4
- import type { StatusCode } from './http.ts'
3
+ import { type ContentNode, createContent } from './content.ts'
5
4
  import type { SchemaNode } from './schema.ts'
6
5
 
6
+ /**
7
+ * All supported HTTP status code literals as strings, as used in API specs
8
+ * (for example, `"200"` and `"404"`).
9
+ */
10
+ type HttpStatusCode =
11
+ // 1xx Informational
12
+ | '100'
13
+ | '101'
14
+ | '102'
15
+ | '103'
16
+ // 2xx Success
17
+ | '200'
18
+ | '201'
19
+ | '202'
20
+ | '203'
21
+ | '204'
22
+ | '205'
23
+ | '206'
24
+ | '207'
25
+ | '208'
26
+ | '226'
27
+ // 3xx Redirection
28
+ | '300'
29
+ | '301'
30
+ | '302'
31
+ | '303'
32
+ | '304'
33
+ | '305'
34
+ | '307'
35
+ | '308'
36
+ // 4xx Client Error
37
+ | '400'
38
+ | '401'
39
+ | '402'
40
+ | '403'
41
+ | '404'
42
+ | '405'
43
+ | '406'
44
+ | '407'
45
+ | '408'
46
+ | '409'
47
+ | '410'
48
+ | '411'
49
+ | '412'
50
+ | '413'
51
+ | '414'
52
+ | '415'
53
+ | '416'
54
+ | '417'
55
+ | '418'
56
+ | '421'
57
+ | '422'
58
+ | '423'
59
+ | '424'
60
+ | '425'
61
+ | '426'
62
+ | '428'
63
+ | '429'
64
+ | '431'
65
+ | '451'
66
+ // 5xx Server Error
67
+ | '500'
68
+ | '501'
69
+ | '502'
70
+ | '503'
71
+ | '504'
72
+ | '505'
73
+ | '506'
74
+ | '507'
75
+ | '508'
76
+ | '510'
77
+ | '511'
78
+
79
+ /**
80
+ * Response status code literal used by operations.
81
+ *
82
+ * Includes specific HTTP status code strings and `"default"` for catch-all responses.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const status: StatusCode = '200'
87
+ * const fallback: StatusCode = 'default'
88
+ * ```
89
+ */
90
+ export type StatusCode = HttpStatusCode | 'default'
91
+
7
92
  /**
8
93
  * AST node representing one operation response variant.
9
94
  *
@@ -16,7 +101,7 @@ import type { SchemaNode } from './schema.ts'
16
101
  * const response: ResponseNode = {
17
102
  * kind: 'Response',
18
103
  * statusCode: '200',
19
- * content: [{ contentType: 'application/json', schema: createSchema({ type: 'string' }) }],
104
+ * content: [{ kind: 'Content', contentType: 'application/json', schema: createSchema({ type: 'string' }) }],
20
105
  * }
21
106
  * ```
22
107
  */
@@ -53,7 +138,7 @@ export type ResponseNode = BaseNode & {
53
138
 
54
139
  type ResponseInput = Pick<ResponseNode, 'statusCode'> &
55
140
  Partial<Omit<ResponseNode, 'kind' | 'statusCode' | 'content'>> & {
56
- content?: Array<UserContent>
141
+ content?: Array<ContentNode>
57
142
  schema?: SchemaNode
58
143
  mediaType?: string | null
59
144
  keysToOmit?: Array<string> | null
@@ -67,8 +152,8 @@ export const responseDef = defineNode<ResponseNode, ResponseInput>({
67
152
  kind: 'Response',
68
153
  build: (props) => {
69
154
  const { schema, mediaType, keysToOmit, content, ...rest } = props
70
- const entries = content ?? (schema ? [{ contentType: mediaType ?? 'application/json', schema, keysToOmit: keysToOmit ?? null }] : undefined)
71
- return { ...rest, content: entries?.map(createContent) }
155
+ const entries = content ?? (schema ? [createContent({ contentType: mediaType ?? 'application/json', schema, keysToOmit: keysToOmit ?? null })] : undefined)
156
+ return { ...rest, content: entries }
72
157
  },
73
158
  children: ['content'],
74
159
  visitorKey: 'response',
@@ -81,7 +166,7 @@ export const responseDef = defineNode<ResponseNode, ResponseInput>({
81
166
  * ```ts
82
167
  * const response = createResponse({
83
168
  * statusCode: '200',
84
- * content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
169
+ * content: [createContent({ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) })],
85
170
  * })
86
171
  * ```
87
172
  */