@gram-lang/kitchen
Le compilateur. Prend un RecipeAST (issu de @gram-lang/parser) et produit un CompilationResult : une charge JSON propre, structurée, prête au rendu — liste de courses, instructions par section avec minutages, un registre global d'ingrédients/matériel, et les éventuels avertissements structurels. Aucune base de données d'ingrédients n'intervient à cette étape ; c'est le travail de @gram-lang/analyzer.
compile
function compile(ast: RecipeAST, options?: CompilerOptions): CompilationResultimport { getAST } from '@gram-lang/parser';
import { compile } from '@gram-lang/kitchen';
const ast = getAST(source);
const compiled = compile(ast);
// compiled.shopping_list, compiled.sections, compiled.metrics, compiled.warnings, ...Lève une Error simple pour les violations structurelles qui ne peuvent pas être représentées comme un avertissement récupérable — ex : plus d'un ingrédient marqué avec le modificateur pourcentage boulanger (*).
CompilerOptions
interface CompilerOptions {
scaleFactor?: number; // intègre un multiplicateur fixe dans la sortie compilée
}CompilationResult
interface CompilationResult {
title: string | null;
slug: string | null;
meta: Meta; // frontmatter parsé
scaleFactor?: number; // présent une fois une mise à l'échelle appliquée
registry: {
ingredients: Record<string, RegistryEntry>;
cookware: Record<string, { id: string; name: string }>;
};
shopping_list: (ShoppingListItem | CompositeItem | Usage)[];
cookware: Usage[];
sections: ProcessedSection[];
warnings: Warning[];
metrics: {
preparationTime: number; // temps de mise en place estimé (minutes)
cookTime: number; // durée du chemin critique (minutes)
activeTime: number; // somme du temps de travail actif (minutes)
totalTime: number; // preparationTime + cookTime
};
}Voir Formats de Données pour un exemple entièrement annoté de cette forme, et Avertissements pour ce qui peut apparaître dans .warnings.
Mise à l'échelle
Les recettes sont compilées à leurs quantités de base ; la mise à l'échelle est une étape séparée et composable afin que les appelants (ex : un curseur « portions » en temps réel dans une UI) puissent remettre à l'échelle sans re-parser ni recompiler.
function resolveScaleFactor(
compiled: CompilationResult | null,
request: ScaleRequest,
convertUnit?: UnitConverter,
): ScaleResolution
function applyScale(result: CompilationResult, factor: number): CompilationResultScaleRequest est soit un multiplicateur fixe, soit une quantité cible pour un ingrédient précis de la liste de courses, que resolveScaleFactor transforme en un unique factor :
type ScaleRequest =
| { type: "factor"; value: number }
| { type: "target"; id: string; qty: number; unit: string | null };import { resolveScaleFactor, applyScale } from '@gram-lang/kitchen';
// « Je veux 300g de farine au total » -> dérive le multiplicateur depuis la liste de courses compilée
const { factor } = resolveScaleFactor(compiled, { type: 'target', id: 'farine', qty: 300, unit: 'g' });
const scaled = applyScale(compiled, factor);resolveScaleFactor lève une sous-classe typée de ScaleError (chacune avec un .code) lorsque la demande ne peut être satisfaite : InvalidFactorError (levée si le facteur n'est pas strictement positif fini ou si une quantité dépasse Infinity), IngredientNotFoundError, NestedOnlyTargetError (la cible n'existe que dans une sous-recette composite), AlternativeTargetError (la cible est une option d'un groupe @a|@b), FixedIngredientError (marqué @= ou non numérique), RelativeTargetError (quantité dérivée d'un %), AmbiguousMultiUnitError (utilisé avec des unités incompatibles dans la recette), NonNumericTargetError, UnitMismatchError.
applyScale est pure — elle ne mute jamais son entrée, de sorte que le même CompilationResult peut être remis à l'échelle à plusieurs reprises (ex : à chaque déplacement d'un curseur) sans effet cumulatif. Elle satisfait l'invariant de parité : applyScale(compile(ast), factor) ≡ compile(ast, { scaleFactor: factor }).
Liste de courses & minutage (bas niveau)
compile() appelle déjà ces fonctions en interne ; elles sont exportées pour un usage avancé (ex : recalculer une liste de courses à partir d'un ProcessedSection[] construit sur mesure).
function generateShoppingList(
sections: ProcessedSection[],
registry: Registry,
options?: CompilerOptions,
): (ShoppingListItem | CompositeItem | Usage)[]
function calculatePreparationTime(sections: ProcessedSection[], registry: Registry): numberRecipeRegistry
Le registre mutable d'ingrédients/matériel construit pendant la compilation, indexé par slugify(name). Implémente l'interface Registry (ingredients: Map, cookware: Map, warnings: Warning[]).
class RecipeRegistry implements Registry {
registerIngredient(name: string, data?: Partial<Omit<RegistryEntry, "id" | "name">>): string; // retourne l'id
registerCookware(name: string): string; // retourne l'id
getIngredientId(name: string): string;
toPlainObject(): { ingredients: Record<string, RegistryEntry>; cookware: Record<string, {...}> };
}RegistryEntry porte id, name, et optionnellement default_unit, is_composite, parent (pour les enfants de sous-recettes composites), et is_intermediate.
Avertissements
CompilationResult.warnings est un Warning[] — les problèmes structurels détectés pendant la compilation (références indéfinies, conflits de portée, unités de minuteur/température invalides, références circulaires...). Voir la référence des avertissements pour la liste complète des codes, les sévérités, et les exports WarningCode/pushWarning.