Skip to content

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

bash
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/format

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

PackageRoleMain entry point
@gram-lang/parserTurns .gram source text into an Abstract Syntax Tree (AST)getAST(source)
@gram-lang/kitchenCompiles the AST into a structured, render-ready payload (shopping list, timings, registry)compile(ast, options?)
@gram-lang/analyzerEnriches a compiled recipe with physical properties (mass, yield, nutrition, baker's percentages) using an ingredient databaseanalyze(compiled, database, options?)
@gram-lang/rendererRenders a (compiled or analyzed) recipe to Markdown or HTMLtoMarkdown / toHTML / toPrintHTML
@gram-lang/formatCanonical code formatter for .gram files (13 unified formatting rules)formatGram(source, options?)
@gram-lang/i18nShared unit/time normalization and UI-string dictionaries used internally by the packages abovenormalizeUnit, 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:

typescript
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 getASTcompile → (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

typescript
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 argumentanalyze(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 by getAST): 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 by compile() and carried through analyze()): an array of Warning objects — 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.