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

NamespaceMembers
buildtime.fsreadText(path), readJson(path), exists(path), listDir(path)
buildtime.cryptosha256(input), sha512(input)
buildtime.timenow(), unix(), iso()
buildtime.locationfile, line, column
buildtime.envAllow-listed environment variables
buildtime.flagshas(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.

OptionDefaultEffect
timeout5000Wall-clock budget in ms, enforced — an infinite loop fails the build rather than hanging it
maxHeap256Heap 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
networkfalseReserved — 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.