11import ts , { FunctionDeclaration } from "typescript" ;
22import { getValues } from "./values.util" ;
3- import { getReferences } from "./references.util" ;
3+ import { getReferences , isRecursiveType } from "./references.util" ;
44import { getNodes } from "./nodes.util" ;
55import {
66 FunctionDefinition ,
@@ -94,6 +94,15 @@ export type Schema =
9494 | GenericInput ;
9595
9696
97+ /**
98+ * Maximum object nesting depth for schema generation through recursive data
99+ * types. Same policy as reference path extraction: the visited set keeps each
100+ * individual branch finite, but a cluster of mutually recursive types still
101+ * allows combinatorially many simple paths, so those are additionally
102+ * depth-capped. Non-recursive nesting is expanded exhaustively.
103+ */
104+ const MAX_SCHEMA_DEPTH = 7 ;
105+
97106/**
98107 * Generates a schema definition for a given TypeScript type.
99108 *
@@ -126,14 +135,16 @@ export const getSchema = (
126135 functions : FunctionDefinition [ ] ,
127136 suggestions : boolean = true ,
128137 suggestionType ?: ts . Type ,
138+ visited : Set < ts . Type > = new Set ( ) ,
139+ recursionCache : Map < ts . Type , boolean > = new Map ( ) ,
129140) : Schema => {
130141
131142 if ( ( parameterType . flags & ts . TypeFlags . TypeParameter ) !== 0 ) {
132143 const decl = parameterType . symbol ?. declarations ?. [ 0 ]
133144 if ( decl && ts . isTypeParameterDeclaration ( decl ) && decl . constraint ) {
134145 // getTypeFromTypeNode statt getBaseConstraintOfType → aliasSymbol bleibt erhalten
135146 const constraintType = checker . getTypeFromTypeNode ( decl . constraint )
136- return getSchema ( checker , node , constraintType , functionDeclarations , functions , suggestions , suggestionType )
147+ return getSchema ( checker , node , constraintType , functionDeclarations , functions , suggestions , suggestionType , visited , recursionCache )
137148 }
138149 }
139150
@@ -176,7 +187,7 @@ export const getSchema = (
176187 ( t ) => ( t . flags & ( ts . TypeFlags . Undefined | ts . TypeFlags . Null ) ) === 0
177188 )
178189 if ( nonNullish . length === 1 ) {
179- const baseSchema = getSchema ( checker , node , nonNullish [ 0 ] , functionDeclarations , functions , false )
190+ const baseSchema = getSchema ( checker , node , nonNullish [ 0 ] , functionDeclarations , functions , false , undefined , visited , recursionCache )
180191 return { ...baseSchema , ...combinedSuggestions }
181192 }
182193 }
@@ -215,7 +226,7 @@ export const getSchema = (
215226 const itemSchemas = itemTypes . flatMap ( itemType => {
216227 const itemTypes = itemType . isUnion ( ) ? itemType . types : [ itemType ] ;
217228 return itemTypes . map ( ( itemType ) =>
218- getSchema ( checker , node , itemType , functionDeclarations , functions , suggestions )
229+ getSchema ( checker , node , itemType , functionDeclarations , functions , suggestions , undefined , visited , recursionCache )
219230 )
220231 } )
221232
@@ -229,6 +240,22 @@ export const getSchema = (
229240
230241 // Handle complex object types with properties
231242 if ( ( parameterType . flags & ts . TypeFlags . Object ) !== 0 ) {
243+ // Recursive data types (e.g. Order.deliveries[].order) are cut off via
244+ // the visited set — the checker caches type identities, so a cycle
245+ // revisits the same ts.Type object. The set only tracks the current
246+ // branch (backtracked below) so the same type may still be expanded on
247+ // sibling paths. The depth cap only applies to types that are part of a
248+ // reference cycle (checked last, it's the expensive test); purely nested
249+ // non-recursive objects are expanded to arbitrary depth.
250+ if (
251+ visited . has ( parameterType ) ||
252+ ( visited . size >= MAX_SCHEMA_DEPTH &&
253+ isRecursiveType ( parameterType , checker , recursionCache ) )
254+ ) {
255+ return { input : "data" , ...combinedSuggestions } ;
256+ }
257+ visited . add ( parameterType ) ;
258+
232259 const properties : Record < string , Schema | Schema [ ] > = { } ;
233260 const required : string [ ] = [ ] ;
234261
@@ -261,7 +288,7 @@ export const getSchema = (
261288
262289 // Recursively generate schemas for property types
263290 const propertySchemas = propertyTypes . map ( ( type ) =>
264- getSchema ( checker , node , type , functionDeclarations , functions , suggestions )
291+ getSchema ( checker , node , type , functionDeclarations , functions , suggestions , undefined , visited , recursionCache )
265292 ) ;
266293
267294 properties [ property . name ] =
@@ -273,6 +300,8 @@ export const getSchema = (
273300 }
274301 }
275302
303+ visited . delete ( parameterType ) ;
304+
276305 return {
277306 input : "data" ,
278307 properties,
0 commit comments