Build-Time Parameters
A real application often needs to vary a build: which environment, which tier, which region. The naive way is process.env.LOOM_TIER read at module scope. It works, and it is exactly the kind of value TypeScript as Data refuses to fold — an ambient read depends on whatever process happens to be running the build, not on the source. Build-time parameters are the supported alternative: the same environment-dependence, moved out of module scope and into the build invocation, where it is explicit, validated, and recorded.
Declare
Section titled “Declare”Declare each parameter in chant.config.ts’s buildParams:
import type { ChantConfig } from "@intentius/chant";
export default { buildParams: { tier: { type: "string", enum: ["light", "production", "production-ha"], default: "light", }, env: { type: "string", default: "dev", // Opt-in, EXPLICIT env-var fallback — the only place an env var may // feed a build-time parameter. Reading process.env directly from // project source is never supported. env: "LOOM_ENV", }, },} satisfies ChantConfig;Each parameter declares a type ("string" | "number" | "boolean"), an optional default, an optional enum of allowed values, and an optional env mapping.
Supply
Section titled “Supply”# Highest precedence — repeatablechant build --param tier=production --param env=staging
# Second precedence — a JSON file of { "name": value }chant build --params-file ./params.prod.json
# Third precedence — only when the parameter declares an `env` mappingLOOM_ENV=staging chant build
# Lowest precedence — chant.config.ts's declared defaultchant buildPrecedence, most to least specific: --param > --params-file > the parameter’s own declared env mapping > default. A parameter with no default and no supplied value is a build error naming the parameter — never a silently-undefined value, and never a thrown error from inside project source:
build parameter "tier" has no value — pass --param tier=<value>, use --params-file, or add a default in chant.config.ts's buildParamsbuild parameter "tier" must be one of "light", "production", "production-ha", got "bogus"This replaces a hand-written validator like:
function tierFromEnv(): Tier { const raw = process.env.LOOM_TIER ?? "light"; if (!VALID_TIERS.includes(raw)) throw new Error(`LOOM_TIER must be one of ${VALID_TIERS.join(", ")}, got "${raw}"`); return raw as Tier;}A declared enum reports the same violation as a build error naming the parameter, instead of a thrown Error from inside a function chant has no way to attribute.
Reference
Section titled “Reference”Source imports the resolved values from @intentius/chant/params — never reads process.env:
import { params } from "@intentius/chant/params";
export const tier = params.tier as Tier;export const env = params.env ?? "dev";params is a plain object, so ordinary property access, optional chaining, and ?? all work exactly as they do on any other value.
Why it folds
Section titled “Why it folds”Because the value is known at build invocation — before any project file is imported or folded — a params.<name> reference resolves to a literal, not a symbolic node, and not a function call. env: process.env.LOOM_ENV ?? "dev" cannot fold: the value comes from ambient state at module-evaluation time. env: params.env folds to "dev" (or whatever the build was invoked with) the same way a const does, so chant build --fold reduces it with zero module execution.
Determinism is preserved, not weakened: same parameters in, same output out. The parameters are supplied explicitly and recorded (BuildResult.buildParams), rather than read invisibly from whatever environment happened to be running the build.
A pointed error for process.env
Section titled “A pointed error for process.env”chant build --fold rejects a bare process reference with a message naming this mechanism, not the generic “unresolved identifier”:
ambient "process" read is not foldable — declare a build-time parameter instead(chant.config.ts's buildParams + `chant build --param name=value`/`--params-file`)and reference it via `import { params } from "@intentius/chant/params"`,rather than reading process.env directlySee also
Section titled “See also”- TypeScript as Data — the fold subset a
params.<name>reference resolves within - Where Values Come From — how a build-time parameter fits among static data, deploy-time inputs, and refused runtime lookups
- Parameters & Outputs — the deploy-time
Parameter(), a different concept chant buildCLI reference —--param/--params-fileflags