forma-ir
FMIR binary format parser, walker, and HTML generator for the Forma Stack. Parses compiled component IR and generates server-rendered HTML with hydration markers — no JavaScript runtime needed.
Install
[]
= "0.2.2"
What It Does
- Parses
.irbinary files (FMIR format) into a validatedIrModule - Walks the opcode stream and generates HTML with proper escaping
- Fills dynamic values (text, attributes, conditionals, lists) from typed
SlotData - Emits hydration markers (
<!--f:t0-->,<!--f:s0-->, etc.) for FormaJS client-side hydration, except insidetextarea,title,styleandscript, whose content HTML reads as text - Writes form-control values as HTML reads them: a
textarea'svalueas its text, aselect's asselectedon the matching option - Renders a list in O(rows), with a committed regression gate that fails the build if that changes (
tests/list_scaling.rs)
The on-disk layout and its stability contract are documented in
docs/FMIR-FORMAT.md.
Quick Start
use ;
// Parse a compiled .ir binary
let module = parse?;
// Fill slots with runtime data
let slots = from_json?;
// Generate HTML
let html = walk_to_html?;
walk_to_html_counted(&module, &slots, limits) writes the same HTML and also
returns what it wrote (WalkCounts: nodes, bytes, list rows); on failure it
returns a WalkFailure with the counts reached and the component path, the
islands open where the walk stopped. It catches a panic inside the walk as
IrError::Panicked, while the application unwinds on panic.
FMIR v3 development
The current set is 3.0.0-alpha.2, a pre-release on crates.io (the development version between releases is 3.0.0-dev). Modules carry a 24-byte signature/version/length/CRC
header and a variable section table; the reader accepts writers of its own major up to its own minor; patch is ignored.
Package prerelease suffixes are absent from the numeric wire triple.
V2 modules require rebuilding. IrModule::parse validates every bytecode string reference,
props schemas, key paths and optional source positions before rendering; IrError::code()
provides stable LOAD- diagnostics. See FMIR-FORMAT.md
for the maintained contract and CHANGELOG.md for the breaking changes.
dump::dump_ir is a lossless canonical text representation shared with the compiler. It
includes every section, including unknown optional bytes and island extensions.
The wasm feature and its JavaScript exports were removed in 3.0.0. Browser
islands re-render with @getforma/core. walk_island remains internal and is
not a supported API; server callers use the full-page walker.
Declared page props (3.0.0 development)
#[derive(forma_ir::PageProps)] describes named Rust structs and unit enums using
FMIR's actual props schema. props::BoundProps::<P>::bind(module, route) checks
that contract by field name once and retains the immutable module. Its encode
method checks each request's values and emits only declared fields. Extra Rust
fields warn and never reach the page; every declared TypeScript field must exist
in Rust, including optional fields. Errors include route, property path and both
source declarations where available.
The derive reference documents supported types,
renames, optional direction, integer bounds and signed epoch-millisecond dates.
props::encode_props is a one-shot convenience that also performs the comparison;
servers should retain a binding and rebind only when the module changes.
When You Need This
- Building your own server (not Axum) — parse IR and generate HTML yourself
- Build tools — inspect, transform, or generate IR files
If you're using Axum, use forma-server instead — it wraps forma-ir with page rendering, asset serving, and CSP headers.
Security
FMIR is a trusted input. This crate validates structure and bounds so that malformed IR cannot read out of bounds, panic, or abort the process — but it does not sanitise the IR's content. An .ir file chooses what markup your page contains. Only run IR produced by a compiler you trust.
- All dynamic text and attribute values are HTML-escaped; tag and attribute names are validated against
[A-Za-z_:][-A-Za-z0-9_:.]* - URL-bearing attributes are scheme-checked and
on*handlers dropped, matching@getforma/coreso SSR and CSR agree - Recursion depth limited to 64; list nesting limited to 4 levels
- Per-render output cap (default 8 MiB) and LIST iteration budget (default 50,000), both configurable via
WalkLimits— a small module plus a large request array cannot force a multi-gigabyte allocation - Parser rejects declared table counts the section cannot hold, and structurally validates the opcode stream once at load
- Island props JSON is escaped so it cannot terminate or re-open its
<script>element — read it withJSON.parse, never by string-matching #![forbid(unsafe_code)]on the crate root — zerounsafeis compiler-enforced, not asserted- XSS payload verification and malformed-input handling covered by the test suite
Part of the Forma Stack
Frontend (TypeScript)
| Package | Description |
|---|---|
| @getforma/core | Reactive DOM library — signals, h(), islands, SSR hydration |
| @getforma/compiler | FMIR compiler and esbuild SSR plugin |
| @getforma/build | Production pipeline — bundling, hashing, compression, manifest |
Backend (Rust)
| Package | Description |
|---|---|
| forma-ir | This crate — FMIR parser and native HTML walker |
| forma-server | Axum middleware — SSR, asset serving, CSP headers |
Full Framework
| Package | Description |
|---|---|
| @getforma/create-app | npx @getforma/create-app — scaffolds Rust server + TypeScript frontend |
License
MIT