@kubb/ast 5.0.0-beta.75 → 5.0.0-beta.8

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/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@kubb/ast",
3
- "version": "5.0.0-beta.75",
4
- "description": "Spec-agnostic AST layer for Kubb. Defines nodes, visitor pattern, and factory functions used across codegen plugins.",
3
+ "version": "5.0.0-beta.8",
4
+ "description": "Spec-agnostic AST layer for Kubb. Defines the node tree, visitor pattern, factory functions, and type guards used across all code generation plugins.",
5
5
  "keywords": [
6
6
  "ast",
7
7
  "codegen",
8
8
  "kubb",
9
- "openapi",
10
9
  "typescript"
11
10
  ],
12
11
  "license": "MIT",
@@ -40,7 +39,7 @@
40
39
  "registry": "https://registry.npmjs.org/"
41
40
  },
42
41
  "devDependencies": {
43
- "@types/node": "^25.6.0",
42
+ "@types/node": "^22.19.19",
44
43
  "@internals/utils": "0.0.0"
45
44
  },
46
45
  "engines": {
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export type * from './types.ts'
33
33
  export {
34
34
  caseParams,
35
35
  collectReferencedSchemaNames,
36
+ collectUsedSchemaNames,
36
37
  containsCircularRef,
37
38
  createDiscriminantNode,
38
39
  createOperationParams,
package/src/utils.ts CHANGED
@@ -652,6 +652,16 @@ export function combineImports(imports: Array<ImportNode>, exports: Array<Export
652
652
  const exportedNames = new Set(exports.flatMap((e) => (Array.isArray(e.name) ? e.name : e.name ? [e.name] : [])))
653
653
  const isUsed = (importName: string): boolean => !source || source.includes(importName) || exportedNames.has(importName)
654
654
 
655
+ // Memoize object import names so the same logical (propertyName, name) pair always
656
+ // reuses the same object reference — Set-based deduplication then works correctly.
657
+ const importNameMemo = new Map<string, { propertyName: string; name?: string }>()
658
+ const canonicalizeName = (n: string | { propertyName: string; name?: string }): string | { propertyName: string; name?: string } => {
659
+ if (typeof n === 'string') return n
660
+ const key = `${n.propertyName}:${n.name ?? ''}`
661
+ if (!importNameMemo.has(key)) importNameMemo.set(key, n)
662
+ return importNameMemo.get(key)!
663
+ }
664
+
655
665
  const result: Array<ImportNode> = []
656
666
  // Accumulates array-named imports keyed by `path:isTypeOnly` for name-merging
657
667
  const namedByPath = new Map<string, ImportNode>()
@@ -669,7 +679,7 @@ export function combineImports(imports: Array<ImportNode>, exports: Array<Export
669
679
  let { name } = curr
670
680
 
671
681
  if (Array.isArray(name)) {
672
- name = [...new Set(name)].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.propertyName)))
682
+ name = [...new Set(name.map(canonicalizeName))].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.name ?? item.propertyName)))
673
683
  if (!name.length) continue
674
684
 
675
685
  const key = pathTypeKey(path, isTypeOnly)
@@ -751,7 +761,19 @@ export function resolveRefName(node: SchemaNode | undefined): string | undefined
751
761
  * Refs are followed by name only — the resolved `node.schema` is not traversed inline.
752
762
  * Use this to determine schema dependencies, build reference graphs, or detect what schemas need to be emitted.
753
763
  *
754
- * @note Returns a Set of schema names for efficient membership testing.
764
+ * @example Collect refs from a single schema
765
+ * ```ts
766
+ * const names = collectReferencedSchemaNames(petSchema)
767
+ * // → Set { 'Category', 'Tag' }
768
+ * ```
769
+ *
770
+ * @example Accumulate refs from multiple schemas into one set
771
+ * ```ts
772
+ * const out = new Set<string>()
773
+ * for (const schema of schemas) {
774
+ * collectReferencedSchemaNames(schema, out)
775
+ * }
776
+ * ```
755
777
  */
756
778
  export function collectReferencedSchemaNames(node: SchemaNode | undefined, out: Set<string> = new Set()): Set<string> {
757
779
  if (!node) return out
@@ -768,6 +790,66 @@ export function collectReferencedSchemaNames(node: SchemaNode | undefined, out:
768
790
  return out
769
791
  }
770
792
 
793
+ /**
794
+ * Collects the names of all top-level schemas transitively used by a set of operations.
795
+ *
796
+ * An operation uses a schema when any of its parameters, request body content, or responses
797
+ * reference it — directly or indirectly through other named schemas.
798
+ * The walk is iterative and safe against reference cycles.
799
+ *
800
+ * Use this together with `include` filters to determine which schemas from `components/schemas`
801
+ * are reachable from the allowed operations, so that schemas used only by excluded operations
802
+ * are not generated.
803
+ *
804
+ * @example Only generate schemas referenced by included operations
805
+ * ```ts
806
+ * const includedOps = inputNode.operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
807
+ * const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
808
+ *
809
+ * for (const schema of inputNode.schemas) {
810
+ * if (schema.name && !allowed.has(schema.name)) continue
811
+ * // … generate schema
812
+ * }
813
+ * ```
814
+ *
815
+ * @example Check whether a specific schema is needed
816
+ * ```ts
817
+ * const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
818
+ * allowed.has('OrderStatus') // false when no included operation references OrderStatus
819
+ * ```
820
+ */
821
+ export function collectUsedSchemaNames(operations: ReadonlyArray<OperationNode>, schemas: ReadonlyArray<SchemaNode>): Set<string> {
822
+ const schemaMap = new Map<string, SchemaNode>()
823
+ for (const schema of schemas) {
824
+ if (schema.name) {
825
+ schemaMap.set(schema.name, schema)
826
+ }
827
+ }
828
+
829
+ const result = new Set<string>()
830
+
831
+ function visitSchema(schema: SchemaNode): void {
832
+ const directRefs = collectReferencedSchemaNames(schema)
833
+ for (const name of directRefs) {
834
+ if (!result.has(name)) {
835
+ result.add(name)
836
+ const namedSchema = schemaMap.get(name)
837
+ if (namedSchema) {
838
+ visitSchema(namedSchema)
839
+ }
840
+ }
841
+ }
842
+ }
843
+
844
+ for (const op of operations) {
845
+ for (const schema of collect<SchemaNode>(op, { depth: 'shallow', schema: (node) => node })) {
846
+ visitSchema(schema)
847
+ }
848
+ }
849
+
850
+ return result
851
+ }
852
+
771
853
  /**
772
854
  * Identifies all schemas that participate in circular dependency chains, including direct self-loops.
773
855
  *