TypeScript as Data
chant reads the resource objects your TypeScript exports. It supports a specific subset of the language, the part whose value is fully determined by literals, constants, and cross-resource references. chant build imports and runs your files to collect those objects. The subset exists so that running them has no side effects and always yields the same output. This page documents exactly which patterns are supported and which are not.
Supported Patterns
Section titled “Supported Patterns”Resource declarations
Section titled “Resource declarations”The core pattern: export a const binding initialized with a typed resource constructor.
import { StorageType } from "@intentius/chant-lexicon-<name>";
export const store = new StorageType({ name: "my-data", versioned: true,});The evaluator extracts the export name (store), resolves the resource type from the lexicon, and evaluates the constructor argument.
Literal values
Section titled “Literal values”Strings, numbers, booleans, null, arrays, and nested object literals are evaluated directly.
import { ServiceType } from "@intentius/chant-lexicon-<name>";
export const service = new ServiceType({ name: "handler", timeout: 30, memorySize: 128, environment: { variables: { DEBUG: "false", }, },});Const variable references
Section titled “Const variable references”const bindings with literal initializers can be referenced by name. The evaluator traces the reference to its initializer and evaluates it.
import { StorageType } from "@intentius/chant-lexicon-<name>";
const tags = { project: "myapp", env: "prod" };
export const store = new StorageType({ name: "data", tags: tags,});Only const bindings are supported. let and var are mutable and cannot be statically traced.
Spread from const sources
Section titled “Spread from const sources”Object spread is supported when the source is a const binding with a known literal value.
import { ServiceType } from "@intentius/chant-lexicon-<name>";
const defaults = { timeout: 30, memorySize: 128 };
export const service = new ServiceType({ ...defaults, name: "handler",});Import and re-export
Section titled “Import and re-export”Standard import statements are followed through the module graph. The evaluator resolves imported names to their definitions in other files.
import { StorageType } from "@intentius/chant-lexicon-<name>";import { sharedTags } from "./shared";
export const store = new StorageType({ name: "data", tags: sharedTags,});Cross-file resource references
Section titled “Cross-file resource references”Import a resource from another file and access its attributes. The evaluator resolves the attribute through the lexicon’s attribute registry.
import { ServiceType } from "@intentius/chant-lexicon-<name>";import { dataTable } from "./table";
export const service = new ServiceType({ environment: { variables: { TABLE_ARN: dataTable.arn, }, },});Intrinsic tagged templates
Section titled “Intrinsic tagged templates”Lexicons can register tagged template literals for provider-specific intrinsics (e.g., string substitution with deploy-time values):
import { StorageType, Intrinsic, Params } from "@intentius/chant-lexicon-<name>";
export const store = new StorageType({ name: Intrinsic`${Params.StackName}-data`,});See your lexicon’s documentation for available intrinsics.
Typed property-kind constructors
Section titled “Typed property-kind constructors”Sub-resource properties can use typed constructors for autocompletion and validation:
import { EncryptionConfig, AccessConfig } from "@intentius/chant-lexicon-<name>";
export const config = new EncryptionConfig({ algorithm: "AES256",});
export const access = new AccessConfig({ publicAccess: false,});Nullish coalescing for defaults
Section titled “Nullish coalescing for defaults”The ?? operator is supported for providing default values, particularly useful in composite props.
timeout: props.timeout ?? 30,Unsupported Patterns
Section titled “Unsupported Patterns”The following patterns are caught by the evaluability lint rules (EVL). Your editor shows them as lint errors, and chant lint reports them in CI. They are not enforced by chant build, so run lint, or rely on your editor, to keep source inside the supported subset.
Function calls as values
Section titled “Function calls as values”// EVL001: Expression not statically evaluableexport const store = new StorageType({ name: getName(), // function call — not a literal});Control flow around resources
Section titled “Control flow around resources”// EVL002: Resource inside control flowif (env === "prod") { export const store = new StorageType({...});}Resources must be top-level exports. For environment-specific configuration, use separate files or composites with different prop values.
Dynamic property access
Section titled “Dynamic property access”// EVL003: Dynamic property accessconst name = config[key];Spread from dynamic sources
Section titled “Spread from dynamic sources”// EVL004: Spread from non-const sourceexport const store = new StorageType({ ...getDefaults(),});Other unsupported patterns
Section titled “Other unsupported patterns”let/varbindings — onlyconstis statically traceable- Class declarations —
class MyStore extends StorageType {...} - Template literals without known tags — only lexicon-registered tags
- Computed property names —
{ [key]: value } require()— onlyimportstatements- Top-level
await - Decorators
For the full EVL rule reference with configuration options, see Evaluability Rules.
Why These Constraints?
Section titled “Why These Constraints?”The supported subset is the set of TypeScript patterns whose value is fully determined by literals, known constants, and symbolic cross-resource references. chant build imports and runs your files. The evaluability rules reject patterns that would make the result depend on anything else, so running the supported subset has no side effects.
This makes synthesis fast, deterministic for conforming source (same source, same output), and auditable (every output value traces to a specific line in the source).
The static boundary also defines a natural division of labor with agentic workflows: agents resolve dynamic values (looking up VPC IDs, reading secrets, querying state), then write or populate chant source files. chant synthesizes the deterministic structure. Agents handle what changes; chant handles what shouldn’t.
For where each kind of value belongs — static data resolved at synthesis, a deploy-time input resolved at apply, or a lookup synthesis refuses — see Where Values Come From.