@kubb/ast 5.0.0-alpha.4 → 5.0.0-alpha.40

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,64 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- Object.defineProperty;
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
3
23
  //#endregion
24
+ let node_crypto = require("node:crypto");
25
+ let node_path = require("node:path");
26
+ node_path = __toESM(node_path, 1);
4
27
  //#region src/constants.ts
5
28
  const visitorDepths = {
6
29
  shallow: "shallow",
7
30
  deep: "deep"
8
31
  };
9
32
  const nodeKinds = {
10
- root: "Root",
33
+ input: "Input",
34
+ output: "Output",
11
35
  operation: "Operation",
12
36
  schema: "Schema",
13
37
  property: "Property",
14
38
  parameter: "Parameter",
15
- response: "Response"
39
+ response: "Response",
40
+ functionParameter: "FunctionParameter",
41
+ parameterGroup: "ParameterGroup",
42
+ functionParameters: "FunctionParameters",
43
+ type: "Type",
44
+ file: "File",
45
+ import: "Import",
46
+ export: "Export",
47
+ source: "Source",
48
+ text: "Text",
49
+ break: "Break"
16
50
  };
51
+ /**
52
+ * Canonical schema type strings used by AST schema nodes.
53
+ *
54
+ * These values are used across the AST as stable discriminators
55
+ * (for example `schema.type === schemaTypes.object`).
56
+ *
57
+ * The map is grouped by intent:
58
+ * - primitives (`string`, `number`, `boolean`, ...)
59
+ * - structural/composite (`object`, `array`, `union`, ...)
60
+ * - special OpenAPI-oriented types (`ref`, `datetime`, `uuid`, ...)
61
+ */
17
62
  const schemaTypes = {
18
63
  string: "string",
19
64
  number: "number",
@@ -37,8 +82,27 @@ const schemaTypes = {
37
82
  uuid: "uuid",
38
83
  email: "email",
39
84
  url: "url",
40
- blob: "blob"
85
+ ipv4: "ipv4",
86
+ ipv6: "ipv6",
87
+ blob: "blob",
88
+ never: "never"
41
89
  };
90
+ /**
91
+ * Primitive scalar schema types used when simplifying union members.
92
+ */
93
+ const SCALAR_PRIMITIVE_TYPES = new Set([
94
+ "string",
95
+ "number",
96
+ "integer",
97
+ "bigint",
98
+ "boolean"
99
+ ]);
100
+ /**
101
+ * Returns `true` when `type` is a scalar primitive schema type.
102
+ */
103
+ function isScalarPrimitive(type) {
104
+ return SCALAR_PRIMITIVE_TYPES.has(type);
105
+ }
42
106
  const httpMethods = {
43
107
  get: "GET",
44
108
  post: "POST",
@@ -71,204 +135,1353 @@ const mediaTypes = {
71
135
  videoMp4: "video/mp4"
72
136
  };
73
137
  //#endregion
138
+ //#region ../../internals/utils/src/casing.ts
139
+ /**
140
+ * Shared implementation for camelCase and PascalCase conversion.
141
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
142
+ * and capitalizes each word according to `pascal`.
143
+ *
144
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
145
+ */
146
+ function toCamelOrPascal(text, pascal) {
147
+ 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) => {
148
+ if (word.length > 1 && word === word.toUpperCase()) return word;
149
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
150
+ return word.charAt(0).toUpperCase() + word.slice(1);
151
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
152
+ }
153
+ /**
154
+ * Splits `text` on `.` and applies `transformPart` to each segment.
155
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
156
+ * Segments are joined with `/` to form a file path.
157
+ *
158
+ * Only splits on dots followed by a letter so that version numbers
159
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
160
+ */
161
+ function applyToFileParts(text, transformPart) {
162
+ const parts = text.split(/\.(?=[a-zA-Z])/);
163
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
164
+ }
165
+ /**
166
+ * Converts `text` to camelCase.
167
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
168
+ *
169
+ * @example
170
+ * camelCase('hello-world') // 'helloWorld'
171
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
172
+ */
173
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
174
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
175
+ prefix,
176
+ suffix
177
+ } : {}));
178
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
179
+ }
180
+ /**
181
+ * Converts `text` to PascalCase.
182
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
183
+ *
184
+ * @example
185
+ * pascalCase('hello-world') // 'HelloWorld'
186
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
187
+ */
188
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
189
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
190
+ prefix,
191
+ suffix
192
+ }) : camelCase(part));
193
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
194
+ }
195
+ //#endregion
196
+ //#region ../../internals/utils/src/reserved.ts
197
+ /**
198
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
199
+ *
200
+ * @example
201
+ * ```ts
202
+ * isValidVarName('status') // true
203
+ * isValidVarName('class') // false (reserved word)
204
+ * isValidVarName('42foo') // false (starts with digit)
205
+ * ```
206
+ */
207
+ function isValidVarName(name) {
208
+ try {
209
+ new Function(`var ${name}`);
210
+ } catch {
211
+ return false;
212
+ }
213
+ return true;
214
+ }
215
+ //#endregion
216
+ //#region ../../internals/utils/src/string.ts
217
+ /**
218
+ * Strips the file extension from a path or file name.
219
+ * Only removes the last `.ext` segment when the dot is not part of a directory name.
220
+ *
221
+ * @example
222
+ * trimExtName('petStore.ts') // 'petStore'
223
+ * trimExtName('/src/models/pet.ts') // '/src/models/pet'
224
+ * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
225
+ * trimExtName('noExtension') // 'noExtension'
226
+ */
227
+ function trimExtName(text) {
228
+ const dotIndex = text.lastIndexOf(".");
229
+ if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
230
+ return text;
231
+ }
232
+ //#endregion
233
+ //#region src/guards.ts
234
+ /**
235
+ * Narrows a `SchemaNode` to the variant that matches `type`.
236
+ *
237
+ * @example
238
+ * ```ts
239
+ * const schema = createSchema({ type: 'string' })
240
+ * const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | undefined
241
+ * ```
242
+ */
243
+ function narrowSchema(node, type) {
244
+ return node?.type === type ? node : void 0;
245
+ }
246
+ function isKind(kind) {
247
+ return (node) => node.kind === kind;
248
+ }
249
+ /**
250
+ * Returns `true` when the input is an `InputNode`.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * if (isInputNode(node)) {
255
+ * console.log(node.schemas.length)
256
+ * }
257
+ * ```
258
+ */
259
+ const isInputNode = isKind("Input");
260
+ /**
261
+ * Returns `true` when the input is an `OutputNode`.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * if (isOutputNode(node)) {
266
+ * console.log(node.files.length)
267
+ * }
268
+ * ```
269
+ */
270
+ const isOutputNode = isKind("Output");
271
+ /**
272
+ * Returns `true` when the input is an `OperationNode`.
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * if (isOperationNode(node)) {
277
+ * console.log(node.operationId)
278
+ * }
279
+ * ```
280
+ */
281
+ const isOperationNode = isKind("Operation");
282
+ /**
283
+ * Returns `true` when the input is a `SchemaNode`.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * if (isSchemaNode(node)) {
288
+ * console.log(node.type)
289
+ * }
290
+ * ```
291
+ */
292
+ const isSchemaNode = isKind("Schema");
293
+ isKind("Property");
294
+ isKind("Parameter");
295
+ isKind("Response");
296
+ isKind("FunctionParameter");
297
+ isKind("ParameterGroup");
298
+ isKind("FunctionParameters");
299
+ //#endregion
300
+ //#region src/utils.ts
301
+ const plainStringTypes = new Set([
302
+ "string",
303
+ "uuid",
304
+ "email",
305
+ "url",
306
+ "datetime"
307
+ ]);
308
+ /**
309
+ * Returns a merged schema view for a ref node, combining the resolved `node.schema`
310
+ * (base from the referenced definition) with any usage-site sibling fields set directly
311
+ * on the ref node (description, readOnly, nullable, deprecated, etc.).
312
+ *
313
+ * Usage-site fields take precedence over the resolved schema's own fields when both are defined.
314
+ *
315
+ * For non-ref nodes the node itself is returned unchanged.
316
+ */
317
+ function syncSchemaRef(node) {
318
+ const ref = narrowSchema(node, "ref");
319
+ if (!ref) return node;
320
+ if (!ref.schema) return node;
321
+ const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
322
+ const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
323
+ return createSchema({
324
+ ...ref.schema,
325
+ ...definedOverrides
326
+ });
327
+ }
328
+ /**
329
+ * Returns `true` when a schema is emitted as a plain `string` type.
330
+ *
331
+ * - `string`, `uuid`, `email`, `url`, `datetime` are always plain strings.
332
+ * - `date` and `time` are plain strings when their `representation` is `'string'` rather than `'date'`.
333
+ *
334
+ * @example
335
+ * ```ts
336
+ * isStringType(createSchema({ type: 'uuid' })) // true
337
+ * isStringType(createSchema({ type: 'date', representation: 'date' })) // false
338
+ * ```
339
+ */
340
+ function isStringType(node) {
341
+ if (plainStringTypes.has(node.type)) return true;
342
+ const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
343
+ if (temporal) return temporal.representation !== "date";
344
+ return false;
345
+ }
346
+ /**
347
+ * Applies casing rules to parameter names and returns a new parameter array.
348
+ *
349
+ * The input array is not mutated.
350
+ * If `casing` is not set, the original array is returned unchanged.
351
+ *
352
+ * Use this before passing parameters to schema builders so that property keys
353
+ * in generated output match the desired casing while preserving
354
+ * `OperationNode.parameters` for other consumers.
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * const params = [createParameter({ name: 'pet_id', in: 'query', schema: createSchema({ type: 'string' }) })]
359
+ * const cased = caseParams(params, 'camelcase')
360
+ * // cased[0].name === 'petId'
361
+ * ```
362
+ */
363
+ function caseParams(params, casing) {
364
+ if (!casing) return params;
365
+ return params.map((param) => {
366
+ const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
367
+ return {
368
+ ...param,
369
+ name: transformed
370
+ };
371
+ });
372
+ }
373
+ /**
374
+ * Creates a single-property object schema used as a discriminator literal.
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
379
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
380
+ * ```
381
+ */
382
+ function createDiscriminantNode({ propertyName, value }) {
383
+ return createSchema({
384
+ type: "object",
385
+ primitive: "object",
386
+ properties: [createProperty({
387
+ name: propertyName,
388
+ schema: createSchema({
389
+ type: "enum",
390
+ primitive: "string",
391
+ enumValues: [value]
392
+ }),
393
+ required: true
394
+ })]
395
+ });
396
+ }
397
+ function resolveParamsType({ node, param, resolver }) {
398
+ if (!resolver) return createParamsType({
399
+ variant: "reference",
400
+ name: param.schema.primitive ?? "unknown"
401
+ });
402
+ const individualName = resolver.resolveParamName(node, param);
403
+ const groupLocation = param.in === "path" || param.in === "query" || param.in === "header" ? param.in : void 0;
404
+ const groupResolvers = {
405
+ path: resolver.resolvePathParamsName,
406
+ query: resolver.resolveQueryParamsName,
407
+ header: resolver.resolveHeaderParamsName
408
+ };
409
+ const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : void 0;
410
+ if (groupName && groupName !== individualName) return createParamsType({
411
+ variant: "member",
412
+ base: groupName,
413
+ key: param.name
414
+ });
415
+ return createParamsType({
416
+ variant: "reference",
417
+ name: individualName
418
+ });
419
+ }
420
+ /**
421
+ * Converts an {@link OperationNode} into a {@link FunctionParametersNode}.
422
+ *
423
+ * Centralizes the per-plugin `getParams()` pattern. Provide a `resolver` for
424
+ * type resolution and `extraParams` for plugin-specific trailing parameters.
425
+ *
426
+ * @example
427
+ * ```ts
428
+ * const params = createOperationParams(node, {
429
+ * paramsType: 'inline',
430
+ * pathParamsType: 'inline',
431
+ * resolver: tsResolver,
432
+ * extraParams: [createFunctionParameter({ name: 'options', type: createParamsType({ variant: 'reference', name: 'Partial<RequestOptions>' }), default: '{}' })],
433
+ * })
434
+ * ```
435
+ */
436
+ function createOperationParams(node, options) {
437
+ const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options;
438
+ const dataName = paramNames?.data ?? "data";
439
+ const paramsName = paramNames?.params ?? "params";
440
+ const headersName = paramNames?.headers ?? "headers";
441
+ const pathName = paramNames?.path ?? "pathParams";
442
+ const wrapType = (type) => createParamsType({
443
+ variant: "reference",
444
+ name: typeWrapper ? typeWrapper(type) : type
445
+ });
446
+ const wrapTypeNode = (type) => type.kind === "ParamsType" && type.variant === "reference" ? wrapType(type.name) : type;
447
+ const casedParams = caseParams(node.parameters, paramsCasing);
448
+ const pathParams = casedParams.filter((p) => p.in === "path");
449
+ const queryParams = casedParams.filter((p) => p.in === "query");
450
+ const headerParams = casedParams.filter((p) => p.in === "header");
451
+ const bodyType = node.requestBody?.schema ? wrapType(resolver?.resolveDataName(node) ?? "unknown") : void 0;
452
+ const bodyRequired = node.requestBody?.required ?? false;
453
+ const queryGroupType = resolver ? resolveGroupType({
454
+ node,
455
+ params: queryParams,
456
+ groupMethod: resolver.resolveQueryParamsName,
457
+ resolver
458
+ }) : void 0;
459
+ const headerGroupType = resolver ? resolveGroupType({
460
+ node,
461
+ params: headerParams,
462
+ groupMethod: resolver.resolveHeaderParamsName,
463
+ resolver
464
+ }) : void 0;
465
+ const params = [];
466
+ if (paramsType === "object") {
467
+ const children = [
468
+ ...pathParams.map((p) => {
469
+ const type = resolveParamsType({
470
+ node,
471
+ param: p,
472
+ resolver
473
+ });
474
+ return createFunctionParameter({
475
+ name: p.name,
476
+ type: wrapTypeNode(type),
477
+ optional: !p.required
478
+ });
479
+ }),
480
+ ...bodyType ? [createFunctionParameter({
481
+ name: dataName,
482
+ type: bodyType,
483
+ optional: !bodyRequired
484
+ })] : [],
485
+ ...buildGroupParam({
486
+ name: paramsName,
487
+ node,
488
+ params: queryParams,
489
+ groupType: queryGroupType,
490
+ resolver,
491
+ wrapType
492
+ }),
493
+ ...buildGroupParam({
494
+ name: headersName,
495
+ node,
496
+ params: headerParams,
497
+ groupType: headerGroupType,
498
+ resolver,
499
+ wrapType
500
+ })
501
+ ];
502
+ if (children.length) params.push(createParameterGroup({
503
+ properties: children,
504
+ default: children.every((c) => c.optional) ? "{}" : void 0
505
+ }));
506
+ } else {
507
+ if (pathParams.length) if (pathParamsType === "inlineSpread") {
508
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]) ?? void 0;
509
+ params.push(createFunctionParameter({
510
+ name: pathName,
511
+ type: spreadType ? wrapType(spreadType) : void 0,
512
+ rest: true
513
+ }));
514
+ } else {
515
+ const pathChildren = pathParams.map((p) => {
516
+ const type = resolveParamsType({
517
+ node,
518
+ param: p,
519
+ resolver
520
+ });
521
+ return createFunctionParameter({
522
+ name: p.name,
523
+ type: wrapTypeNode(type),
524
+ optional: !p.required
525
+ });
526
+ });
527
+ params.push(createParameterGroup({
528
+ properties: pathChildren,
529
+ inline: pathParamsType === "inline",
530
+ default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? "{}" : void 0)
531
+ }));
532
+ }
533
+ if (bodyType) params.push(createFunctionParameter({
534
+ name: dataName,
535
+ type: bodyType,
536
+ optional: !bodyRequired
537
+ }));
538
+ params.push(...buildGroupParam({
539
+ name: paramsName,
540
+ node,
541
+ params: queryParams,
542
+ groupType: queryGroupType,
543
+ resolver,
544
+ wrapType
545
+ }));
546
+ params.push(...buildGroupParam({
547
+ name: headersName,
548
+ node,
549
+ params: headerParams,
550
+ groupType: headerGroupType,
551
+ resolver,
552
+ wrapType
553
+ }));
554
+ }
555
+ params.push(...extraParams);
556
+ return createFunctionParameters({ params });
557
+ }
558
+ /**
559
+ * Builds a single {@link FunctionParameterNode} for a query or header group.
560
+ * Returns an empty array when there are no params to emit.
561
+ *
562
+ * If a pre-resolved `groupType` is provided it emits `name: GroupType`.
563
+ * Otherwise, it builds an inline struct from the individual params.
564
+ */
565
+ function buildGroupParam({ name, node, params, groupType, resolver, wrapType }) {
566
+ if (groupType) return [createFunctionParameter({
567
+ name,
568
+ type: groupType.type.kind === "ParamsType" && groupType.type.variant === "reference" ? wrapType(groupType.type.name) : groupType.type,
569
+ optional: groupType.optional
570
+ })];
571
+ if (params.length) return [createFunctionParameter({
572
+ name,
573
+ type: toStructType({
574
+ node,
575
+ params,
576
+ resolver
577
+ }),
578
+ optional: params.every((p) => !p.required)
579
+ })];
580
+ return [];
581
+ }
582
+ /**
583
+ * Derives a {@link ParamGroupType} from the resolver's group method.
584
+ * Returns `undefined` when the group name equals the individual param name (no real group).
585
+ */
586
+ function resolveGroupType({ node, params, groupMethod, resolver }) {
587
+ if (!params.length) return;
588
+ const firstParam = params[0];
589
+ const groupName = groupMethod.call(resolver, node, firstParam);
590
+ if (groupName === resolver.resolveParamName(node, firstParam)) return;
591
+ const allOptional = params.every((p) => !p.required);
592
+ return {
593
+ type: createParamsType({
594
+ variant: "reference",
595
+ name: groupName
596
+ }),
597
+ optional: allOptional
598
+ };
599
+ }
600
+ /**
601
+ * Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
602
+ *
603
+ * Used when query or header parameters have no dedicated group type name.
604
+ * Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
605
+ */
606
+ function toStructType({ node, params, resolver }) {
607
+ return createParamsType({
608
+ variant: "struct",
609
+ properties: params.map((p) => ({
610
+ name: p.name,
611
+ optional: !p.required,
612
+ type: resolveParamsType({
613
+ node,
614
+ param: p,
615
+ resolver
616
+ })
617
+ }))
618
+ });
619
+ }
620
+ function sourceKey(source) {
621
+ return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
622
+ }
623
+ function pathTypeKey(path, isTypeOnly) {
624
+ return `${path}:${isTypeOnly ?? false}`;
625
+ }
626
+ function exportKey(path, name, isTypeOnly, asAlias) {
627
+ return `${path}:${name ?? ""}:${isTypeOnly ?? false}:${asAlias ?? ""}`;
628
+ }
629
+ function importKey(path, name, isTypeOnly) {
630
+ return `${path}:${name ?? ""}:${isTypeOnly ?? false}`;
631
+ }
632
+ /**
633
+ * Computes a multi-level sort key for exports and imports:
634
+ * non-array names first (wildcards/namespace aliases); type-only before value; alphabetical path; unnamed before named.
635
+ */
636
+ function sortKey(node) {
637
+ const isArray = Array.isArray(node.name) ? "1" : "0";
638
+ const typeOnly = node.isTypeOnly ? "0" : "1";
639
+ const hasName = node.name != null ? "1" : "0";
640
+ const name = Array.isArray(node.name) ? [...node.name].sort().join("\0") : node.name ?? "";
641
+ return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`;
642
+ }
643
+ /**
644
+ * Deduplicates an array of `SourceNode` objects.
645
+ * Named sources are deduplicated by `name + isExportable + isTypeOnly`.
646
+ * Unnamed sources are deduplicated by object reference.
647
+ */
648
+ function combineSources(sources) {
649
+ const seen = /* @__PURE__ */ new Map();
650
+ for (const source of sources) {
651
+ const key = sourceKey(source);
652
+ if (!seen.has(key)) seen.set(key, source);
653
+ }
654
+ return [...seen.values()];
655
+ }
656
+ /**
657
+ * Deduplicates and merges an array of `ExportNode` objects.
658
+ * Exports with the same path and `isTypeOnly` flag have their names merged.
659
+ */
660
+ function combineExports(exports) {
661
+ const result = [];
662
+ const namedByPath = /* @__PURE__ */ new Map();
663
+ const seen = /* @__PURE__ */ new Set();
664
+ for (const curr of [...exports].sort((a, b) => {
665
+ const ka = sortKey(a);
666
+ const kb = sortKey(b);
667
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
668
+ })) {
669
+ const { name, path, isTypeOnly, asAlias } = curr;
670
+ if (Array.isArray(name)) {
671
+ if (!name.length) continue;
672
+ const key = pathTypeKey(path, isTypeOnly);
673
+ const existing = namedByPath.get(key);
674
+ if (existing && Array.isArray(existing.name)) existing.name = [...new Set([...existing.name, ...name])];
675
+ else {
676
+ const newItem = {
677
+ ...curr,
678
+ name: [...new Set(name)]
679
+ };
680
+ result.push(newItem);
681
+ namedByPath.set(key, newItem);
682
+ }
683
+ } else {
684
+ const key = exportKey(path, name, isTypeOnly, asAlias);
685
+ if (!seen.has(key)) {
686
+ result.push(curr);
687
+ seen.add(key);
688
+ }
689
+ }
690
+ }
691
+ return result;
692
+ }
693
+ /**
694
+ * Deduplicates and merges an array of `ImportNode` objects.
695
+ * Filters out unused imports (names not referenced in `source` or re-exported).
696
+ * Imports with the same path and `isTypeOnly` flag have their names merged.
697
+ */
698
+ function combineImports(imports, exports, source) {
699
+ const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
700
+ const isUsed = (importName) => !source || source.includes(importName) || exportedNames.has(importName);
701
+ const result = [];
702
+ const namedByPath = /* @__PURE__ */ new Map();
703
+ const seen = /* @__PURE__ */ new Set();
704
+ for (const curr of [...imports].sort((a, b) => {
705
+ const ka = sortKey(a);
706
+ const kb = sortKey(b);
707
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
708
+ })) {
709
+ if (curr.path === curr.root) continue;
710
+ const { path, isTypeOnly } = curr;
711
+ let { name } = curr;
712
+ if (Array.isArray(name)) {
713
+ name = [...new Set(name)].filter((item) => typeof item === "string" ? isUsed(item) : isUsed(item.propertyName));
714
+ if (!name.length) continue;
715
+ const key = pathTypeKey(path, isTypeOnly);
716
+ const existing = namedByPath.get(key);
717
+ if (existing && Array.isArray(existing.name)) existing.name = [...new Set([...existing.name, ...name])];
718
+ else {
719
+ const newItem = {
720
+ ...curr,
721
+ name
722
+ };
723
+ result.push(newItem);
724
+ namedByPath.set(key, newItem);
725
+ }
726
+ } else {
727
+ if (name && !isUsed(name)) continue;
728
+ const key = importKey(path, name, isTypeOnly);
729
+ if (!seen.has(key)) {
730
+ result.push(curr);
731
+ seen.add(key);
732
+ }
733
+ }
734
+ }
735
+ return result;
736
+ }
737
+ /**
738
+ * Recursively extracts all string content embedded in a {@link CodeNode} tree.
739
+ *
740
+ * Includes text node values, and string attribute fields (`params`, `generics`,
741
+ * `returnType`, `type`) that may reference identifiers needing imports.
742
+ * Used by `createFile` to build the full source string for import filtering.
743
+ */
744
+ function extractStringsFromNodes(nodes) {
745
+ if (!nodes?.length) return "";
746
+ return nodes.map((node) => {
747
+ if (typeof node === "string") return node;
748
+ if (node.kind === "Text") return node.value;
749
+ if (node.kind === "Break") return "";
750
+ if (node.kind === "Jsx") return node.value;
751
+ const parts = [];
752
+ if ("params" in node && node.params) parts.push(node.params);
753
+ if ("generics" in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(", ") : node.generics);
754
+ if ("returnType" in node && node.returnType) parts.push(node.returnType);
755
+ if ("type" in node && typeof node.type === "string") parts.push(node.type);
756
+ const nested = extractStringsFromNodes(node.nodes);
757
+ if (nested) parts.push(nested);
758
+ return parts.join("\n");
759
+ }).filter(Boolean).join("\n");
760
+ }
761
+ //#endregion
74
762
  //#region src/factory.ts
75
763
  /**
76
- * Creates a `RootNode`.
764
+ * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
765
+ *
766
+ * - `optional` is set for non-required, non-nullable schemas.
767
+ * - `nullish` is set for non-required, nullable schemas.
768
+ */
769
+ function syncOptionality(schema, required) {
770
+ const nullable = schema.nullable ?? false;
771
+ return {
772
+ ...schema,
773
+ optional: !required && !nullable ? true : void 0,
774
+ nullish: !required && nullable ? true : void 0
775
+ };
776
+ }
777
+ /**
778
+ * Creates an `InputNode` with stable defaults for `schemas` and `operations`.
779
+ *
780
+ * @example
781
+ * ```ts
782
+ * const input = createInput()
783
+ * // { kind: 'Input', schemas: [], operations: [] }
784
+ * ```
785
+ *
786
+ * @example
787
+ * ```ts
788
+ * const input = createInput({ schemas: [petSchema] })
789
+ * // keeps default operations: []
790
+ * ```
77
791
  */
78
- function createRoot(overrides = {}) {
792
+ function createInput(overrides = {}) {
79
793
  return {
80
794
  schemas: [],
81
795
  operations: [],
82
796
  ...overrides,
83
- kind: "Root"
797
+ kind: "Input"
798
+ };
799
+ }
800
+ /**
801
+ * Creates an `OutputNode` with a stable default for `files`.
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * const output = createOutput()
806
+ * // { kind: 'Output', files: [] }
807
+ * ```
808
+ *
809
+ * @example
810
+ * ```ts
811
+ * const output = createOutput({ files: [petFile] })
812
+ * ```
813
+ */
814
+ function createOutput(overrides = {}) {
815
+ return {
816
+ files: [],
817
+ ...overrides,
818
+ kind: "Output"
819
+ };
820
+ }
821
+ /**
822
+ * Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
823
+ *
824
+ * @example
825
+ * ```ts
826
+ * const operation = createOperation({
827
+ * operationId: 'getPetById',
828
+ * method: 'GET',
829
+ * path: '/pet/{petId}',
830
+ * })
831
+ * // tags, parameters, and responses are []
832
+ * ```
833
+ *
834
+ * @example
835
+ * ```ts
836
+ * const operation = createOperation({
837
+ * operationId: 'findPets',
838
+ * method: 'GET',
839
+ * path: '/pet/findByStatus',
840
+ * tags: ['pet'],
841
+ * })
842
+ * ```
843
+ */
844
+ function createOperation(props) {
845
+ return {
846
+ tags: [],
847
+ parameters: [],
848
+ responses: [],
849
+ ...props,
850
+ kind: "Operation"
851
+ };
852
+ }
853
+ /**
854
+ * Maps schema `type` to its underlying `primitive`.
855
+ * Primitive types map to themselves; special string formats map to `'string'`.
856
+ * Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
857
+ */
858
+ const TYPE_TO_PRIMITIVE = {
859
+ string: "string",
860
+ number: "number",
861
+ integer: "integer",
862
+ bigint: "bigint",
863
+ boolean: "boolean",
864
+ null: "null",
865
+ any: "any",
866
+ unknown: "unknown",
867
+ void: "void",
868
+ never: "never",
869
+ object: "object",
870
+ array: "array",
871
+ date: "date",
872
+ uuid: "string",
873
+ email: "string",
874
+ url: "string",
875
+ datetime: "string",
876
+ time: "string"
877
+ };
878
+ function createSchema(props) {
879
+ const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type];
880
+ if (props["type"] === "object") return {
881
+ properties: [],
882
+ primitive: "object",
883
+ ...props,
884
+ kind: "Schema"
885
+ };
886
+ return {
887
+ primitive: inferredPrimitive,
888
+ ...props,
889
+ kind: "Schema"
890
+ };
891
+ }
892
+ /**
893
+ * Creates a `PropertyNode`.
894
+ *
895
+ * `required` defaults to `false`.
896
+ * `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
897
+ *
898
+ * @example
899
+ * ```ts
900
+ * const property = createProperty({
901
+ * name: 'status',
902
+ * schema: createSchema({ type: 'string' }),
903
+ * })
904
+ * // required=false, schema.optional=true
905
+ * ```
906
+ *
907
+ * @example
908
+ * ```ts
909
+ * const property = createProperty({
910
+ * name: 'status',
911
+ * required: true,
912
+ * schema: createSchema({ type: 'string', nullable: true }),
913
+ * })
914
+ * // required=true, no optional/nullish
915
+ * ```
916
+ */
917
+ function createProperty(props) {
918
+ const required = props.required ?? false;
919
+ return {
920
+ ...props,
921
+ kind: "Property",
922
+ required,
923
+ schema: syncOptionality(props.schema, required)
924
+ };
925
+ }
926
+ /**
927
+ * Creates a `ParameterNode`.
928
+ *
929
+ * `required` defaults to `false`.
930
+ * Nested schema flags are set from `required` and `schema.nullable`.
931
+ *
932
+ * @example
933
+ * ```ts
934
+ * const param = createParameter({
935
+ * name: 'petId',
936
+ * in: 'path',
937
+ * required: true,
938
+ * schema: createSchema({ type: 'string' }),
939
+ * })
940
+ * ```
941
+ *
942
+ * @example
943
+ * ```ts
944
+ * const param = createParameter({
945
+ * name: 'status',
946
+ * in: 'query',
947
+ * schema: createSchema({ type: 'string', nullable: true }),
948
+ * })
949
+ * // required=false, schema.nullish=true
950
+ * ```
951
+ */
952
+ function createParameter(props) {
953
+ const required = props.required ?? false;
954
+ return {
955
+ ...props,
956
+ kind: "Parameter",
957
+ required,
958
+ schema: syncOptionality(props.schema, required)
959
+ };
960
+ }
961
+ /**
962
+ * Creates a `ResponseNode`.
963
+ *
964
+ * @example
965
+ * ```ts
966
+ * const response = createResponse({
967
+ * statusCode: '200',
968
+ * description: 'Success',
969
+ * schema: createSchema({ type: 'object', properties: [] }),
970
+ * })
971
+ * ```
972
+ */
973
+ function createResponse(props) {
974
+ return {
975
+ ...props,
976
+ kind: "Response"
977
+ };
978
+ }
979
+ /**
980
+ * Creates a `FunctionParameterNode`.
981
+ *
982
+ * `optional` defaults to `false`.
983
+ *
984
+ * @example Required typed param
985
+ * ```ts
986
+ * createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }) })
987
+ * // → petId: string
988
+ * ```
989
+ *
990
+ * @example Optional param
991
+ * ```ts
992
+ * createFunctionParameter({ name: 'params', type: createParamsType({ variant: 'reference', name: 'QueryParams' }), optional: true })
993
+ * // → params?: QueryParams
994
+ * ```
995
+ *
996
+ * @example Param with default (implicitly optional; cannot combine with `optional: true`)
997
+ * ```ts
998
+ * createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
999
+ * // → config: RequestConfig = {}
1000
+ * ```
1001
+ */
1002
+ function createFunctionParameter(props) {
1003
+ return {
1004
+ optional: false,
1005
+ ...props,
1006
+ kind: "FunctionParameter"
1007
+ };
1008
+ }
1009
+ /**
1010
+ * Creates a {@link TypeNode} representing a language-agnostic structured type expression.
1011
+ *
1012
+ * Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
1013
+ * named field accessed from a group type. Each language's printer renders the variant
1014
+ * into its own syntax (TypeScript, Python, C#, Kotlin, …).
1015
+ *
1016
+ * @example Reference type (TypeScript: `QueryParams`)
1017
+ * ```ts
1018
+ * createParamsType({ variant: 'reference', name: 'QueryParams' })
1019
+ * ```
1020
+ *
1021
+ * @example Struct type (TypeScript: `{ petId: string }`)
1022
+ * ```ts
1023
+ * createParamsType({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createParamsType({ variant: 'reference', name: 'string' }) }] })
1024
+ * ```
1025
+ *
1026
+ * @example Member type (TypeScript: `DeletePetPathParams['petId']`)
1027
+ * ```ts
1028
+ * createParamsType({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
1029
+ * ```
1030
+ */
1031
+ function createParamsType(props) {
1032
+ return {
1033
+ ...props,
1034
+ kind: "ParamsType"
1035
+ };
1036
+ }
1037
+ /**
1038
+ * Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
1039
+ *
1040
+ * @example Grouped param (TypeScript declaration)
1041
+ * ```ts
1042
+ * createParameterGroup({
1043
+ * properties: [
1044
+ * createFunctionParameter({ name: 'id', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
1045
+ * createFunctionParameter({ name: 'name', type: createParamsType({ variant: 'reference', name: 'string' }), optional: true }),
1046
+ * ],
1047
+ * default: '{}',
1048
+ * })
1049
+ * // declaration → { id, name? }: { id: string; name?: string } = {}
1050
+ * // call → { id, name }
1051
+ * ```
1052
+ *
1053
+ * @example Inline (spread) — children emitted as individual top-level parameters
1054
+ * ```ts
1055
+ * createParameterGroup({
1056
+ * properties: [createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false })],
1057
+ * inline: true,
1058
+ * })
1059
+ * // declaration → petId: string
1060
+ * // call → petId
1061
+ * ```
1062
+ */
1063
+ function createParameterGroup(props) {
1064
+ return {
1065
+ ...props,
1066
+ kind: "ParameterGroup"
1067
+ };
1068
+ }
1069
+ /**
1070
+ * Creates a `FunctionParametersNode` from an ordered list of parameters.
1071
+ *
1072
+ * @example
1073
+ * ```ts
1074
+ * createFunctionParameters({
1075
+ * params: [
1076
+ * createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
1077
+ * createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
1078
+ * ],
1079
+ * })
1080
+ * ```
1081
+ *
1082
+ * @example
1083
+ * ```ts
1084
+ * const empty = createFunctionParameters()
1085
+ * // { kind: 'FunctionParameters', params: [] }
1086
+ * ```
1087
+ */
1088
+ function createFunctionParameters(props = {}) {
1089
+ return {
1090
+ params: [],
1091
+ ...props,
1092
+ kind: "FunctionParameters"
84
1093
  };
85
1094
  }
86
1095
  /**
87
- * Creates an `OperationNode`.
1096
+ * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
1097
+ *
1098
+ * @example Named import
1099
+ * ```ts
1100
+ * createImport({ name: ['useState'], path: 'react' })
1101
+ * // import { useState } from 'react'
1102
+ * ```
1103
+ *
1104
+ * @example Type-only import
1105
+ * ```ts
1106
+ * createImport({ name: ['FC'], path: 'react', isTypeOnly: true })
1107
+ * // import type { FC } from 'react'
1108
+ * ```
88
1109
  */
89
- function createOperation(props) {
1110
+ function createImport(props) {
90
1111
  return {
91
- tags: [],
92
- parameters: [],
93
- responses: [],
94
1112
  ...props,
95
- kind: "Operation"
1113
+ kind: "Import"
96
1114
  };
97
1115
  }
98
- function createSchema(props) {
99
- if (props["type"] === "object") return {
100
- properties: [],
101
- ...props,
102
- kind: "Schema"
103
- };
1116
+ /**
1117
+ * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
1118
+ *
1119
+ * @example Named export
1120
+ * ```ts
1121
+ * createExport({ name: ['Pet'], path: './Pet' })
1122
+ * // export { Pet } from './Pet'
1123
+ * ```
1124
+ *
1125
+ * @example Wildcard export
1126
+ * ```ts
1127
+ * createExport({ path: './utils' })
1128
+ * // export * from './utils'
1129
+ * ```
1130
+ */
1131
+ function createExport(props) {
104
1132
  return {
105
1133
  ...props,
106
- kind: "Schema"
1134
+ kind: "Export"
107
1135
  };
108
1136
  }
109
1137
  /**
110
- * Creates a `PropertyNode`. `required` defaults to `false`.
1138
+ * Creates a `SourceNode` representing a fragment of source code within a file.
1139
+ *
1140
+ * @example
1141
+ * ```ts
1142
+ * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
1143
+ * ```
111
1144
  */
112
- function createProperty(props) {
1145
+ function createSource(props) {
113
1146
  return {
114
- required: false,
115
1147
  ...props,
116
- kind: "Property"
1148
+ kind: "Source"
117
1149
  };
118
1150
  }
119
1151
  /**
120
- * Creates a `ParameterNode`. `required` defaults to `false`.
1152
+ * Creates a fully resolved `FileNode` from a file input descriptor.
1153
+ *
1154
+ * Computes:
1155
+ * - `id` — SHA256 hash of the file path
1156
+ * - `name` — `baseName` without extension
1157
+ * - `extname` — extension extracted from `baseName`
1158
+ *
1159
+ * Deduplicates:
1160
+ * - `sources` via `combineSources`
1161
+ * - `exports` via `combineExports`
1162
+ * - `imports` via `combineImports` (also filters unused imports)
1163
+ *
1164
+ * @throws {Error} when `baseName` has no extension.
1165
+ *
1166
+ * @example
1167
+ * ```ts
1168
+ * const file = createFile({
1169
+ * baseName: 'petStore.ts',
1170
+ * path: 'src/models/petStore.ts',
1171
+ * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
1172
+ * imports: [createImport({ name: ['z'], path: 'zod' })],
1173
+ * exports: [createExport({ name: ['Pet'], path: './petStore' })],
1174
+ * })
1175
+ * // file.id = SHA256 hash of 'src/models/petStore.ts'
1176
+ * // file.name = 'petStore'
1177
+ * // file.extname = '.ts'
1178
+ * ```
121
1179
  */
122
- function createParameter(props) {
1180
+ function createFile(input) {
1181
+ const extname = node_path.default.extname(input.baseName) || (input.baseName.startsWith(".") ? input.baseName : "");
1182
+ if (!extname) throw new Error(`No extname found for ${input.baseName}`);
1183
+ const source = (input.sources ?? []).flatMap((item) => item.nodes ?? []).map((node) => extractStringsFromNodes([node])).filter(Boolean).join("\n\n");
1184
+ const resolvedExports = input.exports?.length ? combineExports(input.exports) : [];
1185
+ const resolvedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || void 0) : [];
1186
+ const resolvedSources = input.sources?.length ? combineSources(input.sources) : [];
123
1187
  return {
124
- required: false,
125
- ...props,
126
- kind: "Parameter"
1188
+ kind: "File",
1189
+ ...input,
1190
+ id: (0, node_crypto.createHash)("sha256").update(input.path).digest("hex"),
1191
+ name: trimExtName(input.baseName),
1192
+ extname,
1193
+ imports: resolvedImports,
1194
+ exports: resolvedExports,
1195
+ sources: resolvedSources,
1196
+ meta: input.meta ?? {}
127
1197
  };
128
1198
  }
129
1199
  /**
130
- * Creates a `ResponseNode`.
1200
+ * Creates a `ConstNode` representing a TypeScript `const` declaration.
1201
+ *
1202
+ * Mirrors the `Const` component from `@kubb/renderer-jsx`.
1203
+ * The component's `children` are represented as `nodes`.
1204
+ *
1205
+ * @example Simple constant
1206
+ * ```ts
1207
+ * createConst({ name: 'pet' })
1208
+ * // const pet = ...
1209
+ * ```
1210
+ *
1211
+ * @example Exported constant with type and `as const`
1212
+ * ```ts
1213
+ * createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
1214
+ * // export const pets: Pet[] = ... as const
1215
+ * ```
1216
+ *
1217
+ * @example With JSDoc and child nodes
1218
+ * ```ts
1219
+ * createConst({
1220
+ * name: 'config',
1221
+ * export: true,
1222
+ * JSDoc: { comments: ['@description App configuration'] },
1223
+ * nodes: [],
1224
+ * })
1225
+ * ```
131
1226
  */
132
- function createResponse(props) {
1227
+ function createConst(props) {
133
1228
  return {
134
1229
  ...props,
135
- kind: "Response"
1230
+ kind: "Const"
136
1231
  };
137
1232
  }
138
- //#endregion
139
- //#region src/guards.ts
140
1233
  /**
141
- * Narrows a `SchemaNode` to the specific variant matching `type`.
1234
+ * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
1235
+ *
1236
+ * Mirrors the `Type` component from `@kubb/renderer-jsx`.
1237
+ * The component's `children` are represented as `nodes`.
1238
+ *
1239
+ * @example Simple type alias
1240
+ * ```ts
1241
+ * createType({ name: 'Pet' })
1242
+ * // type Pet = ...
1243
+ * ```
1244
+ *
1245
+ * @example Exported type with JSDoc
1246
+ * ```ts
1247
+ * createType({
1248
+ * name: 'PetStatus',
1249
+ * export: true,
1250
+ * JSDoc: { comments: ['@description Status of a pet'] },
1251
+ * })
1252
+ * // export type PetStatus = ...
1253
+ * ```
142
1254
  */
143
- function narrowSchema(node, type) {
144
- return node?.type === type ? node : void 0;
145
- }
146
- function isKind(kind) {
147
- return (node) => node.kind === kind;
1255
+ function createType(props) {
1256
+ return {
1257
+ ...props,
1258
+ kind: "Type"
1259
+ };
148
1260
  }
149
1261
  /**
150
- * Type guard for `RootNode`.
151
- */
152
- const isRootNode = isKind("Root");
153
- /**
154
- * Type guard for `OperationNode`.
1262
+ * Creates a `FunctionNode` representing a TypeScript `function` declaration.
1263
+ *
1264
+ * Mirrors the `Function` component from `@kubb/renderer-jsx`.
1265
+ * The component's `children` are represented as `nodes`.
1266
+ *
1267
+ * @example Simple function
1268
+ * ```ts
1269
+ * createFunction({ name: 'getPet' })
1270
+ * // function getPet() { ... }
1271
+ * ```
1272
+ *
1273
+ * @example Exported async function with return type
1274
+ * ```ts
1275
+ * createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
1276
+ * // export async function fetchPet(): Promise<Pet> { ... }
1277
+ * ```
1278
+ *
1279
+ * @example Function with generics and params
1280
+ * ```ts
1281
+ * createFunction({
1282
+ * name: 'identity',
1283
+ * export: true,
1284
+ * generics: ['T'],
1285
+ * params: 'value: T',
1286
+ * returnType: 'T',
1287
+ * })
1288
+ * // export function identity<T>(value: T): T { ... }
1289
+ * ```
155
1290
  */
156
- const isOperationNode = isKind("Operation");
1291
+ function createFunction(props) {
1292
+ return {
1293
+ ...props,
1294
+ kind: "Function"
1295
+ };
1296
+ }
157
1297
  /**
158
- * Type guard for `SchemaNode`.
1298
+ * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
1299
+ *
1300
+ * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
1301
+ * The component's `children` are represented as `nodes`.
1302
+ *
1303
+ * @example Simple arrow function
1304
+ * ```ts
1305
+ * createArrowFunction({ name: 'getPet' })
1306
+ * // const getPet = () => { ... }
1307
+ * ```
1308
+ *
1309
+ * @example Single-line exported arrow function
1310
+ * ```ts
1311
+ * createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
1312
+ * // export const double = (n: number) => ...
1313
+ * ```
1314
+ *
1315
+ * @example Async arrow function with generics
1316
+ * ```ts
1317
+ * createArrowFunction({
1318
+ * name: 'fetchPet',
1319
+ * export: true,
1320
+ * async: true,
1321
+ * generics: ['T'],
1322
+ * params: 'id: string',
1323
+ * returnType: 'T',
1324
+ * })
1325
+ * // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
1326
+ * ```
159
1327
  */
160
- const isSchemaNode = isKind("Schema");
1328
+ function createArrowFunction(props) {
1329
+ return {
1330
+ ...props,
1331
+ kind: "ArrowFunction"
1332
+ };
1333
+ }
161
1334
  /**
162
- * Type guard for `PropertyNode`.
1335
+ * Creates a {@link TextNode} representing a raw string fragment in the source output.
1336
+ *
1337
+ * Use this instead of bare strings when building `nodes` arrays so that every
1338
+ * entry in the array is a typed {@link CodeNode}.
1339
+ *
1340
+ * @example
1341
+ * ```ts
1342
+ * createText('return fetch(id)')
1343
+ * // { kind: 'Text', value: 'return fetch(id)' }
1344
+ * ```
163
1345
  */
164
- const isPropertyNode = isKind("Property");
1346
+ function createText(value) {
1347
+ return {
1348
+ value,
1349
+ kind: "Text"
1350
+ };
1351
+ }
165
1352
  /**
166
- * Type guard for `ParameterNode`.
1353
+ * Creates a {@link BreakNode} representing a line break in the source output.
1354
+ *
1355
+ * Corresponds to `<br/>` in JSX components. Prints as an empty string which,
1356
+ * when joined with `\n` by `printNodes`, produces a blank line.
1357
+ *
1358
+ * @example
1359
+ * ```ts
1360
+ * createBreak()
1361
+ * // { kind: 'Break' }
1362
+ * ```
167
1363
  */
168
- const isParameterNode = isKind("Parameter");
1364
+ function createBreak() {
1365
+ return { kind: "Break" };
1366
+ }
169
1367
  /**
170
- * Type guard for `ResponseNode`.
1368
+ * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
1369
+ *
1370
+ * Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
1371
+ *
1372
+ * @example
1373
+ * ```ts
1374
+ * createJsx('<>\n <a href={href}>Open</a>\n</>')
1375
+ * // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
1376
+ * ```
171
1377
  */
172
- const isResponseNode = isKind("Response");
1378
+ function createJsx(value) {
1379
+ return {
1380
+ value,
1381
+ kind: "Jsx"
1382
+ };
1383
+ }
173
1384
  //#endregion
174
1385
  //#region src/printer.ts
175
1386
  /**
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
- * })
1387
+ * Creates a schema printer factory.
1388
+ *
1389
+ * This function wraps a builder and makes options optional at call sites.
1390
+ *
1391
+ * The builder receives resolved options and returns:
1392
+ * - `name` — a unique identifier for the printer
1393
+ * - `options` options stored on the returned printer instance
1394
+ * - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
1395
+ * - `print` _(optional)_ top-level override exposed as `printer.print`
1396
+ * - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
1397
+ * - This keeps recursion safe and avoids self-calls
1398
+ *
1399
+ * When no `print` override is provided, `printer.print` falls back to `printer.transform` (the node-level dispatcher).
1400
+ *
1401
+ * @example Basic usage — Zod schema printer
1402
+ * ```ts
1403
+ * type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
202
1404
  *
203
- * const printer = zodPrinter({ strict: false })
204
- * printer.name // 'zod'
205
- * printer.options // { strict: false }
206
- * printer.print(node) // 'z.string()'
1405
+ * export const zodPrinter = definePrinter<PrinterZod>((options) => ({
1406
+ * name: 'zod',
1407
+ * options: { strict: options.strict ?? true },
1408
+ * nodes: {
1409
+ * string: () => 'z.string()',
1410
+ * object(node) {
1411
+ * const props = node.properties.map(p => `${p.name}: ${this.transform(p.schema)}`).join(', ')
1412
+ * return `z.object({ ${props} })`
1413
+ * },
1414
+ * },
1415
+ * }))
207
1416
  * ```
208
1417
  */
209
1418
  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)
1419
+ return createPrinterFactory((node) => node.type)(build);
1420
+ }
1421
+ /**
1422
+ * Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
1423
+ **
1424
+ * @example
1425
+ * ```ts
1426
+ * export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>(
1427
+ * (node) => kindToHandlerKey[node.kind],
1428
+ * )
1429
+ * ```
1430
+ */
1431
+ function createPrinterFactory(getKey) {
1432
+ return function(build) {
1433
+ return (options) => {
1434
+ const { name, options: resolvedOptions, nodes, print: printOverride } = build(options ?? {});
1435
+ const context = {
1436
+ options: resolvedOptions,
1437
+ transform: (node) => {
1438
+ const key = getKey(node);
1439
+ if (key === void 0) return null;
1440
+ const handler = nodes[key];
1441
+ if (!handler) return null;
1442
+ return handler.call(context, node);
1443
+ }
1444
+ };
1445
+ return {
1446
+ name,
1447
+ options: resolvedOptions,
1448
+ transform: context.transform,
1449
+ print: printOverride ? printOverride.bind(context) : context.transform
1450
+ };
224
1451
  };
225
1452
  };
226
1453
  }
227
1454
  //#endregion
228
1455
  //#region src/refs.ts
229
1456
  /**
230
- * Indexes named schemas from `root.schemas` by name. Unnamed schemas are skipped.
231
- */
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;
236
- }
237
- /**
238
- * Looks up a schema by name. Prefer over `RefMap.get()` to keep the resolution strategy swappable.
239
- */
240
- function resolveRef(refMap, ref) {
241
- return refMap.get(ref);
242
- }
243
- /**
244
- * Converts a `RefMap` to a plain object.
1457
+ * Returns the last path segment of a reference string.
1458
+ *
1459
+ * Example: `#/components/schemas/Pet` becomes `Pet`.
1460
+ *
1461
+ * @example
1462
+ * ```ts
1463
+ * extractRefName('#/components/schemas/Pet') // 'Pet'
1464
+ * ```
245
1465
  */
246
- function refMapToObject(refMap) {
247
- return Object.fromEntries(refMap);
1466
+ function extractRefName(ref) {
1467
+ return ref.split("/").at(-1) ?? ref;
248
1468
  }
249
1469
  //#endregion
250
- //#region src/utils.ts
251
- const plainStringTypes = new Set([
252
- "string",
253
- "uuid",
254
- "email",
255
- "url",
256
- "datetime"
257
- ]);
1470
+ //#region src/visitor.ts
258
1471
  /**
259
- * Returns `true` when a schema node will be represented as a plain string in generated code.
1472
+ * Creates a small async concurrency limiter.
260
1473
  *
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'`.
1474
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
1475
+ *
1476
+ * @example
1477
+ * ```ts
1478
+ * const limit = createLimit(2)
1479
+ * for (const task of [taskA, taskB, taskC]) {
1480
+ * await limit(() => task())
1481
+ * }
1482
+ * // only 2 tasks run at the same time
1483
+ * ```
263
1484
  */
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;
269
- }
270
- //#endregion
271
- //#region src/visitor.ts
272
1485
  function createLimit(concurrency) {
273
1486
  let active = 0;
274
1487
  const queue = [];
@@ -291,14 +1504,25 @@ function createLimit(concurrency) {
291
1504
  };
292
1505
  }
293
1506
  /**
294
- * Traversable children of `node`, respecting `recurse` for schema nodes.
1507
+ * Returns the immediate traversable children of `node`.
1508
+ *
1509
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
1510
+ * `additionalProperties`) are only included
1511
+ * when `recurse` is `true`; shallow mode skips them.
1512
+ *
1513
+ * @example
1514
+ * ```ts
1515
+ * const children = getChildren(operationNode, true)
1516
+ * // returns parameters, requestBody schema (if present), and responses
1517
+ * ```
295
1518
  */
296
1519
  function getChildren(node, recurse) {
297
1520
  switch (node.kind) {
298
- case "Root": return [...node.schemas, ...node.operations];
1521
+ case "Input": return [...node.schemas, ...node.operations];
1522
+ case "Output": return [];
299
1523
  case "Operation": return [
300
1524
  ...node.parameters,
301
- ...node.requestBody ? [node.requestBody] : [],
1525
+ ...node.requestBody?.schema ? [node.requestBody.schema] : [],
302
1526
  ...node.responses
303
1527
  ];
304
1528
  case "Schema": {
@@ -307,163 +1531,425 @@ function getChildren(node, recurse) {
307
1531
  if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
308
1532
  if ("items" in node && node.items) children.push(...node.items);
309
1533
  if ("members" in node && node.members) children.push(...node.members);
1534
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
310
1535
  return children;
311
1536
  }
312
1537
  case "Property": return [node.schema];
313
1538
  case "Parameter": return [node.schema];
314
1539
  case "Response": return node.schema ? [node.schema] : [];
1540
+ case "FunctionParameter":
1541
+ case "ParameterGroup":
1542
+ case "FunctionParameters":
1543
+ case "Type": return [];
1544
+ default: return [];
315
1545
  }
316
1546
  }
317
1547
  /**
318
1548
  * 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).
1549
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
1550
+ * (default: `WALK_CONCURRENCY`).
1551
+ *
1552
+ * @example
1553
+ * ```ts
1554
+ * await walk(root, {
1555
+ * operation(node) {
1556
+ * console.log(node.operationId)
1557
+ * },
1558
+ * })
1559
+ * ```
1560
+ *
1561
+ * @example
1562
+ * ```ts
1563
+ * // Visit only the current node
1564
+ * await walk(root, { depth: 'shallow', root: () => {} })
1565
+ * ```
320
1566
  */
321
- async function walk(node, visitor, options = {}) {
322
- return _walk(node, visitor, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30));
1567
+ async function walk(node, options) {
1568
+ return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
323
1569
  }
324
- async function _walk(node, visitor, recurse, limit) {
1570
+ async function _walk(node, visitor, recurse, limit, parent) {
325
1571
  switch (node.kind) {
326
- case "Root":
327
- await limit(() => visitor.root?.(node));
1572
+ case "Input":
1573
+ await limit(() => visitor.input?.(node, { parent }));
1574
+ break;
1575
+ case "Output":
1576
+ await limit(() => visitor.output?.(node, { parent }));
328
1577
  break;
329
1578
  case "Operation":
330
- await limit(() => visitor.operation?.(node));
1579
+ await limit(() => visitor.operation?.(node, { parent }));
331
1580
  break;
332
1581
  case "Schema":
333
- await limit(() => visitor.schema?.(node));
1582
+ await limit(() => visitor.schema?.(node, { parent }));
334
1583
  break;
335
1584
  case "Property":
336
- await limit(() => visitor.property?.(node));
1585
+ await limit(() => visitor.property?.(node, { parent }));
337
1586
  break;
338
1587
  case "Parameter":
339
- await limit(() => visitor.parameter?.(node));
1588
+ await limit(() => visitor.parameter?.(node, { parent }));
340
1589
  break;
341
1590
  case "Response":
342
- await limit(() => visitor.response?.(node));
1591
+ await limit(() => visitor.response?.(node, { parent }));
343
1592
  break;
1593
+ case "FunctionParameter":
1594
+ case "ParameterGroup":
1595
+ case "FunctionParameters": break;
344
1596
  }
345
1597
  const children = getChildren(node, recurse);
346
- await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit)));
1598
+ for (const child of children) await _walk(child, visitor, recurse, limit, node);
347
1599
  }
348
- function transform(node, visitor, options = {}) {
349
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
1600
+ function transform(node, options) {
1601
+ const { depth, parent, ...visitor } = options;
1602
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
350
1603
  switch (node.kind) {
351
- case "Root": {
352
- let root = node;
353
- const replaced = visitor.root?.(root);
354
- if (replaced) root = replaced;
1604
+ case "Input": {
1605
+ let input = node;
1606
+ const replaced = visitor.input?.(input, { parent });
1607
+ if (replaced) input = replaced;
355
1608
  return {
356
- ...root,
357
- schemas: root.schemas.map((s) => transform(s, visitor, options)),
358
- operations: root.operations.map((op) => transform(op, visitor, options))
1609
+ ...input,
1610
+ schemas: input.schemas.map((s) => transform(s, {
1611
+ ...options,
1612
+ parent: input
1613
+ })),
1614
+ operations: input.operations.map((op) => transform(op, {
1615
+ ...options,
1616
+ parent: input
1617
+ }))
359
1618
  };
360
1619
  }
1620
+ case "Output": {
1621
+ let output = node;
1622
+ const replaced = visitor.output?.(output, { parent });
1623
+ if (replaced) output = replaced;
1624
+ return output;
1625
+ }
361
1626
  case "Operation": {
362
1627
  let op = node;
363
- const replaced = visitor.operation?.(op);
1628
+ const replaced = visitor.operation?.(op, { parent });
364
1629
  if (replaced) op = replaced;
365
1630
  return {
366
1631
  ...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))
1632
+ parameters: op.parameters.map((p) => transform(p, {
1633
+ ...options,
1634
+ parent: op
1635
+ })),
1636
+ requestBody: op.requestBody ? {
1637
+ ...op.requestBody,
1638
+ schema: op.requestBody.schema ? transform(op.requestBody.schema, {
1639
+ ...options,
1640
+ parent: op
1641
+ }) : void 0
1642
+ } : void 0,
1643
+ responses: op.responses.map((r) => transform(r, {
1644
+ ...options,
1645
+ parent: op
1646
+ }))
370
1647
  };
371
1648
  }
372
1649
  case "Schema": {
373
1650
  let schema = node;
374
- const replaced = visitor.schema?.(schema);
1651
+ const replaced = visitor.schema?.(schema, { parent });
375
1652
  if (replaced) schema = replaced;
1653
+ const childOptions = {
1654
+ ...options,
1655
+ parent: schema
1656
+ };
376
1657
  return {
377
1658
  ...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)) } : {}
1659
+ ..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
1660
+ ..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
1661
+ ..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
1662
+ ..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
381
1663
  };
382
1664
  }
383
1665
  case "Property": {
384
1666
  let prop = node;
385
- const replaced = visitor.property?.(prop);
1667
+ const replaced = visitor.property?.(prop, { parent });
386
1668
  if (replaced) prop = replaced;
387
- return {
1669
+ return createProperty({
388
1670
  ...prop,
389
- schema: transform(prop.schema, visitor, options)
390
- };
1671
+ schema: transform(prop.schema, {
1672
+ ...options,
1673
+ parent: prop
1674
+ })
1675
+ });
391
1676
  }
392
1677
  case "Parameter": {
393
1678
  let param = node;
394
- const replaced = visitor.parameter?.(param);
1679
+ const replaced = visitor.parameter?.(param, { parent });
395
1680
  if (replaced) param = replaced;
396
- return {
1681
+ return createParameter({
397
1682
  ...param,
398
- schema: transform(param.schema, visitor, options)
399
- };
1683
+ schema: transform(param.schema, {
1684
+ ...options,
1685
+ parent: param
1686
+ })
1687
+ });
400
1688
  }
401
1689
  case "Response": {
402
1690
  let response = node;
403
- const replaced = visitor.response?.(response);
1691
+ const replaced = visitor.response?.(response, { parent });
404
1692
  if (replaced) response = replaced;
405
1693
  return {
406
1694
  ...response,
407
- schema: response.schema ? transform(response.schema, visitor, options) : void 0
1695
+ schema: transform(response.schema, {
1696
+ ...options,
1697
+ parent: response
1698
+ })
408
1699
  };
409
1700
  }
1701
+ case "FunctionParameter":
1702
+ case "ParameterGroup":
1703
+ case "FunctionParameters":
1704
+ case "Type": return node;
1705
+ default: return node;
410
1706
  }
411
1707
  }
412
1708
  /**
413
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
1709
+ * Runs a depth-first synchronous collection pass.
1710
+ *
1711
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
1712
+ *
1713
+ * @example
1714
+ * ```ts
1715
+ * const ids = collect(root, {
1716
+ * operation(node) {
1717
+ * return node.operationId
1718
+ * },
1719
+ * })
1720
+ * ```
1721
+ *
1722
+ * @example
1723
+ * ```ts
1724
+ * // Collect from only the current node
1725
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
1726
+ * ```
414
1727
  */
415
- function collect(node, visitor, options = {}) {
416
- const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep;
1728
+ function collect(node, options) {
1729
+ const { depth, parent, ...visitor } = options;
1730
+ const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
417
1731
  const results = [];
418
1732
  let v;
419
1733
  switch (node.kind) {
420
- case "Root":
421
- v = visitor.root?.(node);
1734
+ case "Input":
1735
+ v = visitor.input?.(node, { parent });
1736
+ break;
1737
+ case "Output":
1738
+ v = visitor.output?.(node, { parent });
422
1739
  break;
423
1740
  case "Operation":
424
- v = visitor.operation?.(node);
1741
+ v = visitor.operation?.(node, { parent });
425
1742
  break;
426
1743
  case "Schema":
427
- v = visitor.schema?.(node);
1744
+ v = visitor.schema?.(node, { parent });
428
1745
  break;
429
1746
  case "Property":
430
- v = visitor.property?.(node);
1747
+ v = visitor.property?.(node, { parent });
431
1748
  break;
432
1749
  case "Parameter":
433
- v = visitor.parameter?.(node);
1750
+ v = visitor.parameter?.(node, { parent });
434
1751
  break;
435
1752
  case "Response":
436
- v = visitor.response?.(node);
1753
+ v = visitor.response?.(node, { parent });
437
1754
  break;
1755
+ case "FunctionParameter":
1756
+ case "ParameterGroup":
1757
+ case "FunctionParameters": break;
438
1758
  }
439
1759
  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);
1760
+ for (const child of getChildren(node, recurse)) for (const item of collect(child, {
1761
+ ...options,
1762
+ parent: node
1763
+ })) results.push(item);
441
1764
  return results;
442
1765
  }
443
1766
  //#endregion
444
- exports.buildRefMap = buildRefMap;
1767
+ //#region src/resolvers.ts
1768
+ function findDiscriminator(mapping, ref) {
1769
+ if (!mapping || !ref) return null;
1770
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
1771
+ }
1772
+ function childName(parentName, propName) {
1773
+ return parentName ? pascalCase([parentName, propName].join(" ")) : null;
1774
+ }
1775
+ function enumPropName(parentName, propName, enumSuffix) {
1776
+ return pascalCase([
1777
+ parentName,
1778
+ propName,
1779
+ enumSuffix
1780
+ ].filter(Boolean).join(" "));
1781
+ }
1782
+ /**
1783
+ * Collects import entries for all `ref` schema nodes in `node`.
1784
+ */
1785
+ function collectImports({ node, nameMapping, resolve }) {
1786
+ return collect(node, { schema(schemaNode) {
1787
+ const schemaRef = narrowSchema(schemaNode, "ref");
1788
+ if (!schemaRef?.ref) return;
1789
+ const rawName = extractRefName(schemaRef.ref);
1790
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
1791
+ if (!result) return;
1792
+ return result;
1793
+ } });
1794
+ }
1795
+ //#endregion
1796
+ //#region src/transformers.ts
1797
+ /**
1798
+ * Replaces a discriminator property's schema with a string enum of allowed values.
1799
+ *
1800
+ * If `node` is not an object schema, or if the property does not exist, the input
1801
+ * node is returned as-is.
1802
+ *
1803
+ * @example
1804
+ * ```ts
1805
+ * const schema = createSchema({
1806
+ * type: 'object',
1807
+ * properties: [createProperty({ name: 'type', required: true, schema: createSchema({ type: 'string' }) })],
1808
+ * })
1809
+ * const result = setDiscriminatorEnum({ node: schema, propertyName: 'type', values: ['dog', 'cat'] })
1810
+ * ```
1811
+ */
1812
+ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
1813
+ const objectNode = narrowSchema(node, "object");
1814
+ if (!objectNode?.properties?.length) return node;
1815
+ if (!objectNode.properties.some((prop) => prop.name === propertyName)) return node;
1816
+ return createSchema({
1817
+ ...objectNode,
1818
+ properties: objectNode.properties.map((prop) => {
1819
+ if (prop.name !== propertyName) return prop;
1820
+ return createProperty({
1821
+ ...prop,
1822
+ schema: createSchema({
1823
+ type: "enum",
1824
+ primitive: "string",
1825
+ enumValues: values,
1826
+ name: enumName,
1827
+ readOnly: prop.schema.readOnly,
1828
+ writeOnly: prop.schema.writeOnly
1829
+ })
1830
+ });
1831
+ })
1832
+ });
1833
+ }
1834
+ /**
1835
+ * Merges adjacent anonymous object members into a single anonymous object member.
1836
+ *
1837
+ * @example
1838
+ * ```ts
1839
+ * const merged = mergeAdjacentObjects([
1840
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'a', schema: createSchema({ type: 'string' }) })] }),
1841
+ * createSchema({ type: 'object', properties: [createProperty({ name: 'b', schema: createSchema({ type: 'number' }) })] }),
1842
+ * ])
1843
+ * ```
1844
+ */
1845
+ function mergeAdjacentObjects(members) {
1846
+ return members.reduce((acc, member) => {
1847
+ const objectMember = narrowSchema(member, "object");
1848
+ if (objectMember && !objectMember.name) {
1849
+ const previous = acc.at(-1);
1850
+ const previousObject = previous ? narrowSchema(previous, "object") : void 0;
1851
+ if (previousObject && !previousObject.name) {
1852
+ acc[acc.length - 1] = createSchema({
1853
+ ...previousObject,
1854
+ properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
1855
+ });
1856
+ return acc;
1857
+ }
1858
+ }
1859
+ acc.push(member);
1860
+ return acc;
1861
+ }, []);
1862
+ }
1863
+ /**
1864
+ * Removes enum members that are covered by broader scalar primitives in the same union.
1865
+ *
1866
+ * @example
1867
+ * ```ts
1868
+ * const simplified = simplifyUnion([
1869
+ * createSchema({ type: 'enum', primitive: 'string', enumValues: ['active'] }),
1870
+ * createSchema({ type: 'string' }),
1871
+ * ])
1872
+ * // keeps only string member
1873
+ * ```
1874
+ */
1875
+ function simplifyUnion(members) {
1876
+ const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
1877
+ if (!scalarPrimitives.size) return members;
1878
+ return members.filter((member) => {
1879
+ const enumNode = narrowSchema(member, "enum");
1880
+ if (!enumNode) return true;
1881
+ const primitive = enumNode.primitive;
1882
+ if (!primitive) return true;
1883
+ if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
1884
+ if (scalarPrimitives.has(primitive)) return false;
1885
+ if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1886
+ return true;
1887
+ });
1888
+ }
1889
+ function setEnumName(propNode, parentName, propName, enumSuffix) {
1890
+ const enumNode = narrowSchema(propNode, "enum");
1891
+ if (enumNode?.primitive === "boolean") return {
1892
+ ...propNode,
1893
+ name: void 0
1894
+ };
1895
+ if (enumNode) return {
1896
+ ...propNode,
1897
+ name: enumPropName(parentName, propName, enumSuffix)
1898
+ };
1899
+ return propNode;
1900
+ }
1901
+ //#endregion
1902
+ exports.caseParams = caseParams;
1903
+ exports.childName = childName;
445
1904
  exports.collect = collect;
1905
+ exports.collectImports = collectImports;
1906
+ exports.createArrowFunction = createArrowFunction;
1907
+ exports.createBreak = createBreak;
1908
+ exports.createConst = createConst;
1909
+ exports.createDiscriminantNode = createDiscriminantNode;
1910
+ exports.createExport = createExport;
1911
+ exports.createFile = createFile;
1912
+ exports.createFunction = createFunction;
1913
+ exports.createFunctionParameter = createFunctionParameter;
1914
+ exports.createFunctionParameters = createFunctionParameters;
1915
+ exports.createImport = createImport;
1916
+ exports.createInput = createInput;
1917
+ exports.createJsx = createJsx;
446
1918
  exports.createOperation = createOperation;
1919
+ exports.createOperationParams = createOperationParams;
1920
+ exports.createOutput = createOutput;
447
1921
  exports.createParameter = createParameter;
1922
+ exports.createParameterGroup = createParameterGroup;
1923
+ exports.createParamsType = createParamsType;
1924
+ exports.createPrinterFactory = createPrinterFactory;
448
1925
  exports.createProperty = createProperty;
449
1926
  exports.createResponse = createResponse;
450
- exports.createRoot = createRoot;
451
1927
  exports.createSchema = createSchema;
1928
+ exports.createSource = createSource;
1929
+ exports.createText = createText;
1930
+ exports.createType = createType;
452
1931
  exports.definePrinter = definePrinter;
1932
+ exports.enumPropName = enumPropName;
1933
+ exports.extractRefName = extractRefName;
1934
+ exports.extractStringsFromNodes = extractStringsFromNodes;
1935
+ exports.findDiscriminator = findDiscriminator;
453
1936
  exports.httpMethods = httpMethods;
1937
+ exports.isInputNode = isInputNode;
454
1938
  exports.isOperationNode = isOperationNode;
455
- exports.isParameterNode = isParameterNode;
456
- exports.isPlainStringType = isPlainStringType;
457
- exports.isPropertyNode = isPropertyNode;
458
- exports.isResponseNode = isResponseNode;
459
- exports.isRootNode = isRootNode;
1939
+ exports.isOutputNode = isOutputNode;
1940
+ exports.isScalarPrimitive = isScalarPrimitive;
460
1941
  exports.isSchemaNode = isSchemaNode;
1942
+ exports.isStringType = isStringType;
461
1943
  exports.mediaTypes = mediaTypes;
1944
+ exports.mergeAdjacentObjects = mergeAdjacentObjects;
462
1945
  exports.narrowSchema = narrowSchema;
463
1946
  exports.nodeKinds = nodeKinds;
464
- exports.refMapToObject = refMapToObject;
465
- exports.resolveRef = resolveRef;
466
1947
  exports.schemaTypes = schemaTypes;
1948
+ exports.setDiscriminatorEnum = setDiscriminatorEnum;
1949
+ exports.setEnumName = setEnumName;
1950
+ exports.simplifyUnion = simplifyUnion;
1951
+ exports.syncOptionality = syncOptionality;
1952
+ exports.syncSchemaRef = syncSchemaRef;
467
1953
  exports.transform = transform;
468
1954
  exports.walk = walk;
469
1955