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
@@ -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;
@@ -68,19 +62,18 @@ interface Yield {
68
62
  description?: string;
69
63
  }
70
64
  /**
71
- * Time can be structured (machine-readable) or simple (strings).
72
- * Structured time takes precedence if both exist.
65
+ * Time uses DurationMinutes format (vNext).
66
+ * Required total field with minutes.
73
67
  */
74
- type Time = StructuredTime | SimpleTime;
75
- interface StructuredTime {
76
- prep?: number;
77
- active?: number;
78
- passive?: number;
79
- total?: number;
80
- }
81
- interface SimpleTime {
82
- prepTime?: string;
83
- cookTime?: string;
68
+ interface Time {
69
+ total: DurationMinutes;
70
+ metadata?: Record<string, unknown>;
71
+ [k: `x-${string}`]: unknown;
72
+ }
73
+ interface DurationMinutes {
74
+ minutes: number;
75
+ metadata?: Record<string, unknown>;
76
+ [k: `x-${string}`]: unknown;
84
77
  }
85
78
  interface Equipment {
86
79
  id?: string;
@@ -90,87 +83,119 @@ interface Equipment {
90
83
  capacity?: Quantity;
91
84
  scalingLimit?: number;
92
85
  alternatives?: string[];
86
+ count?: number;
87
+ countScaling?: EquipmentCountScaling;
88
+ upgrades?: EquipmentUpgradeRule[];
93
89
  }
94
90
  interface Quantity {
95
91
  amount: number;
96
92
  /** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
97
93
  unit: string | null;
98
94
  }
95
+ type EquipmentCountScaling = 'fixed' | 'linear' | EquipmentThresholdScaling;
96
+ interface EquipmentThresholdScaling {
97
+ mode: 'threshold';
98
+ steps: EquipmentThresholdStep[];
99
+ }
100
+ interface EquipmentThresholdStep {
101
+ maxFactor: number;
102
+ count: number;
103
+ }
104
+ interface EquipmentUpgradeRule {
105
+ minFactor: number;
106
+ use: string;
107
+ }
99
108
  type IngredientItem = string | Ingredient | IngredientSubsection;
100
109
  interface IngredientSubsection {
101
- subsection: string;
102
- items: (string | Ingredient)[];
110
+ section: string;
111
+ ingredients: IngredientItem[];
103
112
  }
104
113
  interface Ingredient {
105
114
  id?: string;
106
- /** Full human-readable text (e.g. "2 cups flour") */
107
- item: string;
115
+ /** Ingredient name (required in vNext) */
116
+ name: string;
108
117
  quantity?: Quantity;
109
- name?: string;
110
- aisle?: string;
111
- /** Required prep state (e.g. "diced") */
112
- prep?: string;
113
- prepAction?: string;
114
- prepTime?: number;
118
+ /** Required prep state (e.g. "diced") or array of prep items */
119
+ prep?: string | string[];
115
120
  /** ID of equipment where this ingredient goes */
116
121
  destination?: string;
117
122
  scaling?: Scaling;
118
- critical?: boolean;
119
123
  optional?: boolean;
120
124
  notes?: string;
125
+ temperature?: Temperature;
126
+ metadata?: Record<string, unknown>;
127
+ [k: `x-${string}`]: unknown;
121
128
  }
122
129
  /**
123
- * Intelligent Scaling Logic
130
+ * Intelligent Scaling Logic (vNext format)
124
131
  * Defines how an ingredient behaves when the recipe yield changes.
125
132
  */
126
- type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingBakersPercentage;
133
+ type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingToTaste | ScalingBakersPercentage;
127
134
  interface ScalingBase {
128
135
  min?: number;
129
136
  max?: number;
130
137
  }
131
138
  interface ScalingLinear extends ScalingBase {
132
- type: "linear";
139
+ mode: "linear";
133
140
  }
134
141
  interface ScalingDiscrete extends ScalingBase {
135
- type: "discrete";
136
- roundTo?: number;
142
+ mode: "discrete";
143
+ step?: number;
144
+ rounding?: "nearest" | "ceil" | "floor";
137
145
  }
138
146
  interface ScalingProportional extends ScalingBase {
139
- type: "proportional";
147
+ mode: "proportional";
140
148
  factor?: number;
141
149
  }
142
150
  interface ScalingFixed extends ScalingBase {
143
- type: "fixed";
151
+ mode: "fixed";
152
+ }
153
+ interface ScalingToTaste {
154
+ mode: "toTaste";
144
155
  }
145
- interface ScalingBakersPercentage extends ScalingBase {
146
- type: 'bakers_percentage';
156
+ interface ScalingBakersPercentage {
157
+ mode: "bakersPercent";
158
+ /** The percentage relative to the reference (e.g. 2 for 2%) */
159
+ percent: number;
147
160
  /** The ID of the flour/base ingredient this is relative to */
148
- referenceId: string;
149
- /** The percentage relative to the reference (e.g. 0.02 for 2%) */
150
- factor?: number;
161
+ of: string;
151
162
  }
152
163
  type InstructionItem = string | Instruction | InstructionSubsection;
153
164
  interface InstructionSubsection {
154
- subsection: string;
155
- items: (string | Instruction)[];
165
+ section: string;
166
+ steps: InstructionItem[];
156
167
  }
157
168
  interface SoustackInstruction {
158
169
  id?: string;
159
170
  text: string;
160
- destination?: string;
161
171
  /** IDs of steps that must complete before this one starts */
162
172
  dependsOn?: string[];
163
173
  /** IDs of ingredients used in this step */
164
174
  inputs?: string[];
175
+ /** IDs of techniques used in this step */
176
+ techniqueIds?: string[];
177
+ /** IDs of equipment used in this step */
178
+ usesEquipment?: string[];
165
179
  timing?: StepTiming;
166
- /** Optional image URL for this instruction */
167
- image?: string;
180
+ temperature?: Temperature;
181
+ images?: string[];
182
+ videos?: string[];
183
+ metadata?: Record<string, unknown>;
184
+ [k: `x-${string}`]: unknown;
168
185
  }
169
186
  type Instruction = SoustackInstruction;
170
187
  interface StepTiming {
171
- duration: number | string;
172
- type: "active" | "passive";
173
- scaling?: "linear" | "fixed" | "sqrt";
188
+ activity: "active" | "passive";
189
+ duration?: DurationMinutes | DurationRange;
190
+ completionCue?: string;
191
+ metadata?: Record<string, unknown>;
192
+ [k: `x-${string}`]: unknown;
193
+ }
194
+ interface DurationRange {
195
+ minMinutes: number;
196
+ maxMinutes: number;
197
+ metadata?: Record<string, unknown>;
198
+ [k: `x-${string}`]: unknown;
174
199
  }
175
200
  interface Storage {
176
201
  roomTemp?: StorageMethod;
@@ -209,24 +234,11 @@ interface NutritionFacts {
209
234
  calories?: number;
210
235
  protein_g?: number;
211
236
  }
212
- interface AttributionModule {
213
- url?: string;
214
- author?: string;
215
- datePublished?: string;
216
- }
217
- interface TaxonomyModule {
218
- keywords?: string[];
219
- category?: string;
220
- cuisine?: string;
221
- }
222
- interface MediaModule {
223
- images?: string[];
224
- videos?: string[];
225
- }
226
- interface TimesModule {
227
- prepMinutes?: number;
228
- cookMinutes?: number;
229
- totalMinutes?: number;
237
+ interface Temperature {
238
+ value: number;
239
+ unit: "celsius" | "fahrenheit";
240
+ metadata?: Record<string, unknown>;
241
+ [k: `x-${string}`]: unknown;
230
242
  }
231
243
 
232
244
  interface HowToStep {
@@ -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;
@@ -68,19 +62,18 @@ interface Yield {
68
62
  description?: string;
69
63
  }
70
64
  /**
71
- * Time can be structured (machine-readable) or simple (strings).
72
- * Structured time takes precedence if both exist.
65
+ * Time uses DurationMinutes format (vNext).
66
+ * Required total field with minutes.
73
67
  */
74
- type Time = StructuredTime | SimpleTime;
75
- interface StructuredTime {
76
- prep?: number;
77
- active?: number;
78
- passive?: number;
79
- total?: number;
80
- }
81
- interface SimpleTime {
82
- prepTime?: string;
83
- cookTime?: string;
68
+ interface Time {
69
+ total: DurationMinutes;
70
+ metadata?: Record<string, unknown>;
71
+ [k: `x-${string}`]: unknown;
72
+ }
73
+ interface DurationMinutes {
74
+ minutes: number;
75
+ metadata?: Record<string, unknown>;
76
+ [k: `x-${string}`]: unknown;
84
77
  }
85
78
  interface Equipment {
86
79
  id?: string;
@@ -90,87 +83,119 @@ interface Equipment {
90
83
  capacity?: Quantity;
91
84
  scalingLimit?: number;
92
85
  alternatives?: string[];
86
+ count?: number;
87
+ countScaling?: EquipmentCountScaling;
88
+ upgrades?: EquipmentUpgradeRule[];
93
89
  }
94
90
  interface Quantity {
95
91
  amount: number;
96
92
  /** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
97
93
  unit: string | null;
98
94
  }
95
+ type EquipmentCountScaling = 'fixed' | 'linear' | EquipmentThresholdScaling;
96
+ interface EquipmentThresholdScaling {
97
+ mode: 'threshold';
98
+ steps: EquipmentThresholdStep[];
99
+ }
100
+ interface EquipmentThresholdStep {
101
+ maxFactor: number;
102
+ count: number;
103
+ }
104
+ interface EquipmentUpgradeRule {
105
+ minFactor: number;
106
+ use: string;
107
+ }
99
108
  type IngredientItem = string | Ingredient | IngredientSubsection;
100
109
  interface IngredientSubsection {
101
- subsection: string;
102
- items: (string | Ingredient)[];
110
+ section: string;
111
+ ingredients: IngredientItem[];
103
112
  }
104
113
  interface Ingredient {
105
114
  id?: string;
106
- /** Full human-readable text (e.g. "2 cups flour") */
107
- item: string;
115
+ /** Ingredient name (required in vNext) */
116
+ name: string;
108
117
  quantity?: Quantity;
109
- name?: string;
110
- aisle?: string;
111
- /** Required prep state (e.g. "diced") */
112
- prep?: string;
113
- prepAction?: string;
114
- prepTime?: number;
118
+ /** Required prep state (e.g. "diced") or array of prep items */
119
+ prep?: string | string[];
115
120
  /** ID of equipment where this ingredient goes */
116
121
  destination?: string;
117
122
  scaling?: Scaling;
118
- critical?: boolean;
119
123
  optional?: boolean;
120
124
  notes?: string;
125
+ temperature?: Temperature;
126
+ metadata?: Record<string, unknown>;
127
+ [k: `x-${string}`]: unknown;
121
128
  }
122
129
  /**
123
- * Intelligent Scaling Logic
130
+ * Intelligent Scaling Logic (vNext format)
124
131
  * Defines how an ingredient behaves when the recipe yield changes.
125
132
  */
126
- type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingBakersPercentage;
133
+ type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingToTaste | ScalingBakersPercentage;
127
134
  interface ScalingBase {
128
135
  min?: number;
129
136
  max?: number;
130
137
  }
131
138
  interface ScalingLinear extends ScalingBase {
132
- type: "linear";
139
+ mode: "linear";
133
140
  }
134
141
  interface ScalingDiscrete extends ScalingBase {
135
- type: "discrete";
136
- roundTo?: number;
142
+ mode: "discrete";
143
+ step?: number;
144
+ rounding?: "nearest" | "ceil" | "floor";
137
145
  }
138
146
  interface ScalingProportional extends ScalingBase {
139
- type: "proportional";
147
+ mode: "proportional";
140
148
  factor?: number;
141
149
  }
142
150
  interface ScalingFixed extends ScalingBase {
143
- type: "fixed";
151
+ mode: "fixed";
152
+ }
153
+ interface ScalingToTaste {
154
+ mode: "toTaste";
144
155
  }
145
- interface ScalingBakersPercentage extends ScalingBase {
146
- type: 'bakers_percentage';
156
+ interface ScalingBakersPercentage {
157
+ mode: "bakersPercent";
158
+ /** The percentage relative to the reference (e.g. 2 for 2%) */
159
+ percent: number;
147
160
  /** The ID of the flour/base ingredient this is relative to */
148
- referenceId: string;
149
- /** The percentage relative to the reference (e.g. 0.02 for 2%) */
150
- factor?: number;
161
+ of: string;
151
162
  }
152
163
  type InstructionItem = string | Instruction | InstructionSubsection;
153
164
  interface InstructionSubsection {
154
- subsection: string;
155
- items: (string | Instruction)[];
165
+ section: string;
166
+ steps: InstructionItem[];
156
167
  }
157
168
  interface SoustackInstruction {
158
169
  id?: string;
159
170
  text: string;
160
- destination?: string;
161
171
  /** IDs of steps that must complete before this one starts */
162
172
  dependsOn?: string[];
163
173
  /** IDs of ingredients used in this step */
164
174
  inputs?: string[];
175
+ /** IDs of techniques used in this step */
176
+ techniqueIds?: string[];
177
+ /** IDs of equipment used in this step */
178
+ usesEquipment?: string[];
165
179
  timing?: StepTiming;
166
- /** Optional image URL for this instruction */
167
- image?: string;
180
+ temperature?: Temperature;
181
+ images?: string[];
182
+ videos?: string[];
183
+ metadata?: Record<string, unknown>;
184
+ [k: `x-${string}`]: unknown;
168
185
  }
169
186
  type Instruction = SoustackInstruction;
170
187
  interface StepTiming {
171
- duration: number | string;
172
- type: "active" | "passive";
173
- scaling?: "linear" | "fixed" | "sqrt";
188
+ activity: "active" | "passive";
189
+ duration?: DurationMinutes | DurationRange;
190
+ completionCue?: string;
191
+ metadata?: Record<string, unknown>;
192
+ [k: `x-${string}`]: unknown;
193
+ }
194
+ interface DurationRange {
195
+ minMinutes: number;
196
+ maxMinutes: number;
197
+ metadata?: Record<string, unknown>;
198
+ [k: `x-${string}`]: unknown;
174
199
  }
175
200
  interface Storage {
176
201
  roomTemp?: StorageMethod;
@@ -209,24 +234,11 @@ interface NutritionFacts {
209
234
  calories?: number;
210
235
  protein_g?: number;
211
236
  }
212
- interface AttributionModule {
213
- url?: string;
214
- author?: string;
215
- datePublished?: string;
216
- }
217
- interface TaxonomyModule {
218
- keywords?: string[];
219
- category?: string;
220
- cuisine?: string;
221
- }
222
- interface MediaModule {
223
- images?: string[];
224
- videos?: string[];
225
- }
226
- interface TimesModule {
227
- prepMinutes?: number;
228
- cookMinutes?: number;
229
- totalMinutes?: number;
237
+ interface Temperature {
238
+ value: number;
239
+ unit: "celsius" | "fahrenheit";
240
+ metadata?: Record<string, unknown>;
241
+ [k: `x-${string}`]: unknown;
230
242
  }
231
243
 
232
244
  interface HowToStep {