langium-zod / detectRecursiveTypes
Function: detectRecursiveTypes()
function detectRecursiveTypes(descriptors): Set<string>;Defined in: recursion-detector.ts:88
Detects type names that participate in a reference cycle across the descriptor graph.
Builds a directed graph where each object type descriptor is a node and each type reference in its properties is an edge. A depth-first search then identifies all nodes that belong to at least one cycle. The generator uses this set to emit getter-based property accessors instead of direct value expressions, avoiding JavaScript "used before declaration" errors for mutually-recursive Zod schemas.
Only 'object' kind descriptors are considered; union and primitive-alias descriptors are transparent to cycle detection.
Parameters
| Parameter | Type | Description |
|---|---|---|
descriptors | ZodTypeDescriptor[] | The full list of type descriptors to analyse, as returned by extractTypeDescriptors. |
Returns
Set<string>
A Set of type names that are involved in at least one reference cycle.
Remarks
A Langium grammar with a rule like Expression: value=Expression | ... is self-referential at the object level. Without getter syntax, the emitted const ExpressionSchema = z.object({ value: ExpressionSchema }) would fail at runtime because ExpressionSchema is referenced before it is defined. The returned set marks Expression as recursive so the generator can emit get value() { return ExpressionSchema; } instead.
Mutual cycles (A → B → A) are also detected: both A and B will appear in the returned set. Diamond-shaped references (A → C and B → C, but no back-edge) are not cycles and will not appear in the result.
Example
import { extractTypeDescriptors, detectRecursiveTypes } from 'langium-zod';
import { collectAst } from 'langium/grammar';
const descriptors = extractTypeDescriptors(collectAst(myGrammar));
const recursive = detectRecursiveTypes(descriptors);
console.log([...recursive]); // e.g. ['Expression', 'Statement']Use When
- You need to know which grammar types are recursive before calling generateZodCode (e.g. to log or filter them).
- You are building a custom code emitter and need the same cycle information that the built-in generator uses.
Avoid When
- You are using the standard pipeline — generateZodSchemas and generateZodCode call this function internally; you do not need to call it yourself.
Never
- NEVER pass descriptors that have already had projection applied (via
applyProjectionToDescriptors) to this function if the projection strips properties that close cycles. BECAUSE the cycle detection graph will miss the back-edge and fail to mark those types as recursive, leading toundefinedreference errors in the generated schemas at runtime. - NEVER assume the returned set is stable across different filter configurations. BECAUSE filtering with
include/excludecan remove types that close a cycle, making previously recursive types appear acyclic.