soustack 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/index.js +4412 -1275
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/index.d.mts +106 -80
  5. package/dist/index.d.ts +106 -80
  6. package/dist/index.js +4527 -1360
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +4527 -1360
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/scrape/index.d.mts +86 -74
  11. package/dist/scrape/index.d.ts +86 -74
  12. package/dist/scrape/index.js +91 -64
  13. package/dist/scrape/index.js.map +1 -1
  14. package/dist/scrape/index.mjs +91 -64
  15. package/dist/scrape/index.mjs.map +1 -1
  16. package/package.json +15 -6
  17. package/spec/.sync-meta.json +149 -0
  18. package/spec/SOUSTACK_SPEC_VERSION +1 -0
  19. package/spec/defs/common.schema.json +46 -0
  20. package/spec/defs/duration.schema.json +33 -0
  21. package/spec/defs/entities.schema.json +111 -0
  22. package/spec/defs/ingredientQuantified.schema.json +9 -0
  23. package/spec/defs/quantity.schema.json +16 -0
  24. package/spec/defs/scalingRule.schema.json +127 -0
  25. package/spec/defs/temperature.schema.json +63 -0
  26. package/spec/fixtures/content/illustrated-step.valid.json +24 -0
  27. package/spec/fixtures/invalid/equipment-unknown-reference.invalid.json +38 -0
  28. package/spec/fixtures/invalid/mise-en-place-unknown-equipment.invalid.json +37 -0
  29. package/spec/fixtures/invalid/mise-en-place-unknown-input.invalid.json +41 -0
  30. package/spec/fixtures/invalid/storage-leftovers-missing-method.invalid.json +31 -0
  31. package/spec/fixtures/invalid/storage-leftovers-wrong-type.invalid.json +23 -0
  32. package/spec/fixtures/level/base-full.valid.json +162 -0
  33. package/spec/fixtures/level/base-missing-yield.invalid.json +12 -0
  34. package/spec/fixtures/level/lite-min.valid.json +14 -0
  35. package/spec/fixtures/profile/profile-base.valid.json +20 -0
  36. package/spec/fixtures/profile/profile-equipped.valid.json +28 -0
  37. package/spec/fixtures/profile/profile-illustrated.valid.json +28 -0
  38. package/spec/fixtures/profile/profile-lite.valid.json +13 -0
  39. package/spec/fixtures/profile/profile-prepped.valid.json +31 -0
  40. package/spec/fixtures/profile/profile-scalable-missing-scaling.invalid.json +29 -0
  41. package/spec/fixtures/profile/profile-scalable.valid.json +49 -0
  42. package/spec/fixtures/profile/profile-timed-missing-structured.invalid.json +30 -0
  43. package/spec/fixtures/scaling/bakers-percent-missing-ref.invalid.json +41 -0
  44. package/spec/fixtures/scaling/bakers-percent.valid.json +51 -0
  45. package/spec/fixtures/scaling/discrete-range.invalid.json +36 -0
  46. package/spec/fixtures/scaling/missing-quantified.invalid.json +40 -0
  47. package/spec/fixtures/scaling/reject-bakersPercentage.invalid.json +50 -0
  48. package/spec/fixtures/stacks/compute-missing-timed.invalid.json +32 -0
  49. package/spec/fixtures/stacks/dietary-no-signal.invalid.json +16 -0
  50. package/spec/fixtures/stacks/illustrated-empty.invalid.json +13 -0
  51. package/spec/fixtures/stacks/quantified-string.invalid.json +22 -0
  52. package/spec/fixtures/stacks/referenced-missing-input.invalid.json +32 -0
  53. package/spec/fixtures/stacks/storage-min.valid.json +20 -0
  54. package/spec/fixtures/stacks/storage-no-duration.invalid.json +16 -0
  55. package/spec/fixtures/stacks/timed-implies-structured.valid.json +50 -0
  56. package/spec/fixtures/stacks/timed-range.invalid.json +33 -0
  57. package/spec/fixtures/valid/equipment-scaling-rules.valid.json +76 -0
  58. package/spec/fixtures/valid/equipment-strings.valid.json +31 -0
  59. package/spec/fixtures/valid/equipment-structured-uses.valid.json +47 -0
  60. package/spec/fixtures/valid/mise-en-place-basic.valid.json +31 -0
  61. package/spec/fixtures/valid/mise-en-place-referenced-equipment.valid.json +51 -0
  62. package/spec/fixtures/valid/prep-ingredient-strings.valid.json +48 -0
  63. package/spec/fixtures/valid/prep-ingredient-structured.valid.json +45 -0
  64. package/spec/fixtures/valid/profile-equipped.valid.json +29 -0
  65. package/spec/fixtures/valid/profile-prepped.valid.json +32 -0
  66. package/spec/fixtures/valid/quantified-nested-ingredient-sections.valid.json +61 -0
  67. package/spec/fixtures/valid/referenced-scaling.valid.json +67 -0
  68. package/spec/fixtures/valid/storage-leftovers-simple.valid.json +27 -0
  69. package/spec/fixtures/valid/storage-leftovers-structured.valid.json +43 -0
  70. package/spec/fixtures/valid/structured-nested-step-sections.valid.json +84 -0
  71. package/spec/schemas/stacks-registry.schema.json +108 -0
  72. package/spec/soustack.schema.json +2379 -0
  73. package/spec/stacks/compute.schema.json +7 -0
  74. package/spec/stacks/compute@1.md +22 -0
  75. package/spec/stacks/dietary.schema.json +45 -0
  76. package/spec/stacks/dietary@1.md +24 -0
  77. package/spec/stacks/equipment.schema.json +98 -0
  78. package/spec/stacks/equipment@1.md +244 -0
  79. package/spec/stacks/illustrated.schema.json +54 -0
  80. package/spec/stacks/illustrated@1.md +24 -0
  81. package/spec/stacks/prep.schema.json +76 -0
  82. package/spec/stacks/prep@1.md +276 -0
  83. package/spec/stacks/quantified.schema.json +74 -0
  84. package/spec/stacks/quantified@1.md +24 -0
  85. package/spec/stacks/referenced.schema.json +96 -0
  86. package/spec/stacks/referenced@1.md +23 -0
  87. package/spec/stacks/registry.json +112 -0
  88. package/spec/stacks/scaling.schema.json +99 -0
  89. package/spec/stacks/scaling@1.md +238 -0
  90. package/spec/stacks/storage.schema.json +132 -0
  91. package/spec/stacks/storage@1.md +256 -0
  92. package/spec/stacks/structured.schema.json +48 -0
  93. package/spec/stacks/structured@1.md +24 -0
  94. package/spec/stacks/substitutions.schema.json +43 -0
  95. package/spec/stacks/substitutions@1.md +24 -0
  96. package/spec/stacks/techniques.schema.json +28 -0
  97. package/spec/stacks/techniques@1.md +23 -0
  98. package/spec/stacks/timed.schema.json +60 -0
  99. package/spec/stacks/timed@1.md +23 -0
  100. package/src/defs/common.schema.json +46 -0
  101. package/src/defs/duration.schema.json +33 -0
  102. package/src/defs/entities.schema.json +111 -0
  103. package/src/defs/ingredientQuantified.schema.json +9 -0
  104. package/src/defs/quantity.schema.json +16 -0
  105. package/src/defs/scalingRule.schema.json +127 -0
  106. package/src/defs/temperature.schema.json +63 -0
  107. package/src/profiles/base.schema.json +2 -2
  108. package/src/profiles/equipped.schema.json +10 -0
  109. package/src/profiles/illustrated.schema.json +4 -4
  110. package/src/profiles/lite.schema.json +10 -0
  111. package/src/profiles/prepped.schema.json +10 -0
  112. package/src/profiles/scalable.schema.json +6 -6
  113. package/src/profiles/timed.schema.json +10 -0
  114. package/src/schema.json +2271 -248
  115. package/src/schemas/stacks-registry.schema.json +108 -0
  116. package/src/soustack.schema.json +2271 -248
  117. package/src/stacks/compute.schema.json +7 -0
  118. package/src/stacks/compute@1.md +22 -0
  119. package/src/stacks/dietary.schema.json +45 -0
  120. package/src/stacks/dietary@1.md +24 -0
  121. package/src/stacks/equipment.schema.json +98 -0
  122. package/src/stacks/equipment@1.md +244 -0
  123. package/src/stacks/illustrated.schema.json +54 -0
  124. package/src/stacks/illustrated@1.md +24 -0
  125. package/src/stacks/prep.schema.json +76 -0
  126. package/src/stacks/prep@1.md +276 -0
  127. package/src/stacks/quantified.schema.json +74 -0
  128. package/src/stacks/quantified@1.md +24 -0
  129. package/src/stacks/referenced.schema.json +96 -0
  130. package/src/stacks/referenced@1.md +23 -0
  131. package/src/stacks/registry.json +112 -0
  132. package/src/stacks/scaling.schema.json +99 -0
  133. package/src/stacks/scaling@1.md +238 -0
  134. package/src/stacks/storage.schema.json +132 -0
  135. package/src/stacks/storage@1.md +256 -0
  136. package/src/stacks/structured.schema.json +48 -0
  137. package/src/stacks/structured@1.md +24 -0
  138. package/src/stacks/substitutions.schema.json +43 -0
  139. package/src/stacks/substitutions@1.md +24 -0
  140. package/src/stacks/techniques.schema.json +28 -0
  141. package/src/stacks/techniques@1.md +23 -0
  142. package/src/stacks/timed.schema.json +60 -0
  143. package/src/stacks/timed@1.md +23 -0
  144. package/src/profiles/cookable.schema.json +0 -18
  145. package/src/profiles/quantified.schema.json +0 -43
  146. package/src/profiles/schedulable.schema.json +0 -43
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Soustack Recipe Schema v0.3.0
2
+ * Soustack Recipe Schema v0.0.2
3
3
  * A portable, scalable, interoperable recipe format.
4
4
  */
5
5
  interface SoustackRecipe {
@@ -7,20 +7,10 @@ interface SoustackRecipe {
7
7
  '@type'?: 'Recipe';
8
8
  /** Optional $schema pointer for profile-aware validation */
9
9
  $schema?: string;
10
- /** Optional declared validation profile */
11
- profile?: string;
12
- /** Recipe level: "lite" or "base" */
13
- level?: "lite" | "base";
10
+ /** Optional declared validation profile (vNext only) */
11
+ profile?: "base" | "equipped" | "illustrated" | "lite" | "prepped" | "scalable" | "timed";
14
12
  /** Stack declarations as a map: Record<stackName, versionNumber> */
15
13
  stacks?: Record<string, number>;
16
- /** Attribution stack payload */
17
- attribution?: AttributionModule;
18
- /** Taxonomy stack payload */
19
- taxonomy?: TaxonomyModule;
20
- /** Media stack payload */
21
- media?: MediaModule;
22
- /** Times stack payload */
23
- times?: TimesModule;
24
14
  /** Unique identifier (slug or UUID) */
25
15
  id?: string;
26
16
  /** Optional display title */
@@ -37,6 +27,10 @@ interface SoustackRecipe {
37
27
  /** Additional tags for filtering */
38
28
  tags?: string[];
39
29
  /** URL(s) to recipe image(s) */
30
+ images?: string[];
31
+ /** URL(s) to recipe video(s) */
32
+ videos?: string[];
33
+ /** Legacy image field preserved for compatibility */
40
34
  image?: string | string[];
41
35
  /** ISO 8601 date string */
42
36
  dateAdded?: string;
@@ -74,19 +68,18 @@ interface ParsedYield {
74
68
  description?: string;
75
69
  }
76
70
  /**
77
- * Time can be structured (machine-readable) or simple (strings).
78
- * Structured time takes precedence if both exist.
71
+ * Time uses DurationMinutes format (vNext).
72
+ * Required total field with minutes.
79
73
  */
80
- type Time = StructuredTime | SimpleTime;
81
- interface StructuredTime {
82
- prep?: number;
83
- active?: number;
84
- passive?: number;
85
- total?: number;
86
- }
87
- interface SimpleTime {
88
- prepTime?: string;
89
- cookTime?: string;
74
+ interface Time {
75
+ total: DurationMinutes;
76
+ metadata?: Record<string, unknown>;
77
+ [k: `x-${string}`]: unknown;
78
+ }
79
+ interface DurationMinutes {
80
+ minutes: number;
81
+ metadata?: Record<string, unknown>;
82
+ [k: `x-${string}`]: unknown;
90
83
  }
91
84
  interface Equipment {
92
85
  id?: string;
@@ -96,99 +89,144 @@ interface Equipment {
96
89
  capacity?: Quantity$1;
97
90
  scalingLimit?: number;
98
91
  alternatives?: string[];
92
+ count?: number;
93
+ countScaling?: EquipmentCountScaling;
94
+ upgrades?: EquipmentUpgradeRule[];
99
95
  }
100
96
  interface Quantity$1 {
101
97
  amount: number;
102
98
  /** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
103
99
  unit: string | null;
104
100
  }
101
+ type EquipmentCountScaling = 'fixed' | 'linear' | EquipmentThresholdScaling;
102
+ interface EquipmentThresholdScaling {
103
+ mode: 'threshold';
104
+ steps: EquipmentThresholdStep[];
105
+ }
106
+ interface EquipmentThresholdStep {
107
+ maxFactor: number;
108
+ count: number;
109
+ }
110
+ interface EquipmentUpgradeRule {
111
+ minFactor: number;
112
+ use: string;
113
+ }
114
+ interface EquipmentUpgradeRecommendation {
115
+ fromId: string;
116
+ use: string;
117
+ minFactor: number;
118
+ }
119
+ interface ScalingMetadata {
120
+ multiplier: number;
121
+ equipment?: {
122
+ upgrades: EquipmentUpgradeRecommendation[];
123
+ };
124
+ }
125
+ type ScaledRecipe = Recipe & {
126
+ scaling?: ScalingMetadata;
127
+ };
105
128
  type IngredientItem = string | Ingredient$1 | IngredientSubsection;
106
129
  interface IngredientSubsection {
107
- subsection: string;
108
- items: (string | Ingredient$1)[];
130
+ section: string;
131
+ ingredients: IngredientItem[];
109
132
  }
110
133
  interface Ingredient$1 {
111
134
  id?: string;
112
- /** Full human-readable text (e.g. "2 cups flour") */
113
- item: string;
135
+ /** Ingredient name (required in vNext) */
136
+ name: string;
114
137
  quantity?: Quantity$1;
115
- name?: string;
116
- aisle?: string;
117
- /** Required prep state (e.g. "diced") */
118
- prep?: string;
119
- prepAction?: string;
120
- prepTime?: number;
138
+ /** Required prep state (e.g. "diced") or array of prep items */
139
+ prep?: string | string[];
121
140
  /** ID of equipment where this ingredient goes */
122
141
  destination?: string;
123
142
  scaling?: Scaling;
124
- critical?: boolean;
125
143
  optional?: boolean;
126
144
  notes?: string;
145
+ temperature?: Temperature;
146
+ metadata?: Record<string, unknown>;
147
+ [k: `x-${string}`]: unknown;
127
148
  }
128
149
  interface ParsedIngredient {
129
- item: string;
150
+ name: string;
130
151
  quantity?: {
131
152
  amount: number | null;
132
153
  unit: string | null;
133
154
  };
134
- name?: string;
135
155
  prep?: string;
136
156
  optional?: boolean;
137
157
  notes?: string;
138
158
  scaling?: Scaling;
139
159
  }
140
160
  /**
141
- * Intelligent Scaling Logic
161
+ * Intelligent Scaling Logic (vNext format)
142
162
  * Defines how an ingredient behaves when the recipe yield changes.
143
163
  */
144
- type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingBakersPercentage;
164
+ type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingToTaste | ScalingBakersPercentage;
145
165
  interface ScalingBase {
146
166
  min?: number;
147
167
  max?: number;
148
168
  }
149
169
  interface ScalingLinear extends ScalingBase {
150
- type: "linear";
170
+ mode: "linear";
151
171
  }
152
172
  interface ScalingDiscrete extends ScalingBase {
153
- type: "discrete";
154
- roundTo?: number;
173
+ mode: "discrete";
174
+ step?: number;
175
+ rounding?: "nearest" | "ceil" | "floor";
155
176
  }
156
177
  interface ScalingProportional extends ScalingBase {
157
- type: "proportional";
178
+ mode: "proportional";
158
179
  factor?: number;
159
180
  }
160
181
  interface ScalingFixed extends ScalingBase {
161
- type: "fixed";
182
+ mode: "fixed";
162
183
  }
163
- interface ScalingBakersPercentage extends ScalingBase {
164
- type: 'bakers_percentage';
184
+ interface ScalingToTaste {
185
+ mode: "toTaste";
186
+ }
187
+ interface ScalingBakersPercentage {
188
+ mode: "bakersPercent";
189
+ /** The percentage relative to the reference (e.g. 2 for 2%) */
190
+ percent: number;
165
191
  /** The ID of the flour/base ingredient this is relative to */
166
- referenceId: string;
167
- /** The percentage relative to the reference (e.g. 0.02 for 2%) */
168
- factor?: number;
192
+ of: string;
169
193
  }
170
194
  type InstructionItem = string | Instruction | InstructionSubsection;
171
195
  interface InstructionSubsection {
172
- subsection: string;
173
- items: (string | Instruction)[];
196
+ section: string;
197
+ steps: InstructionItem[];
174
198
  }
175
199
  interface SoustackInstruction {
176
200
  id?: string;
177
201
  text: string;
178
- destination?: string;
179
202
  /** IDs of steps that must complete before this one starts */
180
203
  dependsOn?: string[];
181
204
  /** IDs of ingredients used in this step */
182
205
  inputs?: string[];
206
+ /** IDs of techniques used in this step */
207
+ techniqueIds?: string[];
208
+ /** IDs of equipment used in this step */
209
+ usesEquipment?: string[];
183
210
  timing?: StepTiming;
184
- /** Optional image URL for this instruction */
185
- image?: string;
211
+ temperature?: Temperature;
212
+ images?: string[];
213
+ videos?: string[];
214
+ metadata?: Record<string, unknown>;
215
+ [k: `x-${string}`]: unknown;
186
216
  }
187
217
  type Instruction = SoustackInstruction;
188
218
  interface StepTiming {
189
- duration: number | string;
190
- type: "active" | "passive";
191
- scaling?: "linear" | "fixed" | "sqrt";
219
+ activity: "active" | "passive";
220
+ duration?: DurationMinutes | DurationRange;
221
+ completionCue?: string;
222
+ metadata?: Record<string, unknown>;
223
+ [k: `x-${string}`]: unknown;
224
+ }
225
+ interface DurationRange {
226
+ minMinutes: number;
227
+ maxMinutes: number;
228
+ metadata?: Record<string, unknown>;
229
+ [k: `x-${string}`]: unknown;
192
230
  }
193
231
  interface Storage {
194
232
  roomTemp?: StorageMethod;
@@ -227,24 +265,11 @@ interface NutritionFacts {
227
265
  calories?: number;
228
266
  protein_g?: number;
229
267
  }
230
- interface AttributionModule {
231
- url?: string;
232
- author?: string;
233
- datePublished?: string;
234
- }
235
- interface TaxonomyModule {
236
- keywords?: string[];
237
- category?: string;
238
- cuisine?: string;
239
- }
240
- interface MediaModule {
241
- images?: string[];
242
- videos?: string[];
243
- }
244
- interface TimesModule {
245
- prepMinutes?: number;
246
- cookMinutes?: number;
247
- totalMinutes?: number;
268
+ interface Temperature {
269
+ value: number;
270
+ unit: "celsius" | "fahrenheit";
271
+ metadata?: Record<string, unknown>;
272
+ [k: `x-${string}`]: unknown;
248
273
  }
249
274
 
250
275
  interface ScaleRecipeOptions {
@@ -264,7 +289,7 @@ interface ConformanceIssue {
264
289
  severity: ConformanceSeverity;
265
290
  }
266
291
 
267
- type ProfileName = "base" | "equipped" | "illustrated" | "lite" | "prepped" | "scalable" | "timed" | "minimal" | "core";
292
+ type ProfileName = "base" | "equipped" | "illustrated" | "lite" | "prepped" | "scalable" | "timed";
268
293
  interface NormalizedError {
269
294
  path: string;
270
295
  message: string;
@@ -350,6 +375,7 @@ interface SchemaOrgRecipe$1 {
350
375
  nutrition?: NutritionInformation;
351
376
  video?: SchemaOrgImage;
352
377
  '@graph'?: unknown;
378
+ $schema?: string;
353
379
  }
354
380
  type SchemaOrgIngredientList = string | string[];
355
381
  type SchemaOrgInstructionList = string | HowToStep$1 | HowToSection | Array<string | HowToStep$1 | HowToSection>;
@@ -397,7 +423,7 @@ interface NutritionInformation {
397
423
  /**
398
424
  * Convert a Soustack recipe to Schema.org JSON-LD format.
399
425
  *
400
- * BREAKING CHANGE in v0.3.0: This function now targets the "minimal" profile
426
+ * BREAKING CHANGE in v0.0.2: This function now targets the "minimal" profile
401
427
  * and only includes stacks that are schemaOrgMappable (as defined in the
402
428
  * stacks registry). Non-mappable stacks (e.g., nutrition@1, schedule@1)
403
429
  * are excluded from the conversion.
@@ -429,7 +455,7 @@ interface SchemaOrgRecipe {
429
455
 
430
456
  declare function extractSchemaOrgRecipeFromHTML(html: string): SchemaOrgRecipe | null;
431
457
 
432
- declare const SOUSTACK_SPEC_VERSION = "0.3.0";
458
+ declare const SOUSTACK_SPEC_VERSION = "0.0.2";
433
459
 
434
460
  type ConvertTarget = 'metric';
435
461
  type ConvertMode = 'volume' | 'mass';
@@ -494,4 +520,4 @@ interface MiseEnPlacePlan {
494
520
  }
495
521
  declare function miseEnPlace(ingredients: Ingredient[]): MiseEnPlacePlan;
496
522
 
497
- export { type Alternative, type AttributionModule, type ConvertMode, type ConvertTarget, type ConvertedLineItem, type Equipment, type FrozenStorageMethod, type Ingredient$1 as Ingredient, type IngredientItem, type IngredientSubsection, type Instruction, type InstructionItem, type InstructionSubsection, type LineItem, type MakeAheadComponent, type MediaModule, type Ingredient as MiseEnPlaceIngredient, type MiseEnPlacePlan, type Quantity as MiseEnPlaceQuantity, type MiseEnPlaceTask, MissingEquivalencyError, type NormalizationResult, type NutritionFacts, type ParsedIngredient, type ParsedYield, type Quantity$1 as Quantity, type Recipe, type RoundMode, SOUSTACK_SPEC_VERSION, type Scaling, type ScalingBakersPercentage, type ScalingBase, type ScalingDiscrete, type ScalingFixed, type ScalingLinear, type ScalingProportional, type SimpleTime, type Source, type SoustackInstruction, type SoustackRecipe, type StepTiming, type Storage, type StorageMethod, type StructuredTime, type Substitution, type TaxonomyModule, type Time, type TimesModule, UnknownUnitError, UnsupportedConversionError, type ValidateMode, type ValidateResult, type Yield, convertLineItemToMetric, detectProfiles, extractSchemaOrgRecipeFromHTML, fromSchemaOrg, miseEnPlace, normalizeRecipe, scaleRecipe, toSchemaOrg, validateRecipe };
523
+ export { type Alternative, type ConvertMode, type ConvertTarget, type ConvertedLineItem, type DurationMinutes, type DurationRange, type Equipment, type EquipmentCountScaling, type EquipmentThresholdScaling, type EquipmentThresholdStep, type EquipmentUpgradeRecommendation, type EquipmentUpgradeRule, type FrozenStorageMethod, type Ingredient$1 as Ingredient, type IngredientItem, type IngredientSubsection, type Instruction, type InstructionItem, type InstructionSubsection, type LineItem, type MakeAheadComponent, type Ingredient as MiseEnPlaceIngredient, type MiseEnPlacePlan, type Quantity as MiseEnPlaceQuantity, type MiseEnPlaceTask, MissingEquivalencyError, type NormalizationResult, type NutritionFacts, type ParsedIngredient, type ParsedYield, type Quantity$1 as Quantity, type Recipe, type RoundMode, SOUSTACK_SPEC_VERSION, type ScaledRecipe, type Scaling, type ScalingBakersPercentage, type ScalingBase, type ScalingDiscrete, type ScalingFixed, type ScalingLinear, type ScalingMetadata, type ScalingProportional, type ScalingToTaste, type Source, type SoustackInstruction, type SoustackRecipe, type StepTiming, type Storage, type StorageMethod, type Substitution, type Temperature, type Time, UnknownUnitError, UnsupportedConversionError, type ValidateMode, type ValidateResult, type Yield, convertLineItemToMetric, detectProfiles, extractSchemaOrgRecipeFromHTML, fromSchemaOrg, miseEnPlace, normalizeRecipe, scaleRecipe, toSchemaOrg, validateRecipe };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Soustack Recipe Schema v0.3.0
2
+ * Soustack Recipe Schema v0.0.2
3
3
  * A portable, scalable, interoperable recipe format.
4
4
  */
5
5
  interface SoustackRecipe {
@@ -7,20 +7,10 @@ interface SoustackRecipe {
7
7
  '@type'?: 'Recipe';
8
8
  /** Optional $schema pointer for profile-aware validation */
9
9
  $schema?: string;
10
- /** Optional declared validation profile */
11
- profile?: string;
12
- /** Recipe level: "lite" or "base" */
13
- level?: "lite" | "base";
10
+ /** Optional declared validation profile (vNext only) */
11
+ profile?: "base" | "equipped" | "illustrated" | "lite" | "prepped" | "scalable" | "timed";
14
12
  /** Stack declarations as a map: Record<stackName, versionNumber> */
15
13
  stacks?: Record<string, number>;
16
- /** Attribution stack payload */
17
- attribution?: AttributionModule;
18
- /** Taxonomy stack payload */
19
- taxonomy?: TaxonomyModule;
20
- /** Media stack payload */
21
- media?: MediaModule;
22
- /** Times stack payload */
23
- times?: TimesModule;
24
14
  /** Unique identifier (slug or UUID) */
25
15
  id?: string;
26
16
  /** Optional display title */
@@ -37,6 +27,10 @@ interface SoustackRecipe {
37
27
  /** Additional tags for filtering */
38
28
  tags?: string[];
39
29
  /** URL(s) to recipe image(s) */
30
+ images?: string[];
31
+ /** URL(s) to recipe video(s) */
32
+ videos?: string[];
33
+ /** Legacy image field preserved for compatibility */
40
34
  image?: string | string[];
41
35
  /** ISO 8601 date string */
42
36
  dateAdded?: string;
@@ -74,19 +68,18 @@ interface ParsedYield {
74
68
  description?: string;
75
69
  }
76
70
  /**
77
- * Time can be structured (machine-readable) or simple (strings).
78
- * Structured time takes precedence if both exist.
71
+ * Time uses DurationMinutes format (vNext).
72
+ * Required total field with minutes.
79
73
  */
80
- type Time = StructuredTime | SimpleTime;
81
- interface StructuredTime {
82
- prep?: number;
83
- active?: number;
84
- passive?: number;
85
- total?: number;
86
- }
87
- interface SimpleTime {
88
- prepTime?: string;
89
- cookTime?: string;
74
+ interface Time {
75
+ total: DurationMinutes;
76
+ metadata?: Record<string, unknown>;
77
+ [k: `x-${string}`]: unknown;
78
+ }
79
+ interface DurationMinutes {
80
+ minutes: number;
81
+ metadata?: Record<string, unknown>;
82
+ [k: `x-${string}`]: unknown;
90
83
  }
91
84
  interface Equipment {
92
85
  id?: string;
@@ -96,99 +89,144 @@ interface Equipment {
96
89
  capacity?: Quantity$1;
97
90
  scalingLimit?: number;
98
91
  alternatives?: string[];
92
+ count?: number;
93
+ countScaling?: EquipmentCountScaling;
94
+ upgrades?: EquipmentUpgradeRule[];
99
95
  }
100
96
  interface Quantity$1 {
101
97
  amount: number;
102
98
  /** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
103
99
  unit: string | null;
104
100
  }
101
+ type EquipmentCountScaling = 'fixed' | 'linear' | EquipmentThresholdScaling;
102
+ interface EquipmentThresholdScaling {
103
+ mode: 'threshold';
104
+ steps: EquipmentThresholdStep[];
105
+ }
106
+ interface EquipmentThresholdStep {
107
+ maxFactor: number;
108
+ count: number;
109
+ }
110
+ interface EquipmentUpgradeRule {
111
+ minFactor: number;
112
+ use: string;
113
+ }
114
+ interface EquipmentUpgradeRecommendation {
115
+ fromId: string;
116
+ use: string;
117
+ minFactor: number;
118
+ }
119
+ interface ScalingMetadata {
120
+ multiplier: number;
121
+ equipment?: {
122
+ upgrades: EquipmentUpgradeRecommendation[];
123
+ };
124
+ }
125
+ type ScaledRecipe = Recipe & {
126
+ scaling?: ScalingMetadata;
127
+ };
105
128
  type IngredientItem = string | Ingredient$1 | IngredientSubsection;
106
129
  interface IngredientSubsection {
107
- subsection: string;
108
- items: (string | Ingredient$1)[];
130
+ section: string;
131
+ ingredients: IngredientItem[];
109
132
  }
110
133
  interface Ingredient$1 {
111
134
  id?: string;
112
- /** Full human-readable text (e.g. "2 cups flour") */
113
- item: string;
135
+ /** Ingredient name (required in vNext) */
136
+ name: string;
114
137
  quantity?: Quantity$1;
115
- name?: string;
116
- aisle?: string;
117
- /** Required prep state (e.g. "diced") */
118
- prep?: string;
119
- prepAction?: string;
120
- prepTime?: number;
138
+ /** Required prep state (e.g. "diced") or array of prep items */
139
+ prep?: string | string[];
121
140
  /** ID of equipment where this ingredient goes */
122
141
  destination?: string;
123
142
  scaling?: Scaling;
124
- critical?: boolean;
125
143
  optional?: boolean;
126
144
  notes?: string;
145
+ temperature?: Temperature;
146
+ metadata?: Record<string, unknown>;
147
+ [k: `x-${string}`]: unknown;
127
148
  }
128
149
  interface ParsedIngredient {
129
- item: string;
150
+ name: string;
130
151
  quantity?: {
131
152
  amount: number | null;
132
153
  unit: string | null;
133
154
  };
134
- name?: string;
135
155
  prep?: string;
136
156
  optional?: boolean;
137
157
  notes?: string;
138
158
  scaling?: Scaling;
139
159
  }
140
160
  /**
141
- * Intelligent Scaling Logic
161
+ * Intelligent Scaling Logic (vNext format)
142
162
  * Defines how an ingredient behaves when the recipe yield changes.
143
163
  */
144
- type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingBakersPercentage;
164
+ type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingToTaste | ScalingBakersPercentage;
145
165
  interface ScalingBase {
146
166
  min?: number;
147
167
  max?: number;
148
168
  }
149
169
  interface ScalingLinear extends ScalingBase {
150
- type: "linear";
170
+ mode: "linear";
151
171
  }
152
172
  interface ScalingDiscrete extends ScalingBase {
153
- type: "discrete";
154
- roundTo?: number;
173
+ mode: "discrete";
174
+ step?: number;
175
+ rounding?: "nearest" | "ceil" | "floor";
155
176
  }
156
177
  interface ScalingProportional extends ScalingBase {
157
- type: "proportional";
178
+ mode: "proportional";
158
179
  factor?: number;
159
180
  }
160
181
  interface ScalingFixed extends ScalingBase {
161
- type: "fixed";
182
+ mode: "fixed";
162
183
  }
163
- interface ScalingBakersPercentage extends ScalingBase {
164
- type: 'bakers_percentage';
184
+ interface ScalingToTaste {
185
+ mode: "toTaste";
186
+ }
187
+ interface ScalingBakersPercentage {
188
+ mode: "bakersPercent";
189
+ /** The percentage relative to the reference (e.g. 2 for 2%) */
190
+ percent: number;
165
191
  /** The ID of the flour/base ingredient this is relative to */
166
- referenceId: string;
167
- /** The percentage relative to the reference (e.g. 0.02 for 2%) */
168
- factor?: number;
192
+ of: string;
169
193
  }
170
194
  type InstructionItem = string | Instruction | InstructionSubsection;
171
195
  interface InstructionSubsection {
172
- subsection: string;
173
- items: (string | Instruction)[];
196
+ section: string;
197
+ steps: InstructionItem[];
174
198
  }
175
199
  interface SoustackInstruction {
176
200
  id?: string;
177
201
  text: string;
178
- destination?: string;
179
202
  /** IDs of steps that must complete before this one starts */
180
203
  dependsOn?: string[];
181
204
  /** IDs of ingredients used in this step */
182
205
  inputs?: string[];
206
+ /** IDs of techniques used in this step */
207
+ techniqueIds?: string[];
208
+ /** IDs of equipment used in this step */
209
+ usesEquipment?: string[];
183
210
  timing?: StepTiming;
184
- /** Optional image URL for this instruction */
185
- image?: string;
211
+ temperature?: Temperature;
212
+ images?: string[];
213
+ videos?: string[];
214
+ metadata?: Record<string, unknown>;
215
+ [k: `x-${string}`]: unknown;
186
216
  }
187
217
  type Instruction = SoustackInstruction;
188
218
  interface StepTiming {
189
- duration: number | string;
190
- type: "active" | "passive";
191
- scaling?: "linear" | "fixed" | "sqrt";
219
+ activity: "active" | "passive";
220
+ duration?: DurationMinutes | DurationRange;
221
+ completionCue?: string;
222
+ metadata?: Record<string, unknown>;
223
+ [k: `x-${string}`]: unknown;
224
+ }
225
+ interface DurationRange {
226
+ minMinutes: number;
227
+ maxMinutes: number;
228
+ metadata?: Record<string, unknown>;
229
+ [k: `x-${string}`]: unknown;
192
230
  }
193
231
  interface Storage {
194
232
  roomTemp?: StorageMethod;
@@ -227,24 +265,11 @@ interface NutritionFacts {
227
265
  calories?: number;
228
266
  protein_g?: number;
229
267
  }
230
- interface AttributionModule {
231
- url?: string;
232
- author?: string;
233
- datePublished?: string;
234
- }
235
- interface TaxonomyModule {
236
- keywords?: string[];
237
- category?: string;
238
- cuisine?: string;
239
- }
240
- interface MediaModule {
241
- images?: string[];
242
- videos?: string[];
243
- }
244
- interface TimesModule {
245
- prepMinutes?: number;
246
- cookMinutes?: number;
247
- totalMinutes?: number;
268
+ interface Temperature {
269
+ value: number;
270
+ unit: "celsius" | "fahrenheit";
271
+ metadata?: Record<string, unknown>;
272
+ [k: `x-${string}`]: unknown;
248
273
  }
249
274
 
250
275
  interface ScaleRecipeOptions {
@@ -264,7 +289,7 @@ interface ConformanceIssue {
264
289
  severity: ConformanceSeverity;
265
290
  }
266
291
 
267
- type ProfileName = "base" | "equipped" | "illustrated" | "lite" | "prepped" | "scalable" | "timed" | "minimal" | "core";
292
+ type ProfileName = "base" | "equipped" | "illustrated" | "lite" | "prepped" | "scalable" | "timed";
268
293
  interface NormalizedError {
269
294
  path: string;
270
295
  message: string;
@@ -350,6 +375,7 @@ interface SchemaOrgRecipe$1 {
350
375
  nutrition?: NutritionInformation;
351
376
  video?: SchemaOrgImage;
352
377
  '@graph'?: unknown;
378
+ $schema?: string;
353
379
  }
354
380
  type SchemaOrgIngredientList = string | string[];
355
381
  type SchemaOrgInstructionList = string | HowToStep$1 | HowToSection | Array<string | HowToStep$1 | HowToSection>;
@@ -397,7 +423,7 @@ interface NutritionInformation {
397
423
  /**
398
424
  * Convert a Soustack recipe to Schema.org JSON-LD format.
399
425
  *
400
- * BREAKING CHANGE in v0.3.0: This function now targets the "minimal" profile
426
+ * BREAKING CHANGE in v0.0.2: This function now targets the "minimal" profile
401
427
  * and only includes stacks that are schemaOrgMappable (as defined in the
402
428
  * stacks registry). Non-mappable stacks (e.g., nutrition@1, schedule@1)
403
429
  * are excluded from the conversion.
@@ -429,7 +455,7 @@ interface SchemaOrgRecipe {
429
455
 
430
456
  declare function extractSchemaOrgRecipeFromHTML(html: string): SchemaOrgRecipe | null;
431
457
 
432
- declare const SOUSTACK_SPEC_VERSION = "0.3.0";
458
+ declare const SOUSTACK_SPEC_VERSION = "0.0.2";
433
459
 
434
460
  type ConvertTarget = 'metric';
435
461
  type ConvertMode = 'volume' | 'mass';
@@ -494,4 +520,4 @@ interface MiseEnPlacePlan {
494
520
  }
495
521
  declare function miseEnPlace(ingredients: Ingredient[]): MiseEnPlacePlan;
496
522
 
497
- export { type Alternative, type AttributionModule, type ConvertMode, type ConvertTarget, type ConvertedLineItem, type Equipment, type FrozenStorageMethod, type Ingredient$1 as Ingredient, type IngredientItem, type IngredientSubsection, type Instruction, type InstructionItem, type InstructionSubsection, type LineItem, type MakeAheadComponent, type MediaModule, type Ingredient as MiseEnPlaceIngredient, type MiseEnPlacePlan, type Quantity as MiseEnPlaceQuantity, type MiseEnPlaceTask, MissingEquivalencyError, type NormalizationResult, type NutritionFacts, type ParsedIngredient, type ParsedYield, type Quantity$1 as Quantity, type Recipe, type RoundMode, SOUSTACK_SPEC_VERSION, type Scaling, type ScalingBakersPercentage, type ScalingBase, type ScalingDiscrete, type ScalingFixed, type ScalingLinear, type ScalingProportional, type SimpleTime, type Source, type SoustackInstruction, type SoustackRecipe, type StepTiming, type Storage, type StorageMethod, type StructuredTime, type Substitution, type TaxonomyModule, type Time, type TimesModule, UnknownUnitError, UnsupportedConversionError, type ValidateMode, type ValidateResult, type Yield, convertLineItemToMetric, detectProfiles, extractSchemaOrgRecipeFromHTML, fromSchemaOrg, miseEnPlace, normalizeRecipe, scaleRecipe, toSchemaOrg, validateRecipe };
523
+ export { type Alternative, type ConvertMode, type ConvertTarget, type ConvertedLineItem, type DurationMinutes, type DurationRange, type Equipment, type EquipmentCountScaling, type EquipmentThresholdScaling, type EquipmentThresholdStep, type EquipmentUpgradeRecommendation, type EquipmentUpgradeRule, type FrozenStorageMethod, type Ingredient$1 as Ingredient, type IngredientItem, type IngredientSubsection, type Instruction, type InstructionItem, type InstructionSubsection, type LineItem, type MakeAheadComponent, type Ingredient as MiseEnPlaceIngredient, type MiseEnPlacePlan, type Quantity as MiseEnPlaceQuantity, type MiseEnPlaceTask, MissingEquivalencyError, type NormalizationResult, type NutritionFacts, type ParsedIngredient, type ParsedYield, type Quantity$1 as Quantity, type Recipe, type RoundMode, SOUSTACK_SPEC_VERSION, type ScaledRecipe, type Scaling, type ScalingBakersPercentage, type ScalingBase, type ScalingDiscrete, type ScalingFixed, type ScalingLinear, type ScalingMetadata, type ScalingProportional, type ScalingToTaste, type Source, type SoustackInstruction, type SoustackRecipe, type StepTiming, type Storage, type StorageMethod, type Substitution, type Temperature, type Time, UnknownUnitError, UnsupportedConversionError, type ValidateMode, type ValidateResult, type Yield, convertLineItemToMetric, detectProfiles, extractSchemaOrgRecipeFromHTML, fromSchemaOrg, miseEnPlace, normalizeRecipe, scaleRecipe, toSchemaOrg, validateRecipe };