alkgen
The text-generation engine of the alk* stack: handlebars-rust templates rendered over serde-serializable contexts with strict, deterministic output. One crate, one purpose: register a template set, render its templates, get the same bytes every time.
Status: 0.1.0 publishes the core, proven fit for its first consumer, alkforge's server-side HTML rendering. The engine is consumer-blind: it owns no template sets and no assumptions about what the rendered text is. HTML, Rust, and WGSL are all the same shape to it.
The engine's contract
- Strict mode is always on. A missing context variable is a typed error, never silently empty output. A silent gap in generated text becomes a bug in the consumer's build, far from its cause; the engine refuses to produce one.
- One registry per template set. A
TemplateSetis the unit: strict mode, the set's escaper, and its namespace for templates, partials, and helpers all live there. A partial's name is never a render target; the set tracks the difference and rejects a partial-as-target as a typed error. - Consumer-supplied templates register through the same machinery
the engine would use. Template text arrives as
&str(inline literals,include_str!, caller-built strings); there is no filesystem path anywhere in the API, so the core is wasm-clean by construction. - Typed errors, no panics. Malformed template text fails at
registration; render failures (missing variable, unknown template,
unknown helper) fail at render. Every failure maps to exactly one
variant of the public
Errorenum (non-exhaustive; the substrate's detail stays reachable through the error's source chain). Template sets may add helpers, which must be pure: no time, no environment, no randomness, no I/O. - Deterministic, byte-identical output. Repeated renders and rebuilt registries produce the same bytes, and there is no hash-map iteration order that can leak into them. This is what makes consumer-side caching and checked-in generated artifacts safe.
- Two sinks.
renderreturns aString;render_to_writestreams to anyio::Writetarget. Both produce byte-identical output. The write sink is not atomic: a render that fails partway may leave earlier bytes in the target, and consumers that need atomicity buffer first. - Escaping is a per-set choice.
EscapePolicy::Htmlkeeps the substrate's HTML entity escaping on (with{{& }}as the deliberate per-expression opt-out);EscapePolicy::NoOppasses interpolations through verbatim for code generation, where output should be escaped at the source, not mangled by entity escaping.
Usage
use alkgen::{EscapePolicy, TemplateSet};
use serde_json::json;
let mut set = TemplateSet::new(EscapePolicy::Html);
set.register_template("page", "<h1>{{title}}</h1>{{> footer}}")?;
set.register_partial("footer", "<p>{{year}}</p>")?;
let ctx = json!({ "title": "Hello", "year": "2026" });
assert_eq!(set.render("page", &ctx)?, "<h1>Hello</h1><p>2026</p>");
# Ok::<(), alkgen::Error>(())
Integration patterns
How rendered text reaches its consumer; the render machinery is
identical in every posture (see docs/architecture/emit.md, which
ships in the package):
- In-process SSR (the first real consumer: alkforge): link the crate, register your page templates and fragment partials, render on demand or behind your own cache. Determinism is what makes caching safe to add later. Render failure is a typed error your error-page mapping owns.
- Build-time code generation: call the API from a
build.rsscript (there is no CLI in v1, by design), render each document, and write the files yourself: the engine returns text and owns nothing about file placement. For artifacts that get checked in, the family's hygiene convention applies at the emit layer: generated files start with an "Auto-generated by alkgen. DO NOT EDIT." banner and regeneration is diff-quiet because rendering is deterministic. - Render-to-compile (future): render shader or other target text
and feed it to a downstream compiler, the alkminer-v1
ShaderBuildershape. No new surface is required from the engine for this.
What 0.1.0 does not ship
Engine-owned template sets: the BAST codec set (binary
readers/writers/validators for alkcall channels), protocol handler
sets, and the WGSL set are future activations, each behind its own
review; the bast-codegen cargo feature is declared but gates
nothing. The ssr-html target is proven in-crate through
consumer-shaped fixture sets that register through the same public
API a real consumer uses. Your template sets are yours: register them
from your crate through this API.
Design record
docs/architecture/ ships in the package: the component spec
(core.md, emit.md), the activation queue (targets.md), and the
decision records (ADR-001 through 010) this README summarizes.
License
Dual-licensed under MIT or Apache-2.0, at your option.