Skip to content

Evaluation Pipeline

When you run chant build, the tool takes your TypeScript source files through a series of stages to produce deployment-ready output.

Six-stage evaluation pipeline: Discovery → Parse+Check → Resolve Imports → Extract+Evaluate → Expand Composites → Emit
chant build evaluation pipeline

The evaluator scans the project directory for TypeScript source files, applying standard filters:

  • Include all .ts files in the source directory
  • Exclude *.test.ts and *.spec.ts
  • Exclude node_modules

Related: Project Structure

Stage 2: Type-Check and Evaluability (lint, not build)

Section titled “Stage 2: Type-Check and Evaluability (lint, not build)”

Type-checking and the evaluability rules are not part of chant build. They run in chant lint and in your editor. chant build runs under a TypeScript runtime that strips types without checking them.

Run chant lint, or rely on the editor, to catch type errors and patterns that are valid TypeScript but not statically evaluable. See TypeScript as Data for the full list. This stage is what guarantees the imported source stays inside the supported subset.

Related: Evaluability Rules

The tool builds a module graph by following import statements. This handles:

  • Relative imports (import { tags } from "./shared")
  • Lexicon imports (import { Bucket, Sub } from "@intentius/chant-lexicon-aws")
  • Re-exports (export { bucket } from "./bucket")

The module graph determines which files depend on which, and ensures that cross-file const references can be resolved during evaluation.

Related: Module Graph

chant imports each discovered file with the TypeScript runtime and collects the resource objects it exports.

export const <name> = new <ResourceType>({...})

Running the file does the resolution. The language evaluates the object literal, applies spreads from const sources, and follows cross-file imports. Lexicon constructors turn new <ResourceType>({...}) into a Declarable. Lexicon-registered tagged templates produce intrinsic objects. Accessing resource.attribute on an imported resource yields a symbolic reference.

The evaluability rules from Stage 2 keep this safe. They guarantee the file holds only literals, constants, spreads from constants, and cross-resource references, so importing it has no side effects and always produces the same objects.

Related: Evaluator Engine

Composite resources are factory patterns that produce multiple related resources from a single declaration. After all resources are extracted, the evaluator expands composite instances:

  1. Identify composite instances in the extracted resources
  2. Substitute prop values into the composite’s template
  3. Resolve siblings references within the composite
  4. Emit prefixed child resources (e.g., myApi.function, myApi.role)

Note: nested deployment units (e.g. AWS nested stacks) are not composites — they are child projects. A child project is a separate subdirectory that builds independently. The parent references it via a lexicon-specific function (e.g. nestedStack()), and discovery skips child project directories entirely. See Multi-Stack Projects for an overview.

Related: Composite Resources, Multi-Stack Projects, Child Projects (Lexicon Authoring)

The evaluated resources are passed to the lexicon serializer, which produces the target output format. During serialization, the emitter:

  • Converts reference values to the target format’s reference mechanism
  • Maps TypeScript property names to the target format’s naming convention
  • Omits null and missing properties

See your lexicon’s serialization documentation for concrete output examples.

Related: Serializer, Lexicon Registry

Errors are non-fatal per resource. If one resource fails evaluation, the evaluator reports the error (with file path, line, and column) and continues to the next resource. All errors are collected and reported at the end.

The evaluator caches two things during a single chant build run:

  • Evaluated const bindings — each binding is evaluated once, even if referenced by multiple resources
  • Import resolution — each imported binding is resolved once, even if referenced from multiple files

There is no persistent cache across runs.