@kubb/ast 5.0.0-alpha.3 → 5.0.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,14 +4,17 @@ const visitorDepths = {
4
4
  shallow: "shallow",
5
5
  deep: "deep"
6
6
  };
7
- const nodeKinds = {
8
- root: "Root",
9
- operation: "Operation",
10
- schema: "Schema",
11
- property: "Property",
12
- parameter: "Parameter",
13
- response: "Response"
14
- };
7
+ /**
8
+ * Canonical schema type strings used by AST schema nodes.
9
+ *
10
+ * These values are used across the AST as stable discriminators
11
+ * (for example `schema.type === schemaTypes.object`).
12
+ *
13
+ * The map is grouped by intent:
14
+ * - primitives (`string`, `number`, `boolean`, ...)
15
+ * - structural/composite (`object`, `array`, `union`, ...)
16
+ * - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
17
+ */
15
18
  const schemaTypes = {
16
19
  string: "string",
17
20
  number: "number",
@@ -35,8 +38,27 @@ const schemaTypes = {
35
38
  uuid: "uuid",
36
39
  email: "email",
37
40
  url: "url",
38
- blob: "blob"
41
+ ipv4: "ipv4",
42
+ ipv6: "ipv6",
43
+ blob: "blob",
44
+ never: "never"
39
45
  };
46
+ /**
47
+ * Primitive scalar schema types used when simplifying union members.
48
+ */
49
+ const SCALAR_PRIMITIVE_TYPES = new Set([
50
+ "string",
51
+ "number",
52
+ "integer",
53
+ "bigint",
54
+ "boolean"
55
+ ]);
56
+ /**
57
+ * Returns `true` when `type` is a scalar primitive schema type.
58
+ */
59
+ function isScalarPrimitive(type) {
60
+ return SCALAR_PRIMITIVE_TYPES.has(type);
61
+ }
40
62
  const httpMethods = {
41
63
  get: "GET",
42
64
  post: "POST",
@@ -71,7 +93,33 @@ const mediaTypes = {
71
93
  //#endregion
72
94
  //#region src/factory.ts
73
95
  /**
74
- * Creates a `RootNode`.
96
+ * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
97
+ *
98
+ * - `optional` is set for non-required, non-nullable schemas.
99
+ * - `nullish` is set for non-required, nullable schemas.
100
+ */
101
+ function syncOptionality(schema, required) {
102
+ const nullable = schema.nullable ?? false;
103
+ return {
104
+ ...schema,
105
+ optional: !required && !nullable ? true : void 0,
106
+ nullish: !required && nullable ? true : void 0
107
+ };
108
+ }
109
+ /**
110
+ * Creates a `RootNode` with stable defaults for `schemas` and `operations`.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const root = createRoot()
115
+ * // { kind: 'Root', schemas: [], operations: [] }
116
+ * ```
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const root = createRoot({ schemas: [petSchema] })
121
+ * // keeps default operations: []
122
+ * ```
75
123
  */
76
124
  function createRoot(overrides = {}) {
77
125
  return {
@@ -82,7 +130,27 @@ function createRoot(overrides = {}) {
82
130
  };
83
131
  }
84
132
  /**
85
- * Creates an `OperationNode`.
133
+ * Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * const operation = createOperation({
138
+ * operationId: 'getPetById',
139
+ * method: 'GET',
140
+ * path: '/pet/{petId}',
141
+ * })
142
+ * // tags, parameters, and responses are []
143
+ * ```
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const operation = createOperation({
148
+ * operationId: 'findPets',
149
+ * method: 'GET',
150
+ * path: '/pet/findByStatus',
151
+ * tags: ['pet'],
152
+ * })
153
+ * ```
86
154
  */
87
155
  function createOperation(props) {
88
156
  return {
@@ -93,39 +161,125 @@ function createOperation(props) {
93
161
  kind: "Operation"
94
162
  };
95
163
  }
164
+ /**
165
+ * Maps schema `type` to its underlying `primitive`.
166
+ * Primitive types map to themselves; special string formats map to `'string'`.
167
+ * Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
168
+ */
169
+ const TYPE_TO_PRIMITIVE = {
170
+ string: "string",
171
+ number: "number",
172
+ integer: "integer",
173
+ bigint: "bigint",
174
+ boolean: "boolean",
175
+ null: "null",
176
+ any: "any",
177
+ unknown: "unknown",
178
+ void: "void",
179
+ never: "never",
180
+ object: "object",
181
+ array: "array",
182
+ date: "date",
183
+ uuid: "string",
184
+ email: "string",
185
+ url: "string",
186
+ datetime: "string",
187
+ time: "string"
188
+ };
96
189
  function createSchema(props) {
190
+ const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
97
191
  if (props["type"] === "object") return {
98
192
  properties: [],
193
+ primitive: "object",
99
194
  ...props,
100
195
  kind: "Schema"
101
196
  };
102
197
  return {
198
+ primitive: inferredPrimitive,
103
199
  ...props,
104
200
  kind: "Schema"
105
201
  };
106
202
  }
107
203
  /**
108
- * Creates a `PropertyNode`. `required` defaults to `false`.
204
+ * Creates a `PropertyNode`.
205
+ *
206
+ * `required` defaults to `false`.
207
+ * `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * const property = createProperty({
212
+ * name: 'status',
213
+ * schema: createSchema({ type: 'string' }),
214
+ * })
215
+ * // required=false, schema.optional=true
216
+ * ```
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * const property = createProperty({
221
+ * name: 'status',
222
+ * required: true,
223
+ * schema: createSchema({ type: 'string', nullable: true }),
224
+ * })
225
+ * // required=true, no optional/nullish
226
+ * ```
109
227
  */
110
228
  function createProperty(props) {
229
+ const required = props.required ?? false;
111
230
  return {
112
- required: false,
113
231
  ...props,
114
- kind: "Property"
232
+ kind: "Property",
233
+ required,
234
+ schema: syncOptionality(props.schema, required)
115
235
  };
116
236
  }
117
237
  /**
118
- * Creates a `ParameterNode`. `required` defaults to `false`.
238
+ * Creates a `ParameterNode`.
239
+ *
240
+ * `required` defaults to `false`.
241
+ * Nested schema flags are set from `required` and `schema.nullable`.
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * const param = createParameter({
246
+ * name: 'petId',
247
+ * in: 'path',
248
+ * required: true,
249
+ * schema: createSchema({ type: 'string' }),
250
+ * })
251
+ * ```
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * const param = createParameter({
256
+ * name: 'status',
257
+ * in: 'query',
258
+ * schema: createSchema({ type: 'string', nullable: true }),
259
+ * })
260
+ * // required=false, schema.nullish=true
261
+ * ```
119
262
  */
120
263
  function createParameter(props) {
264
+ const required = props.required ?? false;
121
265
  return {
122
- required: false,
123
266
  ...props,
124
- kind: "Parameter"
267
+ kind: "Parameter",
268
+ required,
269
+ schema: syncOptionality(props.schema, required)
125
270
  };
126
271
  }
127
272
  /**
128
273
  * Creates a `ResponseNode`.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * const response = createResponse({
278
+ * statusCode: '200',
279
+ * description: 'Success',
280
+ * schema: createSchema({ type: 'object', properties: [] }),
281
+ * })
282
+ * ```
129
283
  */
130
284
  function createResponse(props) {
131
285
  return {
@@ -133,10 +287,132 @@ function createResponse(props) {
133
287
  kind: "Response"
134
288
  };
135
289
  }
290
+ /**
291
+ * Creates a `FunctionParameterNode`.
292
+ *
293
+ * `optional` defaults to `false`.
294
+ *
295
+ * @example Required typed param
296
+ * ```ts
297
+ * createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }) })
298
+ * // → petId: string
299
+ * ```
300
+ *
301
+ * @example Optional param
302
+ * ```ts
303
+ * createFunctionParameter({ name: 'params', type: createTypeNode({ variant: 'reference', name: 'QueryParams' }), optional: true })
304
+ * // → params?: QueryParams
305
+ * ```
306
+ *
307
+ * @example Param with default (implicitly optional; cannot combine with `optional: true`)
308
+ * ```ts
309
+ * createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
310
+ * // → config: RequestConfig = {}
311
+ * ```
312
+ */
313
+ function createFunctionParameter(props) {
314
+ return {
315
+ optional: false,
316
+ ...props,
317
+ kind: "FunctionParameter"
318
+ };
319
+ }
320
+ /**
321
+ * Creates a {@link TypeNode} representing a language-agnostic structured type expression.
322
+ *
323
+ * Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
324
+ * named field accessed from a group type. Each language's printer renders the variant
325
+ * into its own syntax (TypeScript, Python, C#, Kotlin, …).
326
+ *
327
+ * @example Reference type (TypeScript: `QueryParams`)
328
+ * ```ts
329
+ * createTypeNode({ variant: 'reference', name: 'QueryParams' })
330
+ * ```
331
+ *
332
+ * @example Struct type (TypeScript: `{ petId: string }`)
333
+ * ```ts
334
+ * createTypeNode({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createTypeNode({ variant: 'reference', name: 'string' }) }] })
335
+ * ```
336
+ *
337
+ * @example Member type (TypeScript: `DeletePetPathParams['petId']`)
338
+ * ```ts
339
+ * createTypeNode({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
340
+ * ```
341
+ */
342
+ function createTypeNode(props) {
343
+ return {
344
+ ...props,
345
+ kind: "Type"
346
+ };
347
+ }
348
+ /**
349
+ * Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
350
+ *
351
+ * @example Grouped param (TypeScript declaration)
352
+ * ```ts
353
+ * createParameterGroup({
354
+ * properties: [
355
+ * createFunctionParameter({ name: 'id', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
356
+ * createFunctionParameter({ name: 'name', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: true }),
357
+ * ],
358
+ * default: '{}',
359
+ * })
360
+ * // declaration → { id, name? }: { id: string; name?: string } = {}
361
+ * // call → { id, name }
362
+ * ```
363
+ *
364
+ * @example Inline (spread) — children emitted as individual top-level parameters
365
+ * ```ts
366
+ * createParameterGroup({
367
+ * properties: [createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false })],
368
+ * inline: true,
369
+ * })
370
+ * // declaration → petId: string
371
+ * // call → petId
372
+ * ```
373
+ */
374
+ function createParameterGroup(props) {
375
+ return {
376
+ ...props,
377
+ kind: "ParameterGroup"
378
+ };
379
+ }
380
+ /**
381
+ * Creates a `FunctionParametersNode` from an ordered list of parameters.
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * createFunctionParameters({
386
+ * params: [
387
+ * createFunctionParameter({ name: 'petId', type: createTypeNode({ variant: 'reference', name: 'string' }), optional: false }),
388
+ * createFunctionParameter({ name: 'config', type: createTypeNode({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
389
+ * ],
390
+ * })
391
+ * ```
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * const empty = createFunctionParameters()
396
+ * // { kind: 'FunctionParameters', params: [] }
397
+ * ```
398
+ */
399
+ function createFunctionParameters(props = {}) {
400
+ return {
401
+ params: [],
402
+ ...props,
403
+ kind: "FunctionParameters"
404
+ };
405
+ }
136
406
  //#endregion
137
407
  //#region src/guards.ts
138
408
  /**
139
- * Narrows a `SchemaNode` to the specific variant matching `type`.
409
+ * Narrows a `SchemaNode` to the variant that matches `type`.
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * const schema = createSchema({ type: 'string' })
414
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
415
+ * ```
140
416
  */
141
417
  function narrowSchema(node, type) {
142
418
  return node?.type === type ? node : void 0;
@@ -144,129 +420,214 @@ function narrowSchema(node, type) {
144
420
  function isKind(kind) {
145
421
  return (node) => node.kind === kind;
146
422
  }
423
+ isKind("Root");
147
424
  /**
148
- * Type guard for `RootNode`.
149
- */
150
- const isRootNode = isKind("Root");
151
- /**
152
- * Type guard for `OperationNode`.
425
+ * Returns `true` when the input is an `OperationNode`.
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * if (isOperationNode(node)) {
430
+ * console.log(node.operationId)
431
+ * }
432
+ * ```
153
433
  */
154
434
  const isOperationNode = isKind("Operation");
155
435
  /**
156
- * Type guard for `SchemaNode`.
436
+ * Returns `true` when the input is a `SchemaNode`.
437
+ *
438
+ * @example
439
+ * ```ts
440
+ * if (isSchemaNode(node)) {
441
+ * console.log(node.type)
442
+ * }
443
+ * ```
157
444
  */
158
445
  const isSchemaNode = isKind("Schema");
159
- /**
160
- * Type guard for `PropertyNode`.
161
- */
162
- const isPropertyNode = isKind("Property");
163
- /**
164
- * Type guard for `ParameterNode`.
165
- */
166
- const isParameterNode = isKind("Parameter");
167
- /**
168
- * Type guard for `ResponseNode`.
169
- */
170
- const isResponseNode = isKind("Response");
446
+ isKind("Property");
447
+ isKind("Parameter");
448
+ isKind("Response");
449
+ isKind("FunctionParameter");
450
+ isKind("ParameterGroup");
451
+ isKind("FunctionParameters");
171
452
  //#endregion
172
453
  //#region src/printer.ts
173
454
  /**
174
- * Creates a named printer factory. Mirrors the `definePlugin` / `defineAdapter` pattern
175
- * from `@kubb/core` — wraps a builder to make options optional and separates raw options
176
- * from resolved options.
177
- *
178
- * @example
179
- * ```ts
180
- * type ZodPrinter = PrinterFactoryOptions<'zod', { strict?: boolean }, { strict: boolean }, string>
181
- *
182
- * export const zodPrinter = definePrinter<ZodPrinter>((options) => {
183
- * const { strict = true } = options
184
- * return {
185
- * name: 'zod',
186
- * options: { strict },
187
- * nodes: {
188
- * string(node) {
189
- * return `z.string()`
190
- * },
191
- * object(node) {
192
- * const props = node.properties
193
- * ?.map(p => `${p.name}: ${this.print(p)}`)
194
- * .join(', ') ?? ''
195
- * return `z.object({ ${props} })`
196
- * },
197
- * },
198
- * }
199
- * })
455
+ * Creates a schema printer factory.
456
+ *
457
+ * This function wraps a builder and makes options optional at call sites.
200
458
  *
201
- * const printer = zodPrinter({ strict: false })
202
- * printer.name // 'zod'
203
- * printer.options // { strict: false }
204
- * printer.print(node) // 'z.string()'
459
+ * The builder receives resolved options and returns:
460
+ * - `name` — a unique identifier for the printer
461
+ * - `options` options stored on the returned printer instance
462
+ * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
463
+ * - `print` _(optional)_ — top-level override exposed as `printer.print`
464
+ * - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
465
+ * - This keeps recursion safe and avoids self-calls
466
+ *
467
+ * When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
468
+ *
469
+ * @example Basic usage — Zod schema printer
470
+ * ```ts
471
+ * type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
472
+ *
473
+ * export const zodPrinter = definePrinter<PrinterZod>((options) => ({
474
+ * name: 'zod',
475
+ * options: { strict: options.strict ?? true },
476
+ * nodes: {
477
+ * string: () => 'z.string()',
478
+ * object(node) {
479
+ * const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
480
+ * return `z.object({ ${props} })`
481
+ * },
482
+ * },
483
+ * }))
205
484
  * ```
206
485
  */
207
486
  function definePrinter(build) {
208
- return (options) => {
209
- const { name, options: resolvedOptions, nodes } = build(options ?? {});
210
- const context = {
211
- options: resolvedOptions,
212
- print: (node) => {
213
- const handler = nodes[node.type];
214
- return handler ? handler.call(context, node) : void 0;
215
- }
216
- };
217
- return {
218
- name,
219
- options: resolvedOptions,
220
- print: context.print,
221
- for: (nodes) => nodes.map(context.print)
487
+ return createPrinterFactory((node) => node.type)(build);
488
+ }
489
+ /**
490
+ * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
491
+ **
492
+ * @example
493
+ * ```ts
494
+ * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
495
+ * (node) => kindToHandlerKey[node.kind],
496
+ * )
497
+ * ```
498
+ */
499
+ function createPrinterFactory(getKey) {
500
+ return function(build) {
501
+ return (options) => {
502
+ const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
503
+ const context = {
504
+ options: resolvedOptions,
505
+ transform: (node) => {
506
+ const key = getKey(node);
507
+ if (key === void 0) return null;
508
+ const handler = nodes[key];
509
+ if (!handler) return null;
510
+ return handler.call(context, node);
511
+ }
512
+ };
513
+ return {
514
+ name,
515
+ options: resolvedOptions,
516
+ transform: context.transform,
517
+ print: printOverride ? printOverride.bind(context) : context.transform
518
+ };
222
519
  };
223
520
  };
224
521
  }
225
522
  //#endregion
226
523
  //#region src/refs.ts
227
524
  /**
228
- * Indexes named schemas from `root.schemas` by name. Unnamed schemas are skipped.
525
+ * Returns the last path segment of a reference string.
526
+ *
527
+ * Example: `#/components/schemas/Pet` becomes `Pet`.
528
+ *
529
+ * @example
530
+ * ```ts
531
+ * extractRefName('#/components/schemas/Pet') // 'Pet'
532
+ * ```
229
533
  */
230
- function buildRefMap(root) {
231
- const map = /* @__PURE__ */ new Map();
232
- for (const schema of root.schemas) if (schema.name) map.set(schema.name, schema);
233
- return map;
534
+ function extractRefName(ref) {
535
+ return ref.split("/").at(-1) ?? ref;
234
536
  }
537
+ //#endregion
538
+ //#region ../../internals/utils/src/casing.ts
235
539
  /**
236
- * Looks up a schema by name. Prefer over `RefMap.get()` to keep the resolution strategy swappable.
540
+ * Shared implementation for camelCase and PascalCase conversion.
541
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
542
+ * and capitalizes each word according to `pascal`.
543
+ *
544
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
237
545
  */
238
- function resolveRef(refMap, ref) {
239
- return refMap.get(ref);
546
+ function toCamelOrPascal(text, pascal) {
547
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
548
+ if (word.length > 1 && word === word.toUpperCase()) return word;
549
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
550
+ return word.charAt(0).toUpperCase() + word.slice(1);
551
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
240
552
  }
241
553
  /**
242
- * Converts a `RefMap` to a plain object.
554
+ * Splits `text` on `.` and applies `transformPart` to each segment.
555
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
556
+ * Segments are joined with `/` to form a file path.
557
+ *
558
+ * Only splits on dots followed by a letter so that version numbers
559
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
243
560
  */
244
- function refMapToObject(refMap) {
245
- return Object.fromEntries(refMap);
561
+ function applyToFileParts(text, transformPart) {
562
+ const parts = text.split(/\.(?=[a-zA-Z])/);
563
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
564
+ }
565
+ /**
566
+ * Converts `text` to camelCase.
567
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
568
+ *
569
+ * @example
570
+ * camelCase('hello-world') // 'helloWorld'
571
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
572
+ */
573
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
574
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
575
+ prefix,
576
+ suffix
577
+ } : {}));
578
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
579
+ }
580
+ /**
581
+ * Converts `text` to PascalCase.
582
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
583
+ *
584
+ * @example
585
+ * pascalCase('hello-world') // 'HelloWorld'
586
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
587
+ */
588
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
589
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
590
+ prefix,
591
+ suffix
592
+ }) : camelCase(part));
593
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
246
594
  }
247
595
  //#endregion
248
- //#region src/utils.ts
249
- const plainStringTypes = new Set([
250
- "string",
251
- "uuid",
252
- "email",
253
- "url",
254
- "datetime"
255
- ]);
596
+ //#region ../../internals/utils/src/reserved.ts
256
597
  /**
257
- * Returns `true` when a schema node will be represented as a plain string in generated code.
598
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
258
599
  *
259
- * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
260
- * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
600
+ * @example
601
+ * ```ts
602
+ * isValidVarName('status') // true
603
+ * isValidVarName('class') // false (reserved word)
604
+ * isValidVarName('42foo') // false (starts with digit)
605
+ * ```
261
606
  */
262
- function isPlainStringType(node) {
263
- if (plainStringTypes.has(node.type)) return true;
264
- const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
265
- if (temporal) return temporal.representation !== "date";
266
- return false;
607
+ function isValidVarName(name) {
608
+ try {
609
+ new Function(`var ${name}`);
610
+ } catch {
611
+ return false;
612
+ }
613
+ return true;
267
614
  }
268
615
  //#endregion
269
616
  //#region src/visitor.ts
617
+ /**
618
+ * Creates a small async concurrency limiter.
619
+ *
620
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * const limit = createLimit(2)
625
+ * for (const task of [taskA, taskB, taskC]) {
626
+ * await limit(() => task())
627
+ * }
628
+ * // only 2 tasks run at the same time
629
+ * ```
630
+ */
270
631
  function createLimit(concurrency) {
271
632
  let active = 0;
272
633
  const queue = [];
@@ -289,14 +650,24 @@ function createLimit(concurrency) {
289
650
  };
290
651
  }
291
652
  /**
292
- * Traversable children of `node`, respecting `recurse` for schema nodes.
653
+ * Returns the immediate traversable children of `node`.
654
+ *
655
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
656
+ * `additionalProperties`) are only included
657
+ * when `recurse` is `true`; shallow mode skips them.
658
+ *
659
+ * @example
660
+ * ```ts
661
+ * const children = getChildren(operationNode, true)
662
+ * // returns parameters, requestBody schema (if present), and responses
663
+ * ```
293
664
  */
294
665
  function getChildren(node, recurse) {
295
666
  switch (node.kind) {
296
667
  case "Root": return [...node.schemas, ...node.operations];
297
668
  case "Operation": return [
298
669
  ...node.parameters,
299
- ...node.requestBody ? [node.requestBody] : [],
670
+ ...node.requestBody?.schema ? [node.requestBody.schema] : [],
300
671
  ...node.responses
301
672
  ];
302
673
  case "Schema": {
@@ -305,140 +676,717 @@ function getChildren(node, recurse) {
305
676
  if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
306
677
  if ("items" in node && node.items) children.push(...node.items);
307
678
  if ("members" in node && node.members) children.push(...node.members);
679
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
308
680
  return children;
309
681
  }
310
682
  case "Property": return [node.schema];
311
683
  case "Parameter": return [node.schema];
312
684
  case "Response": return node.schema ? [node.schema] : [];
685
+ case "FunctionParameter":
686
+ case "ParameterGroup":
687
+ case "FunctionParameters":
688
+ case "Type": return [];
313
689
  }
314
690
  }
315
691
  /**
316
692
  * Depth-first traversal for side effects. Visitor return values are ignored.
317
- * Sibling nodes at each level are visited concurrently up to `options.concurrency` (default: 30).
693
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
694
+ * (default: `WALK_CONCURRENCY`).
695
+ *
696
+ * @example
697
+ * ```ts
698
+ * await walk(root, {
699
+ * operation(node) {
700
+ * console.log(node.operationId)
701
+ * },
702
+ * })
703
+ * ```
704
+ *
705
+ * @example
706
+ * ```ts
707
+ * // Visit only the current node
708
+ * await walk(root, { depth: 'shallow', root: () => {} })
709
+ * ```
318
710
  */
319
- async function walk(node, visitor, options = {}) {
320
- return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
711
+ async function walk(node, options) {
712
+ return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
321
713
  }
322
- async function _walk(node, visitor, recurse, limit) {
714
+ async function _walk(node, visitor, recurse, limit, parent) {
323
715
  switch (node.kind) {
324
716
  case "Root":
325
- await limit(() => visitor.root?.(node));
717
+ await limit(() => visitor.root?.(node, { parent }));
326
718
  break;
327
719
  case "Operation":
328
- await limit(() => visitor.operation?.(node));
720
+ await limit(() => visitor.operation?.(node, { parent }));
329
721
  break;
330
722
  case "Schema":
331
- await limit(() => visitor.schema?.(node));
723
+ await limit(() => visitor.schema?.(node, { parent }));
332
724
  break;
333
725
  case "Property":
334
- await limit(() => visitor.property?.(node));
726
+ await limit(() => visitor.property?.(node, { parent }));
335
727
  break;
336
728
  case "Parameter":
337
- await limit(() => visitor.parameter?.(node));
729
+ await limit(() => visitor.parameter?.(node, { parent }));
338
730
  break;
339
731
  case "Response":
340
- await limit(() => visitor.response?.(node));
732
+ await limit(() => visitor.response?.(node, { parent }));
341
733
  break;
734
+ case "FunctionParameter":
735
+ case "ParameterGroup":
736
+ case "FunctionParameters": break;
342
737
  }
343
738
  const children = getChildren(node, recurse);
344
- await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
739
+ for (const child of children) await _walk(child, visitor, recurse, limit, node);
345
740
  }
346
- function transform(node, visitor, options = {}) {
347
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
741
+ function transform(node, options) {
742
+ const { depth, parent, ...visitor } = options;
743
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
348
744
  switch (node.kind) {
349
745
  case "Root": {
350
746
  let root = node;
351
- const replaced = visitor.root?.(root);
747
+ const replaced = visitor.root?.(root, { parent });
352
748
  if (replaced) root = replaced;
353
749
  return {
354
750
  ...root,
355
- schemas: root.schemas.map((s) => transform(s, visitor, options)),
356
- operations: root.operations.map((op) => transform(op, visitor, options))
751
+ schemas: root.schemas.map((s) => transform(s, {
752
+ ...options,
753
+ parent: root
754
+ })),
755
+ operations: root.operations.map((op) => transform(op, {
756
+ ...options,
757
+ parent: root
758
+ }))
357
759
  };
358
760
  }
359
761
  case "Operation": {
360
762
  let op = node;
361
- const replaced = visitor.operation?.(op);
763
+ const replaced = visitor.operation?.(op, { parent });
362
764
  if (replaced) op = replaced;
363
765
  return {
364
766
  ...op,
365
- parameters: op.parameters.map((p) => transform(p, visitor, options)),
366
- requestBody: op.requestBody ? transform(op.requestBody, visitor, options) : void 0,
367
- responses: op.responses.map((r) => transform(r, visitor, options))
767
+ parameters: op.parameters.map((p) => transform(p, {
768
+ ...options,
769
+ parent: op
770
+ })),
771
+ requestBody: op.requestBody ? {
772
+ ...op.requestBody,
773
+ schema: op.requestBody.schema ? transform(op.requestBody.schema, {
774
+ ...options,
775
+ parent: op
776
+ }) : void 0
777
+ } : void 0,
778
+ responses: op.responses.map((r) => transform(r, {
779
+ ...options,
780
+ parent: op
781
+ }))
368
782
  };
369
783
  }
370
784
  case "Schema": {
371
785
  let schema = node;
372
- const replaced = visitor.schema?.(schema);
786
+ const replaced = visitor.schema?.(schema, { parent });
373
787
  if (replaced) schema = replaced;
788
+ const childOptions = {
789
+ ...options,
790
+ parent: schema
791
+ };
374
792
  return {
375
793
  ...schema,
376
- ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, visitor, options)) } : {},
377
- ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, visitor, options)) } : {},
378
- ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, visitor, options)) } : {}
794
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
795
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
796
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
797
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
379
798
  };
380
799
  }
381
800
  case "Property": {
382
801
  let prop = node;
383
- const replaced = visitor.property?.(prop);
802
+ const replaced = visitor.property?.(prop, { parent });
384
803
  if (replaced) prop = replaced;
385
- return {
804
+ return createProperty({
386
805
  ...prop,
387
- schema: transform(prop.schema, visitor, options)
388
- };
806
+ schema: transform(prop.schema, {
807
+ ...options,
808
+ parent: prop
809
+ })
810
+ });
389
811
  }
390
812
  case "Parameter": {
391
813
  let param = node;
392
- const replaced = visitor.parameter?.(param);
814
+ const replaced = visitor.parameter?.(param, { parent });
393
815
  if (replaced) param = replaced;
394
- return {
816
+ return createParameter({
395
817
  ...param,
396
- schema: transform(param.schema, visitor, options)
397
- };
818
+ schema: transform(param.schema, {
819
+ ...options,
820
+ parent: param
821
+ })
822
+ });
398
823
  }
399
824
  case "Response": {
400
825
  let response = node;
401
- const replaced = visitor.response?.(response);
826
+ const replaced = visitor.response?.(response, { parent });
402
827
  if (replaced) response = replaced;
403
828
  return {
404
829
  ...response,
405
- schema: response.schema ? transform(response.schema, visitor, options) : void 0
830
+ schema: transform(response.schema, {
831
+ ...options,
832
+ parent: response
833
+ })
406
834
  };
407
835
  }
836
+ case "FunctionParameter":
837
+ case "ParameterGroup":
838
+ case "FunctionParameters":
839
+ case "Type": return node;
408
840
  }
409
841
  }
410
842
  /**
411
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
843
+ * Composes multiple visitors into one visitor, applied left to right.
844
+ *
845
+ * For each node kind, output from one visitor is input to the next.
846
+ * If a visitor returns `undefined`, the previous node value is kept.
847
+ *
848
+ * @example
849
+ * ```ts
850
+ * const visitor = composeTransformers(
851
+ * { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
852
+ * { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
853
+ * )
854
+ * ```
412
855
  */
413
- function collect(node, visitor, options = {}) {
414
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
856
+ function composeTransformers(...visitors) {
857
+ return {
858
+ root(node, context) {
859
+ return visitors.reduce((acc, v) => v.root?.(acc, context) ?? acc, node);
860
+ },
861
+ operation(node, context) {
862
+ return visitors.reduce((acc, v) => v.operation?.(acc, context) ?? acc, node);
863
+ },
864
+ schema(node, context) {
865
+ return visitors.reduce((acc, v) => v.schema?.(acc, context) ?? acc, node);
866
+ },
867
+ property(node, context) {
868
+ return visitors.reduce((acc, v) => v.property?.(acc, context) ?? acc, node);
869
+ },
870
+ parameter(node, context) {
871
+ return visitors.reduce((acc, v) => v.parameter?.(acc, context) ?? acc, node);
872
+ },
873
+ response(node, context) {
874
+ return visitors.reduce((acc, v) => v.response?.(acc, context) ?? acc, node);
875
+ }
876
+ };
877
+ }
878
+ /**
879
+ * Runs a depth-first synchronous collection pass.
880
+ *
881
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
882
+ *
883
+ * @example
884
+ * ```ts
885
+ * const ids = collect(root, {
886
+ * operation(node) {
887
+ * return node.operationId
888
+ * },
889
+ * })
890
+ * ```
891
+ *
892
+ * @example
893
+ * ```ts
894
+ * // Collect from only the current node
895
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
896
+ * ```
897
+ */
898
+ function collect(node, options) {
899
+ const { depth, parent, ...visitor } = options;
900
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
415
901
  const results = [];
416
902
  let v;
417
903
  switch (node.kind) {
418
904
  case "Root":
419
- v = visitor.root?.(node);
905
+ v = visitor.root?.(node, { parent });
420
906
  break;
421
907
  case "Operation":
422
- v = visitor.operation?.(node);
908
+ v = visitor.operation?.(node, { parent });
423
909
  break;
424
910
  case "Schema":
425
- v = visitor.schema?.(node);
911
+ v = visitor.schema?.(node, { parent });
426
912
  break;
427
913
  case "Property":
428
- v = visitor.property?.(node);
914
+ v = visitor.property?.(node, { parent });
429
915
  break;
430
916
  case "Parameter":
431
- v = visitor.parameter?.(node);
917
+ v = visitor.parameter?.(node, { parent });
432
918
  break;
433
919
  case "Response":
434
- v = visitor.response?.(node);
920
+ v = visitor.response?.(node, { parent });
435
921
  break;
922
+ case "FunctionParameter":
923
+ case "ParameterGroup":
924
+ case "FunctionParameters": break;
436
925
  }
437
926
  if (v !== void 0) results.push(v);
438
- for (const child of getChildren(node, recurse)) for (const item of collect(child, visitor, options)) results.push(item);
927
+ for (const child of getChildren(node, recurse)) for (const item of collect(child, {
928
+ ...options,
929
+ parent: node
930
+ })) results.push(item);
439
931
  return results;
440
932
  }
441
933
  //#endregion
442
- export { buildRefMap, collect, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, definePrinter, httpMethods, isOperationNode, isParameterNode, isPlainStringType, isPropertyNode, isResponseNode, isRootNode, isSchemaNode, mediaTypes, narrowSchema, nodeKinds, refMapToObject, resolveRef, schemaTypes, transform, walk };
934
+ //#region src/resolvers.ts
935
+ function findDiscriminator(mapping, ref) {
936
+ if (!mapping || !ref) return null;
937
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
938
+ }
939
+ function childName(parentName, propName) {
940
+ return parentName ? pascalCase([parentName, propName].join(" ")) : null;
941
+ }
942
+ function enumPropName(parentName, propName, enumSuffix) {
943
+ return pascalCase([
944
+ parentName,
945
+ propName,
946
+ enumSuffix
947
+ ].filter(Boolean).join(" "));
948
+ }
949
+ /**
950
+ * Collects import entries for all `ref` schema nodes in `node`.
951
+ */
952
+ function collectImports({ node, nameMapping, resolve }) {
953
+ return collect(node, { schema(schemaNode) {
954
+ const schemaRef = narrowSchema(schemaNode, "ref");
955
+ if (!schemaRef?.ref) return;
956
+ const rawName = extractRefName(schemaRef.ref);
957
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
958
+ if (!result) return;
959
+ return result;
960
+ } });
961
+ }
962
+ //#endregion
963
+ //#region src/transformers.ts
964
+ /**
965
+ * Replaces a discriminator property's schema with a string enum of allowed values.
966
+ *
967
+ * If `node` is not an object schema, or if the property does not exist, the input
968
+ * node is returned as-is.
969
+ *
970
+ * @example
971
+ * ```ts
972
+ * const schema = createSchema({
973
+ * type: 'object',
974
+ * properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
975
+ * })
976
+ * const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
977
+ * ```
978
+ */
979
+ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
980
+ const objectNode = narrowSchema(node, "object");
981
+ if (!objectNode?.properties?.length) return node;
982
+ if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
983
+ return createSchema({
984
+ ...objectNode,
985
+ properties: objectNode.properties.map((prop) => {
986
+ if (prop.name !== propertyName) return prop;
987
+ return createProperty({
988
+ ...prop,
989
+ schema: createSchema({
990
+ type: "enum",
991
+ primitive: "string",
992
+ enumValues: values,
993
+ name: enumName,
994
+ readOnly: prop.schema.readOnly,
995
+ writeOnly: prop.schema.writeOnly
996
+ })
997
+ });
998
+ })
999
+ });
1000
+ }
1001
+ /**
1002
+ * Merges adjacent anonymous object members into a single anonymous object member.
1003
+ *
1004
+ * @example
1005
+ * ```ts
1006
+ * const merged = mergeAdjacentObjects([
1007
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
1008
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
1009
+ * ])
1010
+ * ```
1011
+ */
1012
+ function mergeAdjacentObjects(members) {
1013
+ return members.reduce((acc, member) => {
1014
+ const objectMember = narrowSchema(member, "object");
1015
+ if (objectMember && !objectMember.name) {
1016
+ const previous = acc.at(-1);
1017
+ const previousObject = previous ? narrowSchema(previous, "object") : void 0;
1018
+ if (previousObject && !previousObject.name) {
1019
+ acc[acc.length - 1] = createSchema({
1020
+ ...previousObject,
1021
+ properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
1022
+ });
1023
+ return acc;
1024
+ }
1025
+ }
1026
+ acc.push(member);
1027
+ return acc;
1028
+ }, []);
1029
+ }
1030
+ /**
1031
+ * Removes enum members that are covered by broader scalar primitives in the same union.
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * const simplified = simplifyUnion([
1036
+ * createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
1037
+ * createSchema({ type: 'string' }),
1038
+ * ])
1039
+ * // keeps only string member
1040
+ * ```
1041
+ */
1042
+ function simplifyUnion(members) {
1043
+ const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
1044
+ if (!scalarPrimitives.size) return members;
1045
+ return members.filter((member) => {
1046
+ const enumNode = narrowSchema(member, "enum");
1047
+ if (!enumNode) return true;
1048
+ const primitive = enumNode.primitive;
1049
+ if (!primitive) return true;
1050
+ if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
1051
+ if (scalarPrimitives.has(primitive)) return false;
1052
+ if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1053
+ return true;
1054
+ });
1055
+ }
1056
+ function setEnumName(propNode, parentName, propName, enumSuffix) {
1057
+ const enumNode = narrowSchema(propNode, "enum");
1058
+ if (enumNode?.primitive === "boolean") return {
1059
+ ...propNode,
1060
+ name: void 0
1061
+ };
1062
+ if (enumNode) return {
1063
+ ...propNode,
1064
+ name: enumPropName(parentName, propName, enumSuffix)
1065
+ };
1066
+ return propNode;
1067
+ }
1068
+ //#endregion
1069
+ //#region src/utils.ts
1070
+ const plainStringTypes = new Set([
1071
+ "string",
1072
+ "uuid",
1073
+ "email",
1074
+ "url",
1075
+ "datetime"
1076
+ ]);
1077
+ /**
1078
+ * Returns a merged schema view for a ref node, combining the resolved `node.schema`
1079
+ * (base from the referenced definition) with any usage-site sibling fields set directly
1080
+ * on the ref node (description, readOnly, nullable, deprecated, etc.).
1081
+ *
1082
+ * Usage-site fields take precedence over the resolved schema's own fields when both are defined.
1083
+ *
1084
+ * For non-ref nodes the node itself is returned unchanged.
1085
+ */
1086
+ function syncSchemaRef(node) {
1087
+ const ref = narrowSchema(node, "ref");
1088
+ if (!ref) return node;
1089
+ if (!ref.schema) return node;
1090
+ const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
1091
+ const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
1092
+ return createSchema({
1093
+ ...ref.schema,
1094
+ ...definedOverrides
1095
+ });
1096
+ }
1097
+ /**
1098
+ * Returns `true` when a schema is emitted as a plain `string` type.
1099
+ *
1100
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
1101
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
1102
+ *
1103
+ * @example
1104
+ * ```ts
1105
+ * isStringType(createSchema({ type: 'uuid' })) // true
1106
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
1107
+ * ```
1108
+ */
1109
+ function isStringType(node) {
1110
+ if (plainStringTypes.has(node.type)) return true;
1111
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
1112
+ if (temporal) return temporal.representation !== "date";
1113
+ return false;
1114
+ }
1115
+ /**
1116
+ * Applies casing rules to parameter names and returns a new parameter array.
1117
+ *
1118
+ * The input array is not mutated.
1119
+ * If `casing` is not set, the original array is returned unchanged.
1120
+ *
1121
+ * Use this before passing parameters to schema builders so that property keys
1122
+ * in generated output match the desired casing while preserving
1123
+ * `OperationNode.parameters` for other consumers.
1124
+ *
1125
+ * @example
1126
+ * ```ts
1127
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
1128
+ * const cased = caseParams(params, 'camelcase')
1129
+ * // cased[0].name === 'petId'
1130
+ * ```
1131
+ */
1132
+ function caseParams(params, casing) {
1133
+ if (!casing) return params;
1134
+ return params.map((param) => {
1135
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
1136
+ return {
1137
+ ...param,
1138
+ name: transformed
1139
+ };
1140
+ });
1141
+ }
1142
+ /**
1143
+ * Creates a single-property object schema used as a discriminator literal.
1144
+ *
1145
+ * @example
1146
+ * ```ts
1147
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
1148
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
1149
+ * ```
1150
+ */
1151
+ function createDiscriminantNode({ propertyName, value }) {
1152
+ return createSchema({
1153
+ type: "object",
1154
+ primitive: "object",
1155
+ properties: [createProperty({
1156
+ name: propertyName,
1157
+ schema: createSchema({
1158
+ type: "enum",
1159
+ primitive: "string",
1160
+ enumValues: [value]
1161
+ }),
1162
+ required: true
1163
+ })]
1164
+ });
1165
+ }
1166
+ function resolveType({ node, param, resolver }) {
1167
+ if (!resolver) return createTypeNode({
1168
+ variant: "reference",
1169
+ name: param.schema.primitive ?? "unknown"
1170
+ });
1171
+ const individualName = resolver.resolveParamName(node, param);
1172
+ const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
1173
+ const groupResolvers = {
1174
+ path: resolver.resolvePathParamsName,
1175
+ query: resolver.resolveQueryParamsName,
1176
+ header: resolver.resolveHeaderParamsName
1177
+ };
1178
+ const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
1179
+ if (groupName && groupName !== individualName) return createTypeNode({
1180
+ variant: "member",
1181
+ base: groupName,
1182
+ key: param.name
1183
+ });
1184
+ return createTypeNode({
1185
+ variant: "reference",
1186
+ name: individualName
1187
+ });
1188
+ }
1189
+ /**
1190
+ * Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
1191
+ *
1192
+ * Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
1193
+ * type resolution and `extraParams` for plugin-specific trailing parameters.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * const params = createOperationParams(node, {
1198
+ * paramsType: 'inline',
1199
+ * pathParamsType: 'inline',
1200
+ * resolver: tsResolver,
1201
+ * extraParams: [createFunctionParameter({ name: 'options', type: createTypeNode({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
1202
+ * })
1203
+ * ```
1204
+ */
1205
+ function createOperationParams(node, options) {
1206
+ const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
1207
+ const dataName = paramNames?.data ?? "data";
1208
+ const paramsName = paramNames?.params ?? "params";
1209
+ const headersName = paramNames?.headers ?? "headers";
1210
+ const pathName = paramNames?.path ?? "pathParams";
1211
+ const wrapType = (type) => createTypeNode({
1212
+ variant: "reference",
1213
+ name: typeWrapper ? typeWrapper(type) : type
1214
+ });
1215
+ const wrapTypeNode = (type) => type.variant === "reference" ? wrapType(type.name) : type;
1216
+ const casedParams = caseParams(node.parameters, paramsCasing);
1217
+ const pathParams = casedParams.filter((p) => p.in === "path");
1218
+ const queryParams = casedParams.filter((p) => p.in === "query");
1219
+ const headerParams = casedParams.filter((p) => p.in === "header");
1220
+ const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
1221
+ const bodyRequired = node.requestBody?.required ?? false;
1222
+ const queryGroupType = resolver ? resolveGroupType({
1223
+ node,
1224
+ params: queryParams,
1225
+ groupMethod: resolver.resolveQueryParamsName,
1226
+ resolver
1227
+ }) : void 0;
1228
+ const headerGroupType = resolver ? resolveGroupType({
1229
+ node,
1230
+ params: headerParams,
1231
+ groupMethod: resolver.resolveHeaderParamsName,
1232
+ resolver
1233
+ }) : void 0;
1234
+ const params = [];
1235
+ if (paramsType === "object") {
1236
+ const children = [
1237
+ ...pathParams.map((p) => {
1238
+ const type = resolveType({
1239
+ node,
1240
+ param: p,
1241
+ resolver
1242
+ });
1243
+ return createFunctionParameter({
1244
+ name: p.name,
1245
+ type: wrapTypeNode(type),
1246
+ optional: !p.required
1247
+ });
1248
+ }),
1249
+ ...bodyType ? [createFunctionParameter({
1250
+ name: dataName,
1251
+ type: bodyType,
1252
+ optional: !bodyRequired
1253
+ })] : [],
1254
+ ...buildGroupParam({
1255
+ name: paramsName,
1256
+ node,
1257
+ params: queryParams,
1258
+ groupType: queryGroupType,
1259
+ resolver,
1260
+ wrapType
1261
+ }),
1262
+ ...buildGroupParam({
1263
+ name: headersName,
1264
+ node,
1265
+ params: headerParams,
1266
+ groupType: headerGroupType,
1267
+ resolver,
1268
+ wrapType
1269
+ })
1270
+ ];
1271
+ if (children.length) params.push(createParameterGroup({
1272
+ properties: children,
1273
+ default: children.every((c) => c.optional) ? "{}" : void 0
1274
+ }));
1275
+ } else {
1276
+ if (pathParams.length) if (pathParamsType === "inlineSpread") {
1277
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
1278
+ params.push(createFunctionParameter({
1279
+ name: pathName,
1280
+ type: spreadType ? wrapType(spreadType) : void 0,
1281
+ rest: true
1282
+ }));
1283
+ } else {
1284
+ const pathChildren = pathParams.map((p) => {
1285
+ const type = resolveType({
1286
+ node,
1287
+ param: p,
1288
+ resolver
1289
+ });
1290
+ return createFunctionParameter({
1291
+ name: p.name,
1292
+ type: wrapTypeNode(type),
1293
+ optional: !p.required
1294
+ });
1295
+ });
1296
+ params.push(createParameterGroup({
1297
+ properties: pathChildren,
1298
+ inline: pathParamsType === "inline",
1299
+ default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
1300
+ }));
1301
+ }
1302
+ if (bodyType) params.push(createFunctionParameter({
1303
+ name: dataName,
1304
+ type: bodyType,
1305
+ optional: !bodyRequired
1306
+ }));
1307
+ params.push(...buildGroupParam({
1308
+ name: paramsName,
1309
+ node,
1310
+ params: queryParams,
1311
+ groupType: queryGroupType,
1312
+ resolver,
1313
+ wrapType
1314
+ }));
1315
+ params.push(...buildGroupParam({
1316
+ name: headersName,
1317
+ node,
1318
+ params: headerParams,
1319
+ groupType: headerGroupType,
1320
+ resolver,
1321
+ wrapType
1322
+ }));
1323
+ }
1324
+ params.push(...extraParams);
1325
+ return createFunctionParameters({ params });
1326
+ }
1327
+ /**
1328
+ * Builds a single {@link FunctionParameterNode} for a query or header group.
1329
+ * Returns an empty array when there are no params to emit.
1330
+ *
1331
+ * If a pre-resolved `groupType` is provided it emits `name: GroupType`.
1332
+ * Otherwise, it builds an inline struct from the individual params.
1333
+ */
1334
+ function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
1335
+ if (groupType) return [createFunctionParameter({
1336
+ name,
1337
+ type: groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
1338
+ optional: groupType.optional
1339
+ })];
1340
+ if (params.length) return [createFunctionParameter({
1341
+ name,
1342
+ type: toStructType({
1343
+ node,
1344
+ params,
1345
+ resolver
1346
+ }),
1347
+ optional: params.every((p) => !p.required)
1348
+ })];
1349
+ return [];
1350
+ }
1351
+ /**
1352
+ * Derives a {@link ParamGroupType} from the resolver's group method.
1353
+ * Returns `undefined` when the group name equals the individual param name (no real group).
1354
+ */
1355
+ function resolveGroupType({ node, params, groupMethod, resolver }) {
1356
+ if (!params.length) return;
1357
+ const firstParam = params[0];
1358
+ const groupName = groupMethod.call(resolver, node, firstParam);
1359
+ if (groupName === resolver.resolveParamName(node, firstParam)) return;
1360
+ const allOptional = params.every((p) => !p.required);
1361
+ return {
1362
+ type: createTypeNode({
1363
+ variant: "reference",
1364
+ name: groupName
1365
+ }),
1366
+ optional: allOptional
1367
+ };
1368
+ }
1369
+ /**
1370
+ * Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
1371
+ *
1372
+ * Used when query or header parameters have no dedicated group type name.
1373
+ * Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
1374
+ */
1375
+ function toStructType({ node, params, resolver }) {
1376
+ return createTypeNode({
1377
+ variant: "struct",
1378
+ properties: params.map((p) => ({
1379
+ name: p.name,
1380
+ optional: !p.required,
1381
+ type: resolveType({
1382
+ node,
1383
+ param: p,
1384
+ resolver
1385
+ })
1386
+ }))
1387
+ });
1388
+ }
1389
+ //#endregion
1390
+ export { caseParams, childName, collect, collectImports, composeTransformers, createDiscriminantNode, createFunctionParameter, createFunctionParameters, createOperation, createOperationParams, createParameter, createParameterGroup, createPrinterFactory, createProperty, createResponse, createRoot, createSchema, createTypeNode, definePrinter, enumPropName, extractRefName, findDiscriminator, httpMethods, isOperationNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, walk };
443
1391
 
444
1392
  //# sourceMappingURL=index.js.map