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