@kubb/ast 5.0.0-beta.3 → 5.0.0-beta.30
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/README.md +1 -1
- package/dist/index.cjs +473 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1306 -999
- package/dist/index.js +465 -332
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/dialect.ts +64 -0
- package/src/dispatch.ts +53 -0
- package/src/factory.ts +127 -11
- package/src/guards.ts +18 -3
- package/src/index.ts +9 -3
- package/src/infer.ts +16 -5
- package/src/nodes/base.ts +2 -0
- package/src/nodes/code.ts +21 -21
- package/src/nodes/content.ts +37 -0
- package/src/nodes/file.ts +16 -14
- package/src/nodes/index.ts +7 -3
- package/src/nodes/operation.ts +98 -62
- package/src/nodes/response.ts +21 -14
- package/src/nodes/root.ts +72 -10
- package/src/nodes/schema.ts +9 -3
- package/src/printer.ts +34 -28
- package/src/refs.ts +4 -2
- package/src/resolvers.ts +4 -4
- package/src/transformers.ts +20 -15
- package/src/types.ts +7 -0
- package/src/utils.ts +109 -68
- package/src/visitor.ts +229 -275
package/dist/index.js
CHANGED
|
@@ -201,6 +201,60 @@ const mediaTypes = {
|
|
|
201
201
|
videoMp4: "video/mp4"
|
|
202
202
|
};
|
|
203
203
|
//#endregion
|
|
204
|
+
//#region src/dialect.ts
|
|
205
|
+
/**
|
|
206
|
+
* Identity helper that types a {@link SchemaDialect} for an adapter. Like
|
|
207
|
+
* `defineParser`, it adds no runtime behavior — it pins the dialect's type for
|
|
208
|
+
* inference and gives adapter authors a discoverable anchor.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* export const oasDialect = defineSchemaDialect({
|
|
213
|
+
* name: 'oas',
|
|
214
|
+
* isNullable,
|
|
215
|
+
* isReference,
|
|
216
|
+
* isDiscriminator,
|
|
217
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
218
|
+
* resolveRef,
|
|
219
|
+
* })
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
function defineSchemaDialect(dialect) {
|
|
223
|
+
return dialect;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/dispatch.ts
|
|
227
|
+
/**
|
|
228
|
+
* Walks an ordered list of {@link DispatchRule}s and returns the first node produced.
|
|
229
|
+
*
|
|
230
|
+
* This is the shared backbone for spec adapters (OpenAPI today, AsyncAPI and others later).
|
|
231
|
+
* The contract an adapter follows is intentionally minimal:
|
|
232
|
+
*
|
|
233
|
+
* context → [rule.match → rule.convert] → node
|
|
234
|
+
*
|
|
235
|
+
* An adapter derives a context from a source spec node, then declares an ordered table of
|
|
236
|
+
* rules mapping spec shapes onto Kubb AST nodes. To add support for a new spec, write a new
|
|
237
|
+
* context type and a new rules table — the traversal here is reused unchanged.
|
|
238
|
+
*
|
|
239
|
+
* Order is significant: earlier rules win, so list higher-precedence or more specific shapes
|
|
240
|
+
* first (e.g. composition keywords before plain `type`). A rule whose `match` returns `true`
|
|
241
|
+
* may still `convert` to `null` to defer to later rules. When no rule produces a node this
|
|
242
|
+
* returns `null`, leaving the caller to apply its own fallback.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```ts
|
|
246
|
+
* const node = dispatch(schemaRules, schemaContext) ?? createSchema({ type: fallbackType })
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
function dispatch(rules, context) {
|
|
250
|
+
for (const rule of rules) {
|
|
251
|
+
if (!rule.match(context)) continue;
|
|
252
|
+
const node = rule.convert(context);
|
|
253
|
+
if (node !== null && node !== void 0) return node;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
204
258
|
//#region ../../internals/utils/src/casing.ts
|
|
205
259
|
/**
|
|
206
260
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -265,6 +319,46 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
265
319
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
266
320
|
}
|
|
267
321
|
//#endregion
|
|
322
|
+
//#region ../../internals/utils/src/promise.ts
|
|
323
|
+
/**
|
|
324
|
+
* Wraps `factory` with a keyed cache backed by the provided store.
|
|
325
|
+
*
|
|
326
|
+
* Pass a `WeakMap` for object keys (results are GC-eligible when the key is
|
|
327
|
+
* collected) or a `Map` for primitive keys. For multi-argument functions,
|
|
328
|
+
* nest two `memoize` calls — the outer keyed by the first argument, the
|
|
329
|
+
* inner (created once per outer miss) keyed by the second.
|
|
330
|
+
*
|
|
331
|
+
* Because the cache is owned by the caller, it can be shared, inspected, or
|
|
332
|
+
* cleared independently of the memoized function.
|
|
333
|
+
*
|
|
334
|
+
* @example Single WeakMap key
|
|
335
|
+
* ```ts
|
|
336
|
+
* const cache = new WeakMap<SchemaNode, Set<string>>()
|
|
337
|
+
* const getRefs = memoize(cache, (node) => collectRefs(node))
|
|
338
|
+
* ```
|
|
339
|
+
*
|
|
340
|
+
* @example Single Map key (primitive)
|
|
341
|
+
* ```ts
|
|
342
|
+
* const cache = new Map<string, Resolver>()
|
|
343
|
+
* const getResolver = memoize(cache, (name) => buildResolver(name))
|
|
344
|
+
* ```
|
|
345
|
+
*
|
|
346
|
+
* @example Two-level (object + primitive)
|
|
347
|
+
* ```ts
|
|
348
|
+
* const outer = new WeakMap<Params[], Map<string, Params[]>>()
|
|
349
|
+
* const fn = memoize(outer, (params) => memoize(new Map(), (key) => transform(params, key)))
|
|
350
|
+
* fn(params)('camelcase')
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
function memoize(store, factory) {
|
|
354
|
+
return (key) => {
|
|
355
|
+
if (store.has(key)) return store.get(key);
|
|
356
|
+
const value = factory(key);
|
|
357
|
+
store.set(key, value);
|
|
358
|
+
return value;
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
268
362
|
//#region ../../internals/utils/src/reserved.ts
|
|
269
363
|
/**
|
|
270
364
|
* JavaScript and Java reserved words.
|
|
@@ -392,11 +486,11 @@ function trimExtName(text) {
|
|
|
392
486
|
* @example
|
|
393
487
|
* ```ts
|
|
394
488
|
* const schema = createSchema({ type: 'string' })
|
|
395
|
-
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode |
|
|
489
|
+
* const stringNode = narrowSchema(schema, 'string') // StringSchemaNode | null
|
|
396
490
|
* ```
|
|
397
491
|
*/
|
|
398
492
|
function narrowSchema(node, type) {
|
|
399
|
-
return node?.type === type ? node :
|
|
493
|
+
return node?.type === type ? node : null;
|
|
400
494
|
}
|
|
401
495
|
function isKind(kind) {
|
|
402
496
|
return (node) => node.kind === kind;
|
|
@@ -435,6 +529,19 @@ const isOutputNode = isKind("Output");
|
|
|
435
529
|
*/
|
|
436
530
|
const isOperationNode = isKind("Operation");
|
|
437
531
|
/**
|
|
532
|
+
* Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* ```ts
|
|
536
|
+
* if (isHttpOperationNode(node)) {
|
|
537
|
+
* console.log(node.method, node.path)
|
|
538
|
+
* }
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
function isHttpOperationNode(node) {
|
|
542
|
+
return node.protocol === "http" || node.method !== void 0 && node.path !== void 0;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
438
545
|
* Returns `true` when the input is a `SchemaNode`.
|
|
439
546
|
*
|
|
440
547
|
* @example
|
|
@@ -445,12 +552,6 @@ const isOperationNode = isKind("Operation");
|
|
|
445
552
|
* ```
|
|
446
553
|
*/
|
|
447
554
|
const isSchemaNode = isKind("Schema");
|
|
448
|
-
isKind("Property");
|
|
449
|
-
isKind("Parameter");
|
|
450
|
-
isKind("Response");
|
|
451
|
-
isKind("FunctionParameter");
|
|
452
|
-
isKind("ParameterGroup");
|
|
453
|
-
isKind("FunctionParameters");
|
|
454
555
|
//#endregion
|
|
455
556
|
//#region src/refs.ts
|
|
456
557
|
/**
|
|
@@ -503,53 +604,92 @@ function createLimit(concurrency) {
|
|
|
503
604
|
});
|
|
504
605
|
};
|
|
505
606
|
}
|
|
607
|
+
const visitorKeysByKind = {
|
|
608
|
+
Input: ["schemas", "operations"],
|
|
609
|
+
Operation: [
|
|
610
|
+
"parameters",
|
|
611
|
+
"requestBody",
|
|
612
|
+
"responses"
|
|
613
|
+
],
|
|
614
|
+
RequestBody: ["content"],
|
|
615
|
+
Content: ["schema"],
|
|
616
|
+
Response: ["content"],
|
|
617
|
+
Schema: [
|
|
618
|
+
"properties",
|
|
619
|
+
"items",
|
|
620
|
+
"members",
|
|
621
|
+
"additionalProperties"
|
|
622
|
+
],
|
|
623
|
+
Property: ["schema"],
|
|
624
|
+
Parameter: ["schema"]
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
628
|
+
*/
|
|
629
|
+
function isNode(value) {
|
|
630
|
+
return typeof value === "object" && value !== null && "kind" in value;
|
|
631
|
+
}
|
|
506
632
|
/**
|
|
507
|
-
* Returns the immediate traversable children of `node
|
|
633
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
508
634
|
*
|
|
509
|
-
*
|
|
510
|
-
* `additionalProperties`) are only included
|
|
511
|
-
* when `recurse` is `true`; shallow mode skips them.
|
|
635
|
+
* `Schema` children are only included when `recurse` is `true`; shallow mode skips them.
|
|
512
636
|
*
|
|
513
637
|
* @example
|
|
514
638
|
* ```ts
|
|
515
639
|
* const children = getChildren(operationNode, true)
|
|
516
|
-
* // returns parameters,
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
function getChildren(node, recurse) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const children = [];
|
|
530
|
-
if (!recurse) return [];
|
|
531
|
-
if ("properties" in node && node.properties.length > 0) children.push(...node.properties);
|
|
532
|
-
if ("items" in node && node.items) children.push(...node.items);
|
|
533
|
-
if ("members" in node && node.members) children.push(...node.members);
|
|
534
|
-
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
535
|
-
return children;
|
|
536
|
-
}
|
|
537
|
-
case "Property": return [node.schema];
|
|
538
|
-
case "Parameter": return [node.schema];
|
|
539
|
-
case "Response": return node.schema ? [node.schema] : [];
|
|
540
|
-
case "FunctionParameter":
|
|
541
|
-
case "ParameterGroup":
|
|
542
|
-
case "FunctionParameters":
|
|
543
|
-
case "Type": return [];
|
|
544
|
-
default: return [];
|
|
640
|
+
* // returns parameters, the request body, and responses
|
|
641
|
+
* ```
|
|
642
|
+
*/
|
|
643
|
+
function* getChildren(node, recurse) {
|
|
644
|
+
if (node.kind === "Schema" && !recurse) return;
|
|
645
|
+
const keys = visitorKeysByKind[node.kind];
|
|
646
|
+
if (!keys) return;
|
|
647
|
+
const record = node;
|
|
648
|
+
for (const key of keys) {
|
|
649
|
+
const value = record[key];
|
|
650
|
+
if (Array.isArray(value)) {
|
|
651
|
+
for (const item of value) if (isNode(item)) yield item;
|
|
652
|
+
} else if (isNode(value)) yield value;
|
|
545
653
|
}
|
|
546
654
|
}
|
|
547
655
|
/**
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
656
|
+
* Maps a node `kind` to the matching visitor callback name. Only the seven
|
|
657
|
+
* traversable node kinds have an entry; every other kind resolves to
|
|
658
|
+
* `undefined` and is skipped.
|
|
659
|
+
*/
|
|
660
|
+
const VISITOR_KEY_BY_KIND = {
|
|
661
|
+
Input: "input",
|
|
662
|
+
Output: "output",
|
|
663
|
+
Operation: "operation",
|
|
664
|
+
Schema: "schema",
|
|
665
|
+
Property: "property",
|
|
666
|
+
Parameter: "parameter",
|
|
667
|
+
Response: "response"
|
|
668
|
+
};
|
|
669
|
+
/**
|
|
670
|
+
* Invokes the visitor callback that matches `node.kind`, passing the traversal
|
|
671
|
+
* context. Returns the callback's result (a replacement node, a collected
|
|
672
|
+
* value, or `undefined` when no callback is registered for the kind).
|
|
551
673
|
*
|
|
552
|
-
*
|
|
674
|
+
* Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
|
|
675
|
+
* in one place. `TResult` is the caller's expected return: the same node type
|
|
676
|
+
* for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
|
|
677
|
+
*/
|
|
678
|
+
function applyVisitor(node, visitor, parent) {
|
|
679
|
+
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
680
|
+
if (!key) return void 0;
|
|
681
|
+
const fn = visitor[key];
|
|
682
|
+
return fn?.(node, { parent });
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Async depth-first traversal for side effects. Visitor return values are
|
|
686
|
+
* ignored. Use `transform` when you want to rewrite nodes.
|
|
687
|
+
*
|
|
688
|
+
* Sibling nodes at each depth run concurrently up to `options.concurrency`
|
|
689
|
+
* (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
|
|
690
|
+
* work; lower values reduce memory pressure.
|
|
691
|
+
*
|
|
692
|
+
* @example Log every operation
|
|
553
693
|
* ```ts
|
|
554
694
|
* await walk(root, {
|
|
555
695
|
* operation(node) {
|
|
@@ -558,213 +698,114 @@ function getChildren(node, recurse) {
|
|
|
558
698
|
* })
|
|
559
699
|
* ```
|
|
560
700
|
*
|
|
561
|
-
* @example
|
|
701
|
+
* @example Only visit the root node
|
|
562
702
|
* ```ts
|
|
563
|
-
*
|
|
564
|
-
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
703
|
+
* await walk(root, { depth: 'shallow', input: () => {} })
|
|
565
704
|
* ```
|
|
566
705
|
*/
|
|
567
706
|
async function walk(node, options) {
|
|
568
707
|
return _walk(node, options, (options.depth ?? visitorDepths.deep) === visitorDepths.deep, createLimit(options.concurrency ?? 30), void 0);
|
|
569
708
|
}
|
|
570
709
|
async function _walk(node, visitor, recurse, limit, parent) {
|
|
571
|
-
|
|
572
|
-
case "Input":
|
|
573
|
-
await limit(() => visitor.input?.(node, { parent }));
|
|
574
|
-
break;
|
|
575
|
-
case "Output":
|
|
576
|
-
await limit(() => visitor.output?.(node, { parent }));
|
|
577
|
-
break;
|
|
578
|
-
case "Operation":
|
|
579
|
-
await limit(() => visitor.operation?.(node, { parent }));
|
|
580
|
-
break;
|
|
581
|
-
case "Schema":
|
|
582
|
-
await limit(() => visitor.schema?.(node, { parent }));
|
|
583
|
-
break;
|
|
584
|
-
case "Property":
|
|
585
|
-
await limit(() => visitor.property?.(node, { parent }));
|
|
586
|
-
break;
|
|
587
|
-
case "Parameter":
|
|
588
|
-
await limit(() => visitor.parameter?.(node, { parent }));
|
|
589
|
-
break;
|
|
590
|
-
case "Response":
|
|
591
|
-
await limit(() => visitor.response?.(node, { parent }));
|
|
592
|
-
break;
|
|
593
|
-
case "FunctionParameter":
|
|
594
|
-
case "ParameterGroup":
|
|
595
|
-
case "FunctionParameters": break;
|
|
596
|
-
}
|
|
710
|
+
await limit(() => applyVisitor(node, visitor, parent));
|
|
597
711
|
const children = getChildren(node, recurse);
|
|
598
712
|
for (const child of children) await _walk(child, visitor, recurse, limit, node);
|
|
599
713
|
}
|
|
600
714
|
function transform(node, options) {
|
|
601
715
|
const { depth, parent, ...visitor } = options;
|
|
602
716
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
parent: op
|
|
643
|
-
}) : void 0
|
|
644
|
-
}))
|
|
645
|
-
} : void 0,
|
|
646
|
-
responses: op.responses.map((r) => transform(r, {
|
|
647
|
-
...options,
|
|
648
|
-
parent: op
|
|
649
|
-
}))
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
case "Schema": {
|
|
653
|
-
let schema = node;
|
|
654
|
-
const replaced = visitor.schema?.(schema, { parent });
|
|
655
|
-
if (replaced) schema = replaced;
|
|
656
|
-
const childOptions = {
|
|
657
|
-
...options,
|
|
658
|
-
parent: schema
|
|
659
|
-
};
|
|
660
|
-
return {
|
|
661
|
-
...schema,
|
|
662
|
-
..."properties" in schema && recurse ? { properties: schema.properties.map((p) => transform(p, childOptions)) } : {},
|
|
663
|
-
..."items" in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {},
|
|
664
|
-
..."members" in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {},
|
|
665
|
-
..."additionalProperties" in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true ? { additionalProperties: transform(schema.additionalProperties, childOptions) } : {}
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
case "Property": {
|
|
669
|
-
let prop = node;
|
|
670
|
-
const replaced = visitor.property?.(prop, { parent });
|
|
671
|
-
if (replaced) prop = replaced;
|
|
672
|
-
return createProperty({
|
|
673
|
-
...prop,
|
|
674
|
-
schema: transform(prop.schema, {
|
|
675
|
-
...options,
|
|
676
|
-
parent: prop
|
|
677
|
-
})
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
case "Parameter": {
|
|
681
|
-
let param = node;
|
|
682
|
-
const replaced = visitor.parameter?.(param, { parent });
|
|
683
|
-
if (replaced) param = replaced;
|
|
684
|
-
return createParameter({
|
|
685
|
-
...param,
|
|
686
|
-
schema: transform(param.schema, {
|
|
687
|
-
...options,
|
|
688
|
-
parent: param
|
|
689
|
-
})
|
|
717
|
+
const rebuilt = transformChildren(applyVisitor(node, visitor, parent) ?? node, options, recurse);
|
|
718
|
+
if (rebuilt === node) return node;
|
|
719
|
+
const finalize = nodeFinalizers[rebuilt.kind];
|
|
720
|
+
return finalize ? finalize(rebuilt) : rebuilt;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Per-kind builders rerun after children are rebuilt. `Property`/`Parameter`
|
|
724
|
+
* resync schema optionality against their `required` flag once the schema may
|
|
725
|
+
* have changed.
|
|
726
|
+
*/
|
|
727
|
+
const nodeFinalizers = {
|
|
728
|
+
Property: (node) => createProperty(node),
|
|
729
|
+
Parameter: (node) => createParameter(node)
|
|
730
|
+
};
|
|
731
|
+
/**
|
|
732
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
733
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
734
|
+
* `Schema` children are skipped in shallow mode.
|
|
735
|
+
*/
|
|
736
|
+
function transformChildren(node, options, recurse) {
|
|
737
|
+
if (node.kind === "Schema" && !recurse) return node;
|
|
738
|
+
const keys = visitorKeysByKind[node.kind];
|
|
739
|
+
if (!keys) return node;
|
|
740
|
+
const record = node;
|
|
741
|
+
const childOptions = {
|
|
742
|
+
...options,
|
|
743
|
+
parent: node
|
|
744
|
+
};
|
|
745
|
+
let updates;
|
|
746
|
+
for (const key of keys) {
|
|
747
|
+
if (!(key in record)) continue;
|
|
748
|
+
const value = record[key];
|
|
749
|
+
if (Array.isArray(value)) {
|
|
750
|
+
let changed = false;
|
|
751
|
+
const mapped = value.map((item) => {
|
|
752
|
+
if (!isNode(item)) return item;
|
|
753
|
+
const next = transform(item, childOptions);
|
|
754
|
+
if (next !== item) changed = true;
|
|
755
|
+
return next;
|
|
690
756
|
});
|
|
757
|
+
if (changed) (updates ??= {})[key] = mapped;
|
|
758
|
+
} else if (isNode(value)) {
|
|
759
|
+
const next = transform(value, childOptions);
|
|
760
|
+
if (next !== value) (updates ??= {})[key] = next;
|
|
691
761
|
}
|
|
692
|
-
case "Response": {
|
|
693
|
-
let response = node;
|
|
694
|
-
const replaced = visitor.response?.(response, { parent });
|
|
695
|
-
if (replaced) response = replaced;
|
|
696
|
-
return {
|
|
697
|
-
...response,
|
|
698
|
-
schema: transform(response.schema, {
|
|
699
|
-
...options,
|
|
700
|
-
parent: response
|
|
701
|
-
})
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
case "FunctionParameter":
|
|
705
|
-
case "ParameterGroup":
|
|
706
|
-
case "FunctionParameters":
|
|
707
|
-
case "Type": return node;
|
|
708
|
-
default: return node;
|
|
709
762
|
}
|
|
763
|
+
return updates ? {
|
|
764
|
+
...node,
|
|
765
|
+
...updates
|
|
766
|
+
} : node;
|
|
710
767
|
}
|
|
711
768
|
/**
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
769
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
770
|
+
* the visitor callbacks. Use `collect` for the eager array form.
|
|
715
771
|
*
|
|
716
|
-
* @example
|
|
772
|
+
* @example Collect every operationId
|
|
717
773
|
* ```ts
|
|
718
|
-
* const ids =
|
|
774
|
+
* const ids: string[] = []
|
|
775
|
+
* for (const id of collectLazy<string>(root, {
|
|
719
776
|
* operation(node) {
|
|
720
777
|
* return node.operationId
|
|
721
778
|
* },
|
|
722
|
-
* })
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
* @example
|
|
726
|
-
* ```ts
|
|
727
|
-
* // Collect from only the current node
|
|
728
|
-
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
779
|
+
* })) {
|
|
780
|
+
* ids.push(id)
|
|
781
|
+
* }
|
|
729
782
|
* ```
|
|
730
783
|
*/
|
|
731
|
-
function
|
|
784
|
+
function* collectLazy(node, options) {
|
|
732
785
|
const { depth, parent, ...visitor } = options;
|
|
733
786
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep;
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
case "Input":
|
|
738
|
-
v = visitor.input?.(node, { parent });
|
|
739
|
-
break;
|
|
740
|
-
case "Output":
|
|
741
|
-
v = visitor.output?.(node, { parent });
|
|
742
|
-
break;
|
|
743
|
-
case "Operation":
|
|
744
|
-
v = visitor.operation?.(node, { parent });
|
|
745
|
-
break;
|
|
746
|
-
case "Schema":
|
|
747
|
-
v = visitor.schema?.(node, { parent });
|
|
748
|
-
break;
|
|
749
|
-
case "Property":
|
|
750
|
-
v = visitor.property?.(node, { parent });
|
|
751
|
-
break;
|
|
752
|
-
case "Parameter":
|
|
753
|
-
v = visitor.parameter?.(node, { parent });
|
|
754
|
-
break;
|
|
755
|
-
case "Response":
|
|
756
|
-
v = visitor.response?.(node, { parent });
|
|
757
|
-
break;
|
|
758
|
-
case "FunctionParameter":
|
|
759
|
-
case "ParameterGroup":
|
|
760
|
-
case "FunctionParameters": break;
|
|
761
|
-
}
|
|
762
|
-
if (v !== void 0) results.push(v);
|
|
763
|
-
for (const child of getChildren(node, recurse)) for (const item of collect(child, {
|
|
787
|
+
const v = applyVisitor(node, visitor, parent);
|
|
788
|
+
if (v != null) yield v;
|
|
789
|
+
for (const child of getChildren(node, recurse)) yield* collectLazy(child, {
|
|
764
790
|
...options,
|
|
765
791
|
parent: node
|
|
766
|
-
})
|
|
767
|
-
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Eager depth-first collection pass. Returns an array of every non-null value
|
|
796
|
+
* the visitor callbacks return.
|
|
797
|
+
*
|
|
798
|
+
* @example Collect every operationId
|
|
799
|
+
* ```ts
|
|
800
|
+
* const ids = collect<string>(root, {
|
|
801
|
+
* operation(node) {
|
|
802
|
+
* return node.operationId
|
|
803
|
+
* },
|
|
804
|
+
* })
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
function collect(node, options) {
|
|
808
|
+
return Array.from(collectLazy(node, options));
|
|
768
809
|
}
|
|
769
810
|
//#endregion
|
|
770
811
|
//#region src/utils.ts
|
|
@@ -818,15 +859,16 @@ function isStringType(node) {
|
|
|
818
859
|
* the desired casing while preserving `OperationNode.parameters` for other consumers.
|
|
819
860
|
* The input array is not mutated. When `casing` is not set, the original array is returned unchanged.
|
|
820
861
|
*/
|
|
862
|
+
const caseParamsMemo = memoize(/* @__PURE__ */ new WeakMap(), (params) => memoize(/* @__PURE__ */ new Map(), (casing) => params.map((param) => {
|
|
863
|
+
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
864
|
+
return {
|
|
865
|
+
...param,
|
|
866
|
+
name: transformed
|
|
867
|
+
};
|
|
868
|
+
})));
|
|
821
869
|
function caseParams(params, casing) {
|
|
822
870
|
if (!casing) return params;
|
|
823
|
-
return params
|
|
824
|
-
const transformed = casing === "camelcase" || !isValidVarName(param.name) ? camelCase(param.name) : param.name;
|
|
825
|
-
return {
|
|
826
|
-
...param,
|
|
827
|
-
name: transformed
|
|
828
|
-
};
|
|
829
|
-
});
|
|
871
|
+
return caseParamsMemo(params)(casing);
|
|
830
872
|
}
|
|
831
873
|
/**
|
|
832
874
|
* Creates a single-property object schema used as a discriminator literal.
|
|
@@ -955,7 +997,7 @@ function createOperationParams(node, options) {
|
|
|
955
997
|
}));
|
|
956
998
|
} else {
|
|
957
999
|
if (pathParams.length) if (pathParamsType === "inlineSpread") {
|
|
958
|
-
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0])
|
|
1000
|
+
const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]);
|
|
959
1001
|
params.push(createFunctionParameter({
|
|
960
1002
|
name: pathName,
|
|
961
1003
|
type: spreadType ? wrapType(spreadType) : void 0,
|
|
@@ -1031,13 +1073,13 @@ function buildGroupParam({ name, node, params, groupType, resolver, wrapType })
|
|
|
1031
1073
|
}
|
|
1032
1074
|
/**
|
|
1033
1075
|
* Derives a {@link ParamGroupType} from the resolver's group method.
|
|
1034
|
-
* Returns `
|
|
1076
|
+
* Returns `null` when the group name equals the individual param name (no real group).
|
|
1035
1077
|
*/
|
|
1036
1078
|
function resolveGroupType({ node, params, groupMethod, resolver }) {
|
|
1037
|
-
if (!params.length) return;
|
|
1079
|
+
if (!params.length) return null;
|
|
1038
1080
|
const firstParam = params[0];
|
|
1039
1081
|
const groupName = groupMethod.call(resolver, node, firstParam);
|
|
1040
|
-
if (groupName === resolver.resolveParamName(node, firstParam)) return;
|
|
1082
|
+
if (groupName === resolver.resolveParamName(node, firstParam)) return null;
|
|
1041
1083
|
const allOptional = params.every((p) => !p.required);
|
|
1042
1084
|
return {
|
|
1043
1085
|
type: createParamsType({
|
|
@@ -1104,6 +1146,16 @@ function combineSources(sources) {
|
|
|
1104
1146
|
return [...seen.values()];
|
|
1105
1147
|
}
|
|
1106
1148
|
/**
|
|
1149
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
1150
|
+
*
|
|
1151
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
1152
|
+
*/
|
|
1153
|
+
function mergeNameArrays(existing, incoming) {
|
|
1154
|
+
const merged = new Set(existing);
|
|
1155
|
+
for (const name of incoming) merged.add(name);
|
|
1156
|
+
return [...merged];
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1107
1159
|
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
1108
1160
|
*
|
|
1109
1161
|
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
@@ -1124,11 +1176,8 @@ function combineExports(exports) {
|
|
|
1124
1176
|
if (!name.length) continue;
|
|
1125
1177
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1126
1178
|
const existing = namedByPath.get(key);
|
|
1127
|
-
if (existing && Array.isArray(existing.name))
|
|
1128
|
-
|
|
1129
|
-
for (const n of name) merged.add(n);
|
|
1130
|
-
existing.name = [...merged];
|
|
1131
|
-
} else {
|
|
1179
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1180
|
+
else {
|
|
1132
1181
|
const newItem = {
|
|
1133
1182
|
...curr,
|
|
1134
1183
|
name: [...new Set(name)]
|
|
@@ -1164,6 +1213,11 @@ function combineImports(imports, exports, source) {
|
|
|
1164
1213
|
if (!importNameMemo.has(key)) importNameMemo.set(key, n);
|
|
1165
1214
|
return importNameMemo.get(key);
|
|
1166
1215
|
};
|
|
1216
|
+
const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
|
|
1217
|
+
for (const node of imports) {
|
|
1218
|
+
if (!Array.isArray(node.name)) continue;
|
|
1219
|
+
if (node.name.some((item) => typeof item === "string" ? isUsed(item) : isUsed(item.name ?? item.propertyName))) pathsWithUsedNamedImport.add(node.path);
|
|
1220
|
+
}
|
|
1167
1221
|
const result = [];
|
|
1168
1222
|
const namedByPath = /* @__PURE__ */ new Map();
|
|
1169
1223
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1181,11 +1235,8 @@ function combineImports(imports, exports, source) {
|
|
|
1181
1235
|
if (!name.length) continue;
|
|
1182
1236
|
const key = pathTypeKey(path, isTypeOnly);
|
|
1183
1237
|
const existing = namedByPath.get(key);
|
|
1184
|
-
if (existing && Array.isArray(existing.name))
|
|
1185
|
-
|
|
1186
|
-
for (const n of name) merged.add(n);
|
|
1187
|
-
existing.name = [...merged];
|
|
1188
|
-
} else {
|
|
1238
|
+
if (existing && Array.isArray(existing.name)) existing.name = mergeNameArrays(existing.name, name);
|
|
1239
|
+
else {
|
|
1189
1240
|
const newItem = {
|
|
1190
1241
|
...curr,
|
|
1191
1242
|
name
|
|
@@ -1194,7 +1245,7 @@ function combineImports(imports, exports, source) {
|
|
|
1194
1245
|
namedByPath.set(key, newItem);
|
|
1195
1246
|
}
|
|
1196
1247
|
} else {
|
|
1197
|
-
if (name && !isUsed(name)) continue;
|
|
1248
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue;
|
|
1198
1249
|
const key = importKey(path, name, isTypeOnly);
|
|
1199
1250
|
if (!seen.has(key)) {
|
|
1200
1251
|
result.push(curr);
|
|
@@ -1230,7 +1281,7 @@ function extractStringsFromNodes(nodes) {
|
|
|
1230
1281
|
/**
|
|
1231
1282
|
* Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
|
|
1232
1283
|
*
|
|
1233
|
-
* Returns `
|
|
1284
|
+
* Returns `null` for non-ref nodes or when no name can be resolved. Use this to get a schema's
|
|
1234
1285
|
* identifier for type definitions or error messages.
|
|
1235
1286
|
*
|
|
1236
1287
|
* @example
|
|
@@ -1240,9 +1291,9 @@ function extractStringsFromNodes(nodes) {
|
|
|
1240
1291
|
* ```
|
|
1241
1292
|
*/
|
|
1242
1293
|
function resolveRefName(node) {
|
|
1243
|
-
if (!node || node.type !== "ref") return
|
|
1244
|
-
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ??
|
|
1245
|
-
return node.name ?? node.schema?.name ??
|
|
1294
|
+
if (!node || node.type !== "ref") return null;
|
|
1295
|
+
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? null;
|
|
1296
|
+
return node.name ?? node.schema?.name ?? null;
|
|
1246
1297
|
}
|
|
1247
1298
|
/**
|
|
1248
1299
|
* Collects every named schema referenced (transitively) from a node via ref edges.
|
|
@@ -1264,14 +1315,19 @@ function resolveRefName(node) {
|
|
|
1264
1315
|
* }
|
|
1265
1316
|
* ```
|
|
1266
1317
|
*/
|
|
1267
|
-
|
|
1268
|
-
|
|
1318
|
+
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1319
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1269
1320
|
collect(node, { schema(child) {
|
|
1270
1321
|
if (child.type === "ref") {
|
|
1271
1322
|
const name = resolveRefName(child);
|
|
1272
|
-
if (name)
|
|
1323
|
+
if (name) refs.add(name);
|
|
1273
1324
|
}
|
|
1274
1325
|
} });
|
|
1326
|
+
return refs;
|
|
1327
|
+
});
|
|
1328
|
+
function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
1329
|
+
if (!node) return out;
|
|
1330
|
+
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1275
1331
|
return out;
|
|
1276
1332
|
}
|
|
1277
1333
|
/**
|
|
@@ -1287,10 +1343,10 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1287
1343
|
*
|
|
1288
1344
|
* @example Only generate schemas referenced by included operations
|
|
1289
1345
|
* ```ts
|
|
1290
|
-
* const includedOps =
|
|
1291
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
1346
|
+
* const includedOps = operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
|
|
1347
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1292
1348
|
*
|
|
1293
|
-
* for (const schema of
|
|
1349
|
+
* for (const schema of schemas) {
|
|
1294
1350
|
* if (schema.name && !allowed.has(schema.name)) continue
|
|
1295
1351
|
* // … generate schema
|
|
1296
1352
|
* }
|
|
@@ -1298,11 +1354,12 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1298
1354
|
*
|
|
1299
1355
|
* @example Check whether a specific schema is needed
|
|
1300
1356
|
* ```ts
|
|
1301
|
-
* const allowed = collectUsedSchemaNames(includedOps,
|
|
1357
|
+
* const allowed = collectUsedSchemaNames(includedOps, schemas)
|
|
1302
1358
|
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
1303
1359
|
* ```
|
|
1304
1360
|
*/
|
|
1305
|
-
|
|
1361
|
+
const collectUsedSchemaNamesMemo = memoize(/* @__PURE__ */ new WeakMap(), (ops) => memoize(/* @__PURE__ */ new WeakMap(), (schemas) => computeUsedSchemaNames(ops, schemas)));
|
|
1362
|
+
function computeUsedSchemaNames(operations, schemas) {
|
|
1306
1363
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
1307
1364
|
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
1308
1365
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -1314,22 +1371,17 @@ function collectUsedSchemaNames(operations, schemas) {
|
|
|
1314
1371
|
if (namedSchema) visitSchema(namedSchema);
|
|
1315
1372
|
}
|
|
1316
1373
|
}
|
|
1317
|
-
for (const op of operations) for (const schema of
|
|
1374
|
+
for (const op of operations) for (const schema of collectLazy(op, {
|
|
1318
1375
|
depth: "shallow",
|
|
1319
1376
|
schema: (node) => node
|
|
1320
1377
|
})) visitSchema(schema);
|
|
1321
1378
|
return result;
|
|
1322
1379
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1329
|
-
*
|
|
1330
|
-
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1331
|
-
*/
|
|
1332
|
-
function findCircularSchemas(schemas) {
|
|
1380
|
+
function collectUsedSchemaNames(operations, schemas) {
|
|
1381
|
+
return collectUsedSchemaNamesMemo(operations)(schemas);
|
|
1382
|
+
}
|
|
1383
|
+
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
1384
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1333
1385
|
const graph = /* @__PURE__ */ new Map();
|
|
1334
1386
|
for (const schema of schemas) {
|
|
1335
1387
|
if (!schema.name) continue;
|
|
@@ -1352,6 +1404,19 @@ function findCircularSchemas(schemas) {
|
|
|
1352
1404
|
}
|
|
1353
1405
|
}
|
|
1354
1406
|
return circular;
|
|
1407
|
+
});
|
|
1408
|
+
/**
|
|
1409
|
+
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
1410
|
+
*
|
|
1411
|
+
* Returns a Set of schema names with circular dependencies. Use this to wrap recursive schema positions
|
|
1412
|
+
* in deferred constructs (lazy getter, `z.lazy(() => …)`) to prevent infinite recursion when generated code runs.
|
|
1413
|
+
* Refs are followed by name only, keeping the algorithm linear in the schema graph size.
|
|
1414
|
+
*
|
|
1415
|
+
* @note Call this once on the full schema graph, then use `containsCircularRef()` to check individual schemas.
|
|
1416
|
+
*/
|
|
1417
|
+
function findCircularSchemas(schemas) {
|
|
1418
|
+
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
1419
|
+
return findCircularSchemasMemo(schemas);
|
|
1355
1420
|
}
|
|
1356
1421
|
/**
|
|
1357
1422
|
* Type guard returning `true` when a schema or anything nested within it contains a ref to a circular schema.
|
|
@@ -1363,19 +1428,23 @@ function findCircularSchemas(schemas) {
|
|
|
1363
1428
|
*/
|
|
1364
1429
|
function containsCircularRef(node, { circularSchemas, excludeName }) {
|
|
1365
1430
|
if (!node || circularSchemas.size === 0) return false;
|
|
1366
|
-
|
|
1367
|
-
if (child.type !== "ref") return
|
|
1431
|
+
for (const _ of collectLazy(node, { schema(child) {
|
|
1432
|
+
if (child.type !== "ref") return null;
|
|
1368
1433
|
const name = resolveRefName(child);
|
|
1369
|
-
return name && name !== excludeName && circularSchemas.has(name) ? true :
|
|
1370
|
-
} })
|
|
1434
|
+
return name && name !== excludeName && circularSchemas.has(name) ? true : null;
|
|
1435
|
+
} })) return true;
|
|
1436
|
+
return false;
|
|
1371
1437
|
}
|
|
1372
1438
|
//#endregion
|
|
1373
1439
|
//#region src/factory.ts
|
|
1374
1440
|
/**
|
|
1375
|
-
*
|
|
1441
|
+
* Updates a schema's `optional` and `nullish` flags from a parent's `required`
|
|
1442
|
+
* value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
|
|
1443
|
+
* object properties combine "required" and "nullable" into a single AST.
|
|
1376
1444
|
*
|
|
1377
|
-
* -
|
|
1378
|
-
* -
|
|
1445
|
+
* - Non-required + non-nullable → `optional: true`.
|
|
1446
|
+
* - Non-required + nullable → `nullish: true`.
|
|
1447
|
+
* - Required → both flags cleared.
|
|
1379
1448
|
*/
|
|
1380
1449
|
function syncOptionality(schema, required) {
|
|
1381
1450
|
const nullable = schema.nullable ?? false;
|
|
@@ -1386,6 +1455,29 @@ function syncOptionality(schema, required) {
|
|
|
1386
1455
|
};
|
|
1387
1456
|
}
|
|
1388
1457
|
/**
|
|
1458
|
+
* Identity-preserving node update: returns `node` unchanged when every field in
|
|
1459
|
+
* `changes` already equals (by reference) the current value, otherwise a new node
|
|
1460
|
+
* with the changes applied.
|
|
1461
|
+
*
|
|
1462
|
+
* Mirrors the TypeScript compiler's `factory.updateX` contract — pair it with the
|
|
1463
|
+
* structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
|
|
1464
|
+
* downstream passes can detect "nothing changed" by identity. Comparison is
|
|
1465
|
+
* shallow: a structurally-equal but newly-allocated array/object counts as a change.
|
|
1466
|
+
*
|
|
1467
|
+
* @example
|
|
1468
|
+
* ```ts
|
|
1469
|
+
* update(node, { name: node.name }) // -> same `node` reference
|
|
1470
|
+
* update(node, { name: 'renamed' }) // -> new node, `name` replaced
|
|
1471
|
+
* ```
|
|
1472
|
+
*/
|
|
1473
|
+
function update(node, changes) {
|
|
1474
|
+
for (const key in changes) if (changes[key] !== node[key]) return {
|
|
1475
|
+
...node,
|
|
1476
|
+
...changes
|
|
1477
|
+
};
|
|
1478
|
+
return node;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1389
1481
|
* Creates an `InputNode` with stable defaults for `schemas` and `operations`.
|
|
1390
1482
|
*
|
|
1391
1483
|
* @example
|
|
@@ -1404,11 +1496,31 @@ function createInput(overrides = {}) {
|
|
|
1404
1496
|
return {
|
|
1405
1497
|
schemas: [],
|
|
1406
1498
|
operations: [],
|
|
1499
|
+
meta: {
|
|
1500
|
+
circularNames: [],
|
|
1501
|
+
enumNames: []
|
|
1502
|
+
},
|
|
1407
1503
|
...overrides,
|
|
1408
1504
|
kind: "Input"
|
|
1409
1505
|
};
|
|
1410
1506
|
}
|
|
1411
1507
|
/**
|
|
1508
|
+
* Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
|
|
1509
|
+
*
|
|
1510
|
+
* @example
|
|
1511
|
+
* ```ts
|
|
1512
|
+
* const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
|
|
1513
|
+
* ```
|
|
1514
|
+
*/
|
|
1515
|
+
function createStreamInput(schemas, operations, meta) {
|
|
1516
|
+
return {
|
|
1517
|
+
kind: "Input",
|
|
1518
|
+
schemas,
|
|
1519
|
+
operations,
|
|
1520
|
+
meta
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1412
1524
|
* Creates an `OutputNode` with a stable default for `files`.
|
|
1413
1525
|
*
|
|
1414
1526
|
* @example
|
|
@@ -1430,35 +1542,35 @@ function createOutput(overrides = {}) {
|
|
|
1430
1542
|
};
|
|
1431
1543
|
}
|
|
1432
1544
|
/**
|
|
1433
|
-
* Creates
|
|
1434
|
-
*
|
|
1435
|
-
* @example
|
|
1436
|
-
* ```ts
|
|
1437
|
-
* const operation = createOperation({
|
|
1438
|
-
* operationId: 'getPetById',
|
|
1439
|
-
* method: 'GET',
|
|
1440
|
-
* path: '/pet/{petId}',
|
|
1441
|
-
* })
|
|
1442
|
-
* // tags, parameters, and responses are []
|
|
1443
|
-
* ```
|
|
1444
|
-
*
|
|
1445
|
-
* @example
|
|
1446
|
-
* ```ts
|
|
1447
|
-
* const operation = createOperation({
|
|
1448
|
-
* operationId: 'findPets',
|
|
1449
|
-
* method: 'GET',
|
|
1450
|
-
* path: '/pet/findByStatus',
|
|
1451
|
-
* tags: ['pet'],
|
|
1452
|
-
* })
|
|
1453
|
-
* ```
|
|
1545
|
+
* Creates a `ContentNode` for a single request-body or response content type.
|
|
1454
1546
|
*/
|
|
1547
|
+
function createContent(props) {
|
|
1548
|
+
return {
|
|
1549
|
+
...props,
|
|
1550
|
+
kind: "Content"
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Creates a `RequestBodyNode`, normalizing each content entry into a `ContentNode`.
|
|
1555
|
+
*/
|
|
1556
|
+
function createRequestBody(props) {
|
|
1557
|
+
return {
|
|
1558
|
+
...props,
|
|
1559
|
+
kind: "RequestBody",
|
|
1560
|
+
content: props.content?.map(createContent)
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1455
1563
|
function createOperation(props) {
|
|
1564
|
+
const { requestBody, ...rest } = props;
|
|
1565
|
+
const isHttp = rest.method !== void 0 && rest.path !== void 0;
|
|
1456
1566
|
return {
|
|
1457
1567
|
tags: [],
|
|
1458
1568
|
parameters: [],
|
|
1459
1569
|
responses: [],
|
|
1460
|
-
...
|
|
1461
|
-
|
|
1570
|
+
...rest,
|
|
1571
|
+
...isHttp ? { protocol: "http" } : {},
|
|
1572
|
+
kind: "Operation",
|
|
1573
|
+
requestBody: requestBody ? createRequestBody(requestBody) : void 0
|
|
1462
1574
|
};
|
|
1463
1575
|
}
|
|
1464
1576
|
/**
|
|
@@ -1572,19 +1684,29 @@ function createParameter(props) {
|
|
|
1572
1684
|
/**
|
|
1573
1685
|
* Creates a `ResponseNode`.
|
|
1574
1686
|
*
|
|
1687
|
+
* Response body schemas live inside `content`. For convenience a single legacy `schema`
|
|
1688
|
+
* (with optional `mediaType`/`keysToOmit`) is normalized into one `content` entry, so the same
|
|
1689
|
+
* schema is never stored both at the node root and inside `content`.
|
|
1690
|
+
*
|
|
1575
1691
|
* @example
|
|
1576
1692
|
* ```ts
|
|
1577
1693
|
* const response = createResponse({
|
|
1578
1694
|
* statusCode: '200',
|
|
1579
|
-
*
|
|
1580
|
-
* schema: createSchema({ type: 'object', properties: [] }),
|
|
1695
|
+
* content: [{ contentType: 'application/json', schema: createSchema({ type: 'object', properties: [] }) }],
|
|
1581
1696
|
* })
|
|
1582
1697
|
* ```
|
|
1583
1698
|
*/
|
|
1584
1699
|
function createResponse(props) {
|
|
1700
|
+
const { schema, mediaType, keysToOmit, content, ...rest } = props;
|
|
1701
|
+
const entries = content ?? (schema ? [{
|
|
1702
|
+
contentType: mediaType ?? "application/json",
|
|
1703
|
+
schema,
|
|
1704
|
+
keysToOmit: keysToOmit ?? null
|
|
1705
|
+
}] : void 0);
|
|
1585
1706
|
return {
|
|
1586
|
-
...
|
|
1587
|
-
kind: "Response"
|
|
1707
|
+
...rest,
|
|
1708
|
+
kind: "Response",
|
|
1709
|
+
content: entries?.map(createContent)
|
|
1588
1710
|
};
|
|
1589
1711
|
}
|
|
1590
1712
|
/**
|
|
@@ -1995,22 +2117,27 @@ function createJsx(value) {
|
|
|
1995
2117
|
//#endregion
|
|
1996
2118
|
//#region src/printer.ts
|
|
1997
2119
|
/**
|
|
1998
|
-
*
|
|
1999
|
-
*
|
|
2000
|
-
*
|
|
2120
|
+
* Defines a schema printer: a function that takes a `SchemaNode` and emits
|
|
2121
|
+
* code in your target language. Each plugin that produces code from schemas
|
|
2122
|
+
* (TypeScript types, Zod schemas, Faker factories) ships a printer built
|
|
2123
|
+
* with this helper.
|
|
2001
2124
|
*
|
|
2002
2125
|
* The builder receives resolved options and returns:
|
|
2003
|
-
* - `name` — a unique identifier for the printer
|
|
2004
|
-
* - `options` — options stored on the returned printer instance
|
|
2005
|
-
* - `nodes` — a map of `SchemaType` → handler functions that convert a `SchemaNode` to `TOutput`
|
|
2006
|
-
* - `print` _(optional)_ — top-level override exposed as `printer.print`
|
|
2007
|
-
* - Inside this function, use `this.transform(node)` to dispatch to the `nodes` map
|
|
2008
|
-
* - This keeps recursion safe and avoids self-calls
|
|
2009
2126
|
*
|
|
2010
|
-
*
|
|
2127
|
+
* - `name` — unique identifier for the printer.
|
|
2128
|
+
* - `options` — stored on the returned printer instance.
|
|
2129
|
+
* - `nodes` — map of `SchemaType` → handler. Handlers return the rendered
|
|
2130
|
+
* output (a string, a TypeScript AST node, ...) for that schema type.
|
|
2131
|
+
* - `print` (optional) — top-level override exposed as `printer.print`.
|
|
2132
|
+
* Use `this.transform(node)` inside it to dispatch to `nodes` recursively.
|
|
2011
2133
|
*
|
|
2012
|
-
*
|
|
2134
|
+
* Without a `print` override, `printer.print` falls back to `printer.transform`
|
|
2135
|
+
* (the node-level dispatcher).
|
|
2136
|
+
*
|
|
2137
|
+
* @example Tiny Zod printer
|
|
2013
2138
|
* ```ts
|
|
2139
|
+
* import { definePrinter, type PrinterFactoryOptions } from '@kubb/ast'
|
|
2140
|
+
*
|
|
2014
2141
|
* type PrinterZod = PrinterFactoryOptions<'zod', { strict?: boolean }, string>
|
|
2015
2142
|
*
|
|
2016
2143
|
* export const zodPrinter = definePrinter<PrinterZod>((options) => ({
|
|
@@ -2019,7 +2146,9 @@ function createJsx(value) {
|
|
|
2019
2146
|
* nodes: {
|
|
2020
2147
|
* string: () => 'z.string()',
|
|
2021
2148
|
* object(node) {
|
|
2022
|
-
* const props = node.properties
|
|
2149
|
+
* const props = node.properties
|
|
2150
|
+
* .map((p) => `${p.name}: ${this.transform(p.schema)}`)
|
|
2151
|
+
* .join(', ')
|
|
2023
2152
|
* return `z.object({ ${props} })`
|
|
2024
2153
|
* },
|
|
2025
2154
|
* },
|
|
@@ -2047,7 +2176,7 @@ function createPrinterFactory(getKey) {
|
|
|
2047
2176
|
options: resolvedOptions,
|
|
2048
2177
|
transform: (node) => {
|
|
2049
2178
|
const key = getKey(node);
|
|
2050
|
-
if (key ===
|
|
2179
|
+
if (key === null) return null;
|
|
2051
2180
|
const handler = nodes[key];
|
|
2052
2181
|
if (!handler) return null;
|
|
2053
2182
|
return handler.call(context, node);
|
|
@@ -2084,10 +2213,10 @@ function enumPropName(parentName, propName, enumSuffix) {
|
|
|
2084
2213
|
function collectImports({ node, nameMapping, resolve }) {
|
|
2085
2214
|
return collect(node, { schema(schemaNode) {
|
|
2086
2215
|
const schemaRef = narrowSchema(schemaNode, "ref");
|
|
2087
|
-
if (!schemaRef?.ref) return;
|
|
2216
|
+
if (!schemaRef?.ref) return null;
|
|
2088
2217
|
const rawName = extractRefName(schemaRef.ref);
|
|
2089
2218
|
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
2090
|
-
if (!result) return;
|
|
2219
|
+
if (!result) return null;
|
|
2091
2220
|
return result;
|
|
2092
2221
|
} });
|
|
2093
2222
|
}
|
|
@@ -2141,23 +2270,27 @@ function setDiscriminatorEnum({ node, propertyName, values, enumName }) {
|
|
|
2141
2270
|
* ])
|
|
2142
2271
|
* ```
|
|
2143
2272
|
*/
|
|
2144
|
-
function
|
|
2145
|
-
|
|
2273
|
+
function* mergeAdjacentObjectsLazy(members) {
|
|
2274
|
+
let acc;
|
|
2275
|
+
for (const member of members) {
|
|
2146
2276
|
const objectMember = narrowSchema(member, "object");
|
|
2147
|
-
if (objectMember && !objectMember.name) {
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
...
|
|
2153
|
-
properties: [...previousObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2277
|
+
if (objectMember && !objectMember.name && acc !== void 0) {
|
|
2278
|
+
const accObject = narrowSchema(acc, "object");
|
|
2279
|
+
if (accObject && !accObject.name) {
|
|
2280
|
+
acc = createSchema({
|
|
2281
|
+
...accObject,
|
|
2282
|
+
properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
|
|
2154
2283
|
});
|
|
2155
|
-
|
|
2284
|
+
continue;
|
|
2156
2285
|
}
|
|
2157
2286
|
}
|
|
2158
|
-
acc
|
|
2159
|
-
|
|
2160
|
-
}
|
|
2287
|
+
if (acc !== void 0) yield acc;
|
|
2288
|
+
acc = member;
|
|
2289
|
+
}
|
|
2290
|
+
if (acc !== void 0) yield acc;
|
|
2291
|
+
}
|
|
2292
|
+
function mergeAdjacentObjects(members) {
|
|
2293
|
+
return [...mergeAdjacentObjectsLazy(members)];
|
|
2161
2294
|
}
|
|
2162
2295
|
/**
|
|
2163
2296
|
* Removes enum members that are covered by broader scalar primitives in the same union.
|
|
@@ -2189,7 +2322,7 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2189
2322
|
const enumNode = narrowSchema(propNode, "enum");
|
|
2190
2323
|
if (enumNode?.primitive === "boolean") return {
|
|
2191
2324
|
...propNode,
|
|
2192
|
-
name:
|
|
2325
|
+
name: null
|
|
2193
2326
|
};
|
|
2194
2327
|
if (enumNode) return {
|
|
2195
2328
|
...propNode,
|
|
@@ -2198,6 +2331,6 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2198
2331
|
return propNode;
|
|
2199
2332
|
}
|
|
2200
2333
|
//#endregion
|
|
2201
|
-
export { caseParams, childName, collect, collectImports, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createResponse, createSchema, createSource, createText, createType, definePrinter, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, narrowSchema, nodeKinds, resolveRefName, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, walk };
|
|
2334
|
+
export { caseParams, childName, collect, collectImports, collectLazy, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createContent, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createRequestBody, createResponse, createSchema, createSource, createStreamInput, createText, createType, definePrinter, defineSchemaDialect, dispatch, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isHttpOperationNode, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, mergeAdjacentObjectsLazy, narrowSchema, nodeKinds, resolveRefName, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, update, walk };
|
|
2202
2335
|
|
|
2203
2336
|
//# sourceMappingURL=index.js.map
|