@@ -98,19 +98,18 @@ if (!result.ok) {
9898 console .error (' Profile validation failed' , result .schemaErrors );
9999}
100100
101- // Validate with modules
102- const recipeWithModules = {
101+ // Validate with stacks
102+ const recipeWithStacks = {
103103 profile: ' base' ,
104- modules: [ ' nutrition@1 ' , ' times@1 ' ] ,
104+ stacks: { scaling: 1 , storage: 1 } ,
105105 name: ' Test Recipe' ,
106- ingredients: [' 1 cup flour ' ],
106+ ingredients: [{ ingredient: ' flour ' , quantity: 1 , unit: ' cup ' } ],
107107 instructions: [' Mix' ],
108- nutrition: { calories: 100 , protein_g: 5 }, // Module payload required if declared
109- times: { prepMinutes: 10 , cookMinutes: 20 , totalMinutes: 30 }, // v0.3: uses *Minutes fields
108+ // Stack payloads are validated when stacks are declared
110109};
111- const result2 = validateRecipe (recipeWithModules );
112- // Validates using: base + profile + nutrition @1 module + times @1 module
113- // Module contract: if module is declared, payload must exist (and vice versa)
110+ const result2 = validateRecipe (recipeWithStacks );
111+ // Validates using: base profile + scaling @1 stack + storage @1 stack
112+ // Stack contract: if stack is declared, corresponding payload must exist (and vice versa)
114113```
115114
116115### Imperial → metric ingredient conversion
@@ -147,7 +146,7 @@ The converter rounds using “sane” defaults (1 g/ml under 1 kg/1 L, the
147146## Spec compatibility & bundled schemas
148147
149148- Targets Soustack spec ** v0.0.2** (` spec/SOUSTACK_SPEC_VERSION ` , exported as ` SOUSTACK_SPEC_VERSION ` ).
150- - Ships the base schema, profile schemas, and module schemas in ` spec/schemas/recipe/ ` and mirrors them into ` src/schemas/recipe / ` for consumers.
149+ - Ships the base schema, profile schemas, and stack schemas in ` spec/stacks/ ` and mirrors them into ` src/stacks / ` for consumers.
151150- Vendored fixtures live in ` spec/fixtures ` so tests can run offline, and version drift can be checked via ` npm run validate:version ` .
152151
153152### Composed Validation Model
@@ -159,46 +158,51 @@ Soustack v0.0.2 uses a **composed validation model** where recipes are validated
159158 "allOf" : [
160159 { "$ref" : " base.schema.json" },
161160 { "$ref" : " profiles/{profile}.schema.json" },
162- { "$ref" : " modules/{module1}/{version }.schema.json" },
163- { "$ref" : " modules/{module2}/{version }.schema.json" }
161+ { "$ref" : " stacks/{stack1 }.schema.json" },
162+ { "$ref" : " stacks/{stack2 }.schema.json" }
164163 ]
165164}
166165```
167166
168167The validator:
169- - ** Base schema** : Defines the core recipe structure (` @type ` , ` name ` , ` ingredients ` , ` instructions ` , ` profile ` , ` modules ` )
168+ - ** Base schema** : Defines the core recipe structure (` @type ` , ` name ` , ` ingredients ` , ` instructions ` , ` profile ` , ` stacks ` )
170169- ** Profile overlay** : Adds profile-specific requirements (e.g., ` base ` or ` lite ` )
171- - ** Module overlays** : Each declared module adds its own validation rules
170+ - ** Stack overlays** : Each declared stack adds its own validation rules
172171
173172** Defaults:**
174173- If ` profile ` is missing, it defaults to the schema bundle's configured default
175- - If ` modules ` is missing, it defaults to ` [] `
176-
177- ** Module Contract:** Modules enforce a symmetric contract:
178- - If a module is declared in ` modules ` , the corresponding payload must exist
179- - If a payload exists (e.g., ` nutrition ` , ` times ` ), the module must be declared
180- - The validator automatically infers modules from payloads and enforces this contract
181-
182- ** Caching:** Validators are cached by ` ${profile}::${sortedModules.join(",")} ` for performance.
183-
184- ### Module Resolution
185-
186- Modules are resolved to schema references using the pattern:
187- - Module identifier format: ` <name>@<version> ` (e.g., ` nutrition@1 ` , ` schedule@1 ` )
188- - Schema reference: ` https://soustack.org/schemas/recipe/modules/<name>/<version>.schema.json `
189-
190- The module registry (` schemas/registry/modules.json ` ) defines which modules are available and their properties, including:
191- - ` schemaOrgMappable ` : Whether the module can be converted to Schema.org format
192- - ` minProfile ` : Minimum profile required to use the module
193- - ` allowedOnLite ` : Whether the module can be used with the lite profile
194-
195- ** Available Modules (v0.0.2):**
196- - ` attribution@1 ` : Source attribution (url, author, datePublished)
197- - ` taxonomy@1 ` : Classification (keywords, category, cuisine)
198- - ` media@1 ` : Images and videos (images, videos arrays)
199- - ` times@1 ` : Timing information (prepMinutes, cookMinutes, totalMinutes)
200- - ` nutrition@1 ` : Nutritional data (calories, protein_g as numbers)
201- - ` schedule@1 ` : Task scheduling (requires timed profile, includes instruction dependencies)
174+ - If ` stacks ` is missing, it defaults to ` {} `
175+
176+ ** Stack Contract:** Stacks enforce a symmetric contract:
177+ - If a stack is declared in ` stacks ` , the corresponding payload must exist (for non-structural stacks)
178+ - If a payload exists (e.g., ` storage ` , ` equipment ` ), the stack must be declared
179+ - The validator automatically infers stacks from payloads and enforces this contract
180+
181+ ** Caching:** Validators are cached by ` ${profile}::${sortedStackNames.join(",")} ` for performance.
182+
183+ ### Stack Resolution
184+
185+ Stacks are resolved to schema references using the pattern:
186+ - Stack declaration format: ` { "stackName": versionNumber } ` (e.g., ` { "scaling": 1, "storage": 1 } ` )
187+ - Schema reference: ` https://spec.soustack.org/stacks/{stackName}.schema.json `
188+
189+ The stack registry (` stacks/registry.json ` ) defines which stacks are available and their properties, including:
190+ - Stack dependencies (e.g., ` scaling ` requires ` quantified ` )
191+ - Profile requirements (some stacks require specific profiles)
192+
193+ ** Available Stacks (v0.0.2):**
194+ - ` quantified ` : Quantified ingredients with units
195+ - ` scaling ` : Scaling rules and modes
196+ - ` structured ` : Structured instructions with steps
197+ - ` timed ` : Timing information for instructions
198+ - ` illustrated ` : Images and videos
199+ - ` equipment ` : Required tools and equipment
200+ - ` storage ` : Storage instructions
201+ - ` prep ` : Prep guidance and mise en place
202+ - ` dietary ` : Dietary information
203+ - ` substitutions ` : Ingredient substitutions
204+ - ` techniques ` : Cooking techniques
205+ - ` compute ` : Computational recipe features
202206
203207## Programmatic Usage
204208
@@ -271,7 +275,7 @@ async function convert(url: string) {
271275
272276Use the helpers to move between Schema.org JSON-LD and Soustack's structured recipe format. The conversion automatically handles image normalization, supporting multiple image formats from Schema.org.
273277
274- ** BREAKING CHANGE in v0.0.2:** ` toSchemaOrg() ` now targets the ** lite profile** and only includes modules that are marked as ` schemaOrgMappable ` in the modules registry . Non-mappable modules (e.g., ` nutrition@1 ` , ` schedule@1 ` ) are excluded from the conversion.
278+ ** BREAKING CHANGE in v0.0.2:** ` toSchemaOrg() ` now targets the ** lite profile** and only includes stacks that can be mapped to Schema.org format . Non-mappable stacks are excluded from the conversion.
275279
276280``` ts
277281import { fromSchemaOrg , toSchemaOrg , normalizeImage } from ' soustack' ;
@@ -392,11 +396,11 @@ npx soustack convert --from soustack --to schemaorg recipe.soustack.json -o reci
392396# Scrape URLs (canonical workflow)
393397npx soustack scrape < url> -o recipe.soustack.json
394398
395- # Import from URL (alias for scrape, for compatibility)
399+ # Import from URL (optional alias for scrape, for compatibility)
396400npx soustack import --url " https://example.com/recipe" -o recipe.soustack.json
397401
398- # Bulk pipeline (delegates to @soustack/ingest)
399- npx soustack ingest < source > [ --out < path > ] # requires @soustack/ingest
402+ # Bulk ingest pipeline (delegates to @soustack/ingest)
403+ npx soustack ingest < input > --out < dir > # requires @soustack/ingest
400404
401405# Scale recipes
402406npx soustack scale recipe.soustack.json 2
0 commit comments