API reference
Gram is not just a file format — it’s a pipeline of small, composable libraries. Each package does one job and hands a plain JSON object to the next. You can use the whole pipeline, or pick out just the piece you need (e.g. only the parser, to build a linter).
All packages are ESM-only, side-effect free, and run anywhere JavaScript runs — Node.js, Deno, Bun, edge runtimes, or directly in the browser (see the Playground for a fully client-side example).
Package matrix
Section titled “Package matrix”| Package | Role | Main entry point |
|---|---|---|
@gram-lang/parser | Turns .gram source text into an Abstract Syntax Tree (AST) | getAST(source) |
@gram-lang/modules | Resolves @use imports, tracks dependencies, scales bases by yield, and composes a unified AST | loadModuleGraph(entry, host), composeRecipe(graph, opts) |
@gram-lang/kitchen | Compiles the AST into a structured, render-ready payload (shopping list, timings, registry) | compile(ast, options?) |
@gram-lang/analyzer | Enriches a compiled recipe with physical properties (mass, yield, nutrition, baker’s percentages) using an ingredient database | analyze(compiled, database, options?) |
@gram-lang/renderer | Renders a (compiled or analyzed) recipe to Markdown or HTML | toMarkdown / toHTML / toPrintHTML |
@gram-lang/format | Canonical code formatter for .gram files (13 unified formatting rules) | formatGram(source, options?) |
@gram-lang/i18n | Shared unit/time normalization and UI-string dictionaries used internally by the packages above | normalizeUnit, getDictionary |
Analysis is optional: compile() alone already gives you a complete, renderable recipe (with default un-standardized quantities). You only need @gram-lang/analyzer when you want mass conversion, nutrition estimates, or baker’s percentages, which requires an ingredient database.
Optional: Node-only shortcut (@gram-lang/cli)
Section titled “Optional: Node-only shortcut (@gram-lang/cli)”If you’re running in Node.js and don’t need per-stage control, @gram-lang/cli — yes, the same package that ships the gram binary — also exports a small library surface:
runPipeline(filePath, options?) reads the file, then runs getAST → compile → (only if db is passed) analyze for you, returning { content, compiled, analyzed }. It throws GramCLIError (an Error subclass carrying an .exitCode from the exported ExitCode enum) instead of a bare error — handy if you want to map failures to your own exit codes. It’s Node-only (reads from disk with node:fs); for the browser or edge runtimes, compose the individual packages as shown below.
The full pipeline
Section titled “The full pipeline”Modular recipes with @gram-lang/modules
Section titled “Modular recipes with @gram-lang/modules”When working with recipes that contain @use import directives across multiple files, insert @gram-lang/modules between the parser and the compiler:
Handling parse errors and warnings
Section titled “Handling parse errors and warnings”getAST() is the only function in the pipeline that throws: a syntax error means there’s no AST to work with. Every later stage instead collects warnings — a malformed recipe still compiles, so you can still render it and show the user what’s wrong.
GramParseError(thrown bygetAST): has.message(ohm-js’s human-readable prose, source excerpt included),.offset(character offset into the input), and.expected(what the parser expected there).CompilationResult.warnings/AnalyzedCompilationResult.warnings(returned bycompile()and carried throughanalyze()): an array ofWarningobjects — never bare strings. See the Warnings reference for the full list of codes and how to treat them as errors with--strict-style logic.