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).
Installation
npm install @gram-lang/parser @gram-lang/kitchen @gram-lang/analyzer @gram-lang/renderer @gram-lang/format
# or
bun add @gram-lang/parser @gram-lang/kitchen @gram-lang/analyzer @gram-lang/renderer @gram-lang/formatAll 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
| Package | Role | Main entry point |
|---|---|---|
@gram-lang/parser | Turns .gram source text into an Abstract Syntax Tree (AST) | getAST(source) |
@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)
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:
import { runPipeline, GramCLIError } from '@gram-lang/cli';
const { content, compiled, analyzed } = await runPipeline('recipe.gram', {
db: myDatabase, // optional — omit to skip the analyzer stage
scaleFactor: 2, // optional
bakersReference: 'flour', // optional
});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
import { getAST, GramParseError } from '@gram-lang/parser';
import { compile } from '@gram-lang/kitchen';
import { analyze, validateIngredientDatabase } from '@gram-lang/analyzer';
import { toHTML } from '@gram-lang/renderer';
function renderRecipe(source: string, rawIngredientDb: unknown) {
// 1. Parse .gram text into an AST
let ast;
try {
ast = getAST(source);
} catch (err) {
if (err instanceof GramParseError) {
console.error(`Syntax error at offset ${err.offset}: expected ${err.expected}`);
}
throw err;
}
// 2. Compile the AST into a structured recipe (shopping list, timings, registry)
const compiled = compile(ast);
// 3. Load and validate the ingredient database, then enrich with physical/nutritional data
const { data: database, rejected } = validateIngredientDatabase(rawIngredientDb);
if (rejected.length > 0) {
console.warn('Ignoring invalid ingredient entries:', rejected);
}
const { result: analyzed, missingIngredients } = analyze(compiled, database);
// 4. Render to HTML
return toHTML(analyzed);
}Note: the
analyze()call above takes the ingredient database as its second positional argument —analyze(compiled, database, options?)— not as a field on an options object.
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.
See also: Data Formats for the shape of the JSON objects flowing between these stages, and How to Build a Custom UI for a walkthrough of consuming the final JSON in a frontend framework.