API Stability Notice
Macroforge is under active development. The API is not yet stable and may change between versions. Some documentation sections may be outdated.
Buildtime Evaluation
@buildtime runs a piece of TypeScript during the build and replaces it with its result. Reading a schema file, hashing a manifest, or stamping a build time all become compile-time constants rather than runtime work.
import { buildtime } from "macroforge/buildtime";
/** @buildtime */
const BUILT_AT = buildtime.time.iso();
/** @buildtime */
const SCHEMA = buildtime.fs.readJson("./schema.json");After expansion the imports and the buildtime calls are gone — the output contains only literals:
const BUILT_AT = "2026-07-21T10:30:00.000Z";
const SCHEMA = { "version": 1, "fields": [] };Evaluation happens in a sandboxed JavaScript engine (Boa), so build-time code can’t reach the network or write files.
The three tiers
What @buildtime does depends on what it’s attached to.
Tier 1 — const expression
The expression is evaluated and its result serialized to a TypeScript literal.
/** @buildtime */
const VERSION_HASH = buildtime.crypto.sha256(buildtime.fs.readText("./package.json"));Tier 2 — function
The function is called with no arguments. If it returns a string, that string is spliced into the output verbatim as source code — which is how you generate declarations. Any other return value is serialized as const NAME = <literal>.
/** @buildtime */
function generateGuards() {
const schema = buildtime.fs.readJson("./schema.json") as { fields: Field[] };
return schema.fields
.map((f) => `export function is${f.name}(v: unknown): v is ${f.type} { return typeof v === "${f.type}"; }`)
.join("\n");
}Tier 3 — type alias
The expression must return a string containing TypeScript type syntax. Returning anything else is an error.
/** @buildtime */
type SchemaKeys = (() => {
const schema = buildtime.fs.readJson("./schema.json") as { fields: { name: string }[] };
return schema.fields.map((f) => JSON.stringify(f.name)).join(" | ");
})();Declarations must be top-level. export visibility is preserved.
API
| Namespace | Members |
|---|---|
buildtime.fs | readText(path), readJson(path), exists(path), listDir(path) |
buildtime.crypto | sha256(input), sha512(input) |
buildtime.time | now(), unix(), iso() |
buildtime.location | file, line, column |
buildtime.env | Allow-listed environment variables |
buildtime.flags | has(name), get(name) |
The same object is also exported as $buildtime. Calling any of these outside a build pass throws.
Rebuild invalidation
Every file read through buildtime.fs is recorded and returned as buildtimeDependencies on the expansion result. The Vite plugin uses this to re-expand a module when a file it read at build time changes, so editing schema.json re-runs the generator without a manual restart.
Sandbox configuration
Build-time code runs sandboxed. Configure it with a buildtime block:
// macroforge.config.ts
export default {
buildtime: {
capabilities: {
timeout: 5000, // ms
maxHeap: 256, // MiB (advisory)
filesystem: { read: ["src/**"], write: [] },
env: ["NODE_ENV", "CI"],
network: false
},
flags: { RELEASE: "1", CHANNEL: "beta" }
}
};Capability keys may also be written flat (buildtime.timeout) if you prefer a shorter config; the nested form is canonical because it’s the path sandbox diagnostics point you at.
| Option | Default | Effect |
|---|---|---|
timeout | 5000 | Wall-clock budget in ms, enforced — an infinite loop fails the build rather than hanging it |
maxHeap | 256 | Heap ceiling in MiB. Advisory: Boa exposes no memory-limit hook, so it’s carried but not enforced |
filesystem.read | ["**"] | Globs readable via buildtime.fs |
filesystem.write | [] | Reserved — no write API is exposed to sandboxed code |
env | [] | Names exposed as buildtime.env.NAME; anything not listed is undefined |
network | false | Reserved — no network API is exposed |
flags | {} | Values returned by buildtime.flags |
A partial block only overrides what it names. Omitting buildtime entirely keeps every default.
Environment variables
buildtime.env is deny-by-default: a variable is only readable when you name it in env.
// macroforge.config.ts
export default { buildtime: { capabilities: { env: ["NODE_ENV"] } } };/** @buildtime */
const MODE = buildtime.env.NODE_ENV ?? "development";Build flags
flags gives you a build-time switch that doesn’t depend on the process environment:
// macroforge.config.ts
export default { buildtime: { flags: { CHANNEL: "beta" } } };/** @buildtime */
const CHANNEL = buildtime.flags.get("CHANNEL") ?? "stable";
/** @buildtime */
const IS_BETA = buildtime.flags.has("CHANNEL");get() returns undefined for an unset flag, and has() returns false. Flag values are strings; booleans and numbers written in config are stringified.