Evaluator Engine
chant has two ways to turn a source file’s exported resources into structured data for the lexicon serializer: reduce their AST to a value directly (the default), or run them for real (the per-file fallback, and what --no-fold forces). This page covers both — see TypeScript as Data for the full comparison.
Default: fold instead of run
Section titled “Default: fold instead of run”chant build folds by default since #1134 (mechanism from #1022/#1026/#1023, epic #1019): it skips execution for files that stay inside the fold subset. It walks top-level statements in the source file directly — looking for an exported new Type({...}) resource, or a composite factory call — and recursively reduces the property object into a value tree from the AST, with zero module execution. This is the literal “read the AST as data” mechanism; it’s implemented in packages/core/src/fold/{fold,subset}.ts.
A file outside the subset falls back to importing and running it instead, transparently, logged as [fold:run] <reason> under chant build --verbose (a count otherwise). A folded file and a run file produce byte-identical serialized output (#1025) — see Folded vs Run for what folds today and what still falls back.
The fallback: import and run
Section titled “The fallback: import and run”A run-fallback file (and every file under --no-fold) is dynamically imported — real module execution, the same as any other TypeScript entry point. new ResourceType({...}) really constructs a Declarable; a composite factory call really runs its factory function. Nothing here is a bespoke evaluator: it’s the TypeScript runtime.
What keeps that safe is the evaluability lint rules (EVL), covered in TypeScript as Data: they reject anything a resource file could do besides read literals, constants, and cross-resource references, so running the supported subset has no side effects and always yields the same result. EVL runs in chant lint and your editor, not inside chant build itself — see Evaluability Rules for how that diagnostic layer relates to the enforcement mechanism above.
Supported expressions (fold subset)
Section titled “Supported expressions (fold subset)”- Literals: strings, numbers, booleans,
null, arrays, object literals - Identifiers: references to
constbindings (traced to their initializer) - Property access:
obj.keyon known objects, including the cross-resource{ __attrRef }case - Spread:
{...defaults, name: "x"}fromconstsources - Ternary:
cond ? a : b - Binary expressions: arithmetic, string concatenation, comparisons
- Nullish coalescing and logical operators:
left ?? right,&&,|| - Tagged templates: registered lexicon intrinsic tags
This is the same subset the evaluability rules (EVL001/EVL003) flag violations of — one shared definition (fold/subset.ts), imported by both, with a short list of environment-dependent exceptions where the two can’t fully agree; see Evaluability Rules for the list. Expressions outside this subset make fold() reject that one file; they don’t stop the default run path at all, since running plain JavaScript imposes no such restriction on its own.
Output
Section titled “Output”Each extracted resource — folded or run — produces an evaluated resource with:
- LogicalName — derived from the export binding name
- ResourceType — provider type string from the resource registry
- Props — ordered map of evaluated property values
Composite support
Section titled “Composite support”A Composite({...}) definition is a factory function; running it (default path) or resolving and invoking it through the file’s imports with folded arguments (--fold path, #1023) both expand its resource map the same way, resolving siblings references between member resources.