soustack 0.2.1 → 0.2.3
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.
- package/LICENSE +21 -21
- package/README.md +301 -244
- package/dist/cli/index.js +1697 -1357
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +48 -138
- package/dist/index.d.ts +48 -138
- package/dist/index.js +1093 -1466
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1092 -1453
- package/dist/index.mjs.map +1 -1
- package/dist/scrape.d.mts +308 -0
- package/dist/scrape.d.ts +308 -0
- package/dist/scrape.js +819 -0
- package/dist/scrape.js.map +1 -0
- package/dist/scrape.mjs +814 -0
- package/dist/scrape.mjs.map +1 -0
- package/package.json +86 -75
- package/src/profiles/.gitkeep +0 -0
- package/src/profiles/base.schema.json +9 -0
- package/src/profiles/cookable.schema.json +18 -0
- package/src/profiles/illustrated.schema.json +48 -0
- package/src/profiles/quantified.schema.json +43 -0
- package/src/profiles/scalable.schema.json +75 -0
- package/src/profiles/schedulable.schema.json +43 -0
- package/src/schema.json +43 -22
- package/src/soustack.schema.json +344 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soustack Recipe Schema v0.2.1
|
|
3
|
+
* A portable, scalable, interoperable recipe format.
|
|
4
|
+
*/
|
|
5
|
+
interface SoustackRecipe {
|
|
6
|
+
/** Optional $schema pointer for profile-aware validation */
|
|
7
|
+
$schema?: string;
|
|
8
|
+
/** Unique identifier (slug or UUID) */
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Optional display title */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** The title of the recipe */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Semantic versioning (e.g., 1.0.0) */
|
|
15
|
+
recipeVersion?: string;
|
|
16
|
+
/** Deprecated alias for recipeVersion */
|
|
17
|
+
version?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Primary category (e.g., "Main Course") */
|
|
20
|
+
category?: string;
|
|
21
|
+
/** Additional tags for filtering */
|
|
22
|
+
tags?: string[];
|
|
23
|
+
/** URL(s) to recipe image(s) */
|
|
24
|
+
image?: string | string[];
|
|
25
|
+
/** ISO 8601 date string */
|
|
26
|
+
dateAdded?: string;
|
|
27
|
+
/** Last updated timestamp */
|
|
28
|
+
dateModified?: string;
|
|
29
|
+
source?: Source;
|
|
30
|
+
yield?: Yield;
|
|
31
|
+
time?: Time;
|
|
32
|
+
equipment?: Equipment[];
|
|
33
|
+
ingredients: IngredientItem[];
|
|
34
|
+
instructions: InstructionItem[];
|
|
35
|
+
storage?: Storage;
|
|
36
|
+
substitutions?: Substitution[];
|
|
37
|
+
nutrition?: NutritionFacts;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
[k: `x-${string}`]: unknown;
|
|
40
|
+
}
|
|
41
|
+
type Recipe = SoustackRecipe;
|
|
42
|
+
interface Source {
|
|
43
|
+
author?: string;
|
|
44
|
+
url?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
adapted?: boolean;
|
|
47
|
+
}
|
|
48
|
+
interface Yield {
|
|
49
|
+
amount: number;
|
|
50
|
+
unit: string;
|
|
51
|
+
servings?: number;
|
|
52
|
+
description?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Time can be structured (machine-readable) or simple (strings).
|
|
56
|
+
* Structured time takes precedence if both exist.
|
|
57
|
+
*/
|
|
58
|
+
type Time = StructuredTime | SimpleTime;
|
|
59
|
+
interface StructuredTime {
|
|
60
|
+
prep?: number;
|
|
61
|
+
active?: number;
|
|
62
|
+
passive?: number;
|
|
63
|
+
total?: number;
|
|
64
|
+
}
|
|
65
|
+
interface SimpleTime {
|
|
66
|
+
prepTime?: string;
|
|
67
|
+
cookTime?: string;
|
|
68
|
+
}
|
|
69
|
+
interface Equipment {
|
|
70
|
+
id?: string;
|
|
71
|
+
name: string;
|
|
72
|
+
required?: boolean;
|
|
73
|
+
label?: string;
|
|
74
|
+
capacity?: Quantity;
|
|
75
|
+
scalingLimit?: number;
|
|
76
|
+
alternatives?: string[];
|
|
77
|
+
}
|
|
78
|
+
interface Quantity {
|
|
79
|
+
amount: number;
|
|
80
|
+
/** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
|
|
81
|
+
unit: string | null;
|
|
82
|
+
}
|
|
83
|
+
type IngredientItem = string | Ingredient | IngredientSubsection;
|
|
84
|
+
interface IngredientSubsection {
|
|
85
|
+
subsection: string;
|
|
86
|
+
items: (string | Ingredient)[];
|
|
87
|
+
}
|
|
88
|
+
interface Ingredient {
|
|
89
|
+
id?: string;
|
|
90
|
+
/** Full human-readable text (e.g. "2 cups flour") */
|
|
91
|
+
item: string;
|
|
92
|
+
quantity?: Quantity;
|
|
93
|
+
name?: string;
|
|
94
|
+
aisle?: string;
|
|
95
|
+
/** Required prep state (e.g. "diced") */
|
|
96
|
+
prep?: string;
|
|
97
|
+
prepAction?: string;
|
|
98
|
+
prepTime?: number;
|
|
99
|
+
/** ID of equipment where this ingredient goes */
|
|
100
|
+
destination?: string;
|
|
101
|
+
scaling?: Scaling;
|
|
102
|
+
critical?: boolean;
|
|
103
|
+
optional?: boolean;
|
|
104
|
+
notes?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Intelligent Scaling Logic
|
|
108
|
+
* Defines how an ingredient behaves when the recipe yield changes.
|
|
109
|
+
*/
|
|
110
|
+
type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingBakersPercentage;
|
|
111
|
+
interface ScalingBase {
|
|
112
|
+
min?: number;
|
|
113
|
+
max?: number;
|
|
114
|
+
}
|
|
115
|
+
interface ScalingLinear extends ScalingBase {
|
|
116
|
+
type: "linear";
|
|
117
|
+
}
|
|
118
|
+
interface ScalingDiscrete extends ScalingBase {
|
|
119
|
+
type: "discrete";
|
|
120
|
+
roundTo?: number;
|
|
121
|
+
}
|
|
122
|
+
interface ScalingProportional extends ScalingBase {
|
|
123
|
+
type: "proportional";
|
|
124
|
+
factor?: number;
|
|
125
|
+
}
|
|
126
|
+
interface ScalingFixed extends ScalingBase {
|
|
127
|
+
type: "fixed";
|
|
128
|
+
}
|
|
129
|
+
interface ScalingBakersPercentage extends ScalingBase {
|
|
130
|
+
type: 'bakers_percentage';
|
|
131
|
+
/** The ID of the flour/base ingredient this is relative to */
|
|
132
|
+
referenceId: string;
|
|
133
|
+
/** The percentage relative to the reference (e.g. 0.02 for 2%) */
|
|
134
|
+
factor?: number;
|
|
135
|
+
}
|
|
136
|
+
type InstructionItem = string | Instruction | InstructionSubsection;
|
|
137
|
+
interface InstructionSubsection {
|
|
138
|
+
subsection: string;
|
|
139
|
+
items: (string | Instruction)[];
|
|
140
|
+
}
|
|
141
|
+
interface SoustackInstruction {
|
|
142
|
+
id?: string;
|
|
143
|
+
text: string;
|
|
144
|
+
destination?: string;
|
|
145
|
+
/** IDs of steps that must complete before this one starts */
|
|
146
|
+
dependsOn?: string[];
|
|
147
|
+
/** IDs of ingredients used in this step */
|
|
148
|
+
inputs?: string[];
|
|
149
|
+
timing?: StepTiming;
|
|
150
|
+
/** Optional image URL for this instruction */
|
|
151
|
+
image?: string;
|
|
152
|
+
}
|
|
153
|
+
type Instruction = SoustackInstruction;
|
|
154
|
+
interface StepTiming {
|
|
155
|
+
duration: number | string;
|
|
156
|
+
type: "active" | "passive";
|
|
157
|
+
scaling?: "linear" | "fixed" | "sqrt";
|
|
158
|
+
}
|
|
159
|
+
interface Storage {
|
|
160
|
+
roomTemp?: StorageMethod;
|
|
161
|
+
refrigerated?: StorageMethod;
|
|
162
|
+
frozen?: FrozenStorageMethod;
|
|
163
|
+
reheating?: string;
|
|
164
|
+
makeAhead?: MakeAheadComponent[];
|
|
165
|
+
}
|
|
166
|
+
interface StorageMethod {
|
|
167
|
+
/** ISO 8601 duration (e.g. P3D) */
|
|
168
|
+
duration: string;
|
|
169
|
+
method?: string;
|
|
170
|
+
notes?: string;
|
|
171
|
+
}
|
|
172
|
+
interface FrozenStorageMethod extends StorageMethod {
|
|
173
|
+
thawing?: string;
|
|
174
|
+
}
|
|
175
|
+
interface MakeAheadComponent extends StorageMethod {
|
|
176
|
+
component: string;
|
|
177
|
+
storage: "roomTemp" | "refrigerated" | "frozen";
|
|
178
|
+
}
|
|
179
|
+
interface Substitution {
|
|
180
|
+
ingredient: string;
|
|
181
|
+
critical?: boolean;
|
|
182
|
+
notes?: string;
|
|
183
|
+
alternatives?: Alternative[];
|
|
184
|
+
}
|
|
185
|
+
interface Alternative {
|
|
186
|
+
name: string;
|
|
187
|
+
ratio: string;
|
|
188
|
+
notes?: string;
|
|
189
|
+
impact?: string;
|
|
190
|
+
dietary?: string[];
|
|
191
|
+
}
|
|
192
|
+
interface NutritionFacts {
|
|
193
|
+
calories?: string;
|
|
194
|
+
fatContent?: string;
|
|
195
|
+
carbohydrateContent?: string;
|
|
196
|
+
proteinContent?: string;
|
|
197
|
+
fiberContent?: string;
|
|
198
|
+
sugarContent?: string;
|
|
199
|
+
sodiumContent?: string;
|
|
200
|
+
servingSize?: string;
|
|
201
|
+
[key: string]: string | number | null | string[] | undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface HowToStep {
|
|
205
|
+
'@type'?: 'HowToStep' | 'HowToSection' | string;
|
|
206
|
+
name?: string;
|
|
207
|
+
text?: string;
|
|
208
|
+
itemListElement?: Array<string | HowToStep>;
|
|
209
|
+
}
|
|
210
|
+
interface SchemaOrgRecipe {
|
|
211
|
+
'@type': string | string[];
|
|
212
|
+
name?: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
image?: string | string[];
|
|
215
|
+
recipeIngredient?: string[];
|
|
216
|
+
recipeInstructions?: Array<string | HowToStep>;
|
|
217
|
+
recipeYield?: string | number;
|
|
218
|
+
prepTime?: string;
|
|
219
|
+
cookTime?: string;
|
|
220
|
+
totalTime?: string;
|
|
221
|
+
author?: unknown;
|
|
222
|
+
datePublished?: string;
|
|
223
|
+
aggregateRating?: unknown;
|
|
224
|
+
[key: string]: unknown;
|
|
225
|
+
}
|
|
226
|
+
interface FetchRequestInit {
|
|
227
|
+
headers?: Record<string, string>;
|
|
228
|
+
signal?: AbortSignal;
|
|
229
|
+
redirect?: 'follow' | 'error' | 'manual';
|
|
230
|
+
}
|
|
231
|
+
interface FetchResponse {
|
|
232
|
+
ok: boolean;
|
|
233
|
+
status: number;
|
|
234
|
+
statusText: string;
|
|
235
|
+
text(): Promise<string>;
|
|
236
|
+
}
|
|
237
|
+
type FetchImplementation = (url: string, init?: FetchRequestInit) => Promise<FetchResponse>;
|
|
238
|
+
interface FetchOptions {
|
|
239
|
+
timeout?: number;
|
|
240
|
+
userAgent?: string;
|
|
241
|
+
maxRetries?: number;
|
|
242
|
+
fetchFn?: FetchImplementation;
|
|
243
|
+
}
|
|
244
|
+
interface ScrapeRecipeOptions extends FetchOptions {
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Scrapes a recipe from a URL (Node.js only).
|
|
249
|
+
*
|
|
250
|
+
* ⚠️ Not available in browser environments due to CORS restrictions.
|
|
251
|
+
* For browser usage, fetch the HTML yourself and use extractRecipeFromHTML().
|
|
252
|
+
*
|
|
253
|
+
* @param url - The URL of the recipe page to scrape
|
|
254
|
+
* @param options - Fetch options (timeout, userAgent, maxRetries)
|
|
255
|
+
* @returns A Soustack recipe object
|
|
256
|
+
* @throws Error if no recipe is found
|
|
257
|
+
*/
|
|
258
|
+
declare function scrapeRecipe(url: string, options?: ScrapeRecipeOptions): Promise<Recipe>;
|
|
259
|
+
/**
|
|
260
|
+
* Extracts a recipe from HTML string (browser and Node.js compatible).
|
|
261
|
+
*
|
|
262
|
+
* This function works in both environments and doesn't require network access.
|
|
263
|
+
* Perfect for browser usage where you fetch HTML yourself (with cookies/session).
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* // In browser:
|
|
268
|
+
* const response = await fetch('https://example.com/recipe');
|
|
269
|
+
* const html = await response.text();
|
|
270
|
+
* const recipe = extractRecipeFromHTML(html);
|
|
271
|
+
* ```
|
|
272
|
+
*
|
|
273
|
+
* @param html - The HTML string containing Schema.org recipe data
|
|
274
|
+
* @returns A Soustack recipe object
|
|
275
|
+
* @throws Error if no recipe is found
|
|
276
|
+
*/
|
|
277
|
+
declare function extractRecipeFromHTML(html: string): Recipe;
|
|
278
|
+
/**
|
|
279
|
+
* Extract Schema.org recipe data from HTML string (browser-compatible).
|
|
280
|
+
*
|
|
281
|
+
* Returns the raw Schema.org recipe object, which can then be converted
|
|
282
|
+
* to Soustack format using fromSchemaOrg(). This gives you access to the
|
|
283
|
+
* original Schema.org data for inspection, debugging, or custom transformations.
|
|
284
|
+
*
|
|
285
|
+
* @param html - HTML string containing Schema.org recipe data
|
|
286
|
+
* @returns Schema.org recipe object, or null if not found
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* // In browser:
|
|
291
|
+
* const response = await fetch('https://example.com/recipe');
|
|
292
|
+
* const html = await response.text();
|
|
293
|
+
* const schemaOrgRecipe = extractSchemaOrgRecipeFromHTML(html);
|
|
294
|
+
*
|
|
295
|
+
* if (schemaOrgRecipe) {
|
|
296
|
+
* // Inspect or modify Schema.org data before converting
|
|
297
|
+
* console.log('Found recipe:', schemaOrgRecipe.name);
|
|
298
|
+
*
|
|
299
|
+
* // Convert to Soustack format
|
|
300
|
+
* const soustackRecipe = fromSchemaOrg(schemaOrgRecipe);
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
declare function extractSchemaOrgRecipeFromHTML(html: string): SchemaOrgRecipe | null;
|
|
305
|
+
|
|
306
|
+
declare function fetchPage(url: string, options?: FetchOptions): Promise<string>;
|
|
307
|
+
|
|
308
|
+
export { type FetchImplementation, type FetchOptions, type SchemaOrgRecipe, type ScrapeRecipeOptions, extractRecipeFromHTML, extractSchemaOrgRecipeFromHTML, fetchPage, scrapeRecipe };
|
package/dist/scrape.d.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soustack Recipe Schema v0.2.1
|
|
3
|
+
* A portable, scalable, interoperable recipe format.
|
|
4
|
+
*/
|
|
5
|
+
interface SoustackRecipe {
|
|
6
|
+
/** Optional $schema pointer for profile-aware validation */
|
|
7
|
+
$schema?: string;
|
|
8
|
+
/** Unique identifier (slug or UUID) */
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Optional display title */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** The title of the recipe */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Semantic versioning (e.g., 1.0.0) */
|
|
15
|
+
recipeVersion?: string;
|
|
16
|
+
/** Deprecated alias for recipeVersion */
|
|
17
|
+
version?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
/** Primary category (e.g., "Main Course") */
|
|
20
|
+
category?: string;
|
|
21
|
+
/** Additional tags for filtering */
|
|
22
|
+
tags?: string[];
|
|
23
|
+
/** URL(s) to recipe image(s) */
|
|
24
|
+
image?: string | string[];
|
|
25
|
+
/** ISO 8601 date string */
|
|
26
|
+
dateAdded?: string;
|
|
27
|
+
/** Last updated timestamp */
|
|
28
|
+
dateModified?: string;
|
|
29
|
+
source?: Source;
|
|
30
|
+
yield?: Yield;
|
|
31
|
+
time?: Time;
|
|
32
|
+
equipment?: Equipment[];
|
|
33
|
+
ingredients: IngredientItem[];
|
|
34
|
+
instructions: InstructionItem[];
|
|
35
|
+
storage?: Storage;
|
|
36
|
+
substitutions?: Substitution[];
|
|
37
|
+
nutrition?: NutritionFacts;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
[k: `x-${string}`]: unknown;
|
|
40
|
+
}
|
|
41
|
+
type Recipe = SoustackRecipe;
|
|
42
|
+
interface Source {
|
|
43
|
+
author?: string;
|
|
44
|
+
url?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
adapted?: boolean;
|
|
47
|
+
}
|
|
48
|
+
interface Yield {
|
|
49
|
+
amount: number;
|
|
50
|
+
unit: string;
|
|
51
|
+
servings?: number;
|
|
52
|
+
description?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Time can be structured (machine-readable) or simple (strings).
|
|
56
|
+
* Structured time takes precedence if both exist.
|
|
57
|
+
*/
|
|
58
|
+
type Time = StructuredTime | SimpleTime;
|
|
59
|
+
interface StructuredTime {
|
|
60
|
+
prep?: number;
|
|
61
|
+
active?: number;
|
|
62
|
+
passive?: number;
|
|
63
|
+
total?: number;
|
|
64
|
+
}
|
|
65
|
+
interface SimpleTime {
|
|
66
|
+
prepTime?: string;
|
|
67
|
+
cookTime?: string;
|
|
68
|
+
}
|
|
69
|
+
interface Equipment {
|
|
70
|
+
id?: string;
|
|
71
|
+
name: string;
|
|
72
|
+
required?: boolean;
|
|
73
|
+
label?: string;
|
|
74
|
+
capacity?: Quantity;
|
|
75
|
+
scalingLimit?: number;
|
|
76
|
+
alternatives?: string[];
|
|
77
|
+
}
|
|
78
|
+
interface Quantity {
|
|
79
|
+
amount: number;
|
|
80
|
+
/** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
|
|
81
|
+
unit: string | null;
|
|
82
|
+
}
|
|
83
|
+
type IngredientItem = string | Ingredient | IngredientSubsection;
|
|
84
|
+
interface IngredientSubsection {
|
|
85
|
+
subsection: string;
|
|
86
|
+
items: (string | Ingredient)[];
|
|
87
|
+
}
|
|
88
|
+
interface Ingredient {
|
|
89
|
+
id?: string;
|
|
90
|
+
/** Full human-readable text (e.g. "2 cups flour") */
|
|
91
|
+
item: string;
|
|
92
|
+
quantity?: Quantity;
|
|
93
|
+
name?: string;
|
|
94
|
+
aisle?: string;
|
|
95
|
+
/** Required prep state (e.g. "diced") */
|
|
96
|
+
prep?: string;
|
|
97
|
+
prepAction?: string;
|
|
98
|
+
prepTime?: number;
|
|
99
|
+
/** ID of equipment where this ingredient goes */
|
|
100
|
+
destination?: string;
|
|
101
|
+
scaling?: Scaling;
|
|
102
|
+
critical?: boolean;
|
|
103
|
+
optional?: boolean;
|
|
104
|
+
notes?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Intelligent Scaling Logic
|
|
108
|
+
* Defines how an ingredient behaves when the recipe yield changes.
|
|
109
|
+
*/
|
|
110
|
+
type Scaling = ScalingLinear | ScalingDiscrete | ScalingProportional | ScalingFixed | ScalingBakersPercentage;
|
|
111
|
+
interface ScalingBase {
|
|
112
|
+
min?: number;
|
|
113
|
+
max?: number;
|
|
114
|
+
}
|
|
115
|
+
interface ScalingLinear extends ScalingBase {
|
|
116
|
+
type: "linear";
|
|
117
|
+
}
|
|
118
|
+
interface ScalingDiscrete extends ScalingBase {
|
|
119
|
+
type: "discrete";
|
|
120
|
+
roundTo?: number;
|
|
121
|
+
}
|
|
122
|
+
interface ScalingProportional extends ScalingBase {
|
|
123
|
+
type: "proportional";
|
|
124
|
+
factor?: number;
|
|
125
|
+
}
|
|
126
|
+
interface ScalingFixed extends ScalingBase {
|
|
127
|
+
type: "fixed";
|
|
128
|
+
}
|
|
129
|
+
interface ScalingBakersPercentage extends ScalingBase {
|
|
130
|
+
type: 'bakers_percentage';
|
|
131
|
+
/** The ID of the flour/base ingredient this is relative to */
|
|
132
|
+
referenceId: string;
|
|
133
|
+
/** The percentage relative to the reference (e.g. 0.02 for 2%) */
|
|
134
|
+
factor?: number;
|
|
135
|
+
}
|
|
136
|
+
type InstructionItem = string | Instruction | InstructionSubsection;
|
|
137
|
+
interface InstructionSubsection {
|
|
138
|
+
subsection: string;
|
|
139
|
+
items: (string | Instruction)[];
|
|
140
|
+
}
|
|
141
|
+
interface SoustackInstruction {
|
|
142
|
+
id?: string;
|
|
143
|
+
text: string;
|
|
144
|
+
destination?: string;
|
|
145
|
+
/** IDs of steps that must complete before this one starts */
|
|
146
|
+
dependsOn?: string[];
|
|
147
|
+
/** IDs of ingredients used in this step */
|
|
148
|
+
inputs?: string[];
|
|
149
|
+
timing?: StepTiming;
|
|
150
|
+
/** Optional image URL for this instruction */
|
|
151
|
+
image?: string;
|
|
152
|
+
}
|
|
153
|
+
type Instruction = SoustackInstruction;
|
|
154
|
+
interface StepTiming {
|
|
155
|
+
duration: number | string;
|
|
156
|
+
type: "active" | "passive";
|
|
157
|
+
scaling?: "linear" | "fixed" | "sqrt";
|
|
158
|
+
}
|
|
159
|
+
interface Storage {
|
|
160
|
+
roomTemp?: StorageMethod;
|
|
161
|
+
refrigerated?: StorageMethod;
|
|
162
|
+
frozen?: FrozenStorageMethod;
|
|
163
|
+
reheating?: string;
|
|
164
|
+
makeAhead?: MakeAheadComponent[];
|
|
165
|
+
}
|
|
166
|
+
interface StorageMethod {
|
|
167
|
+
/** ISO 8601 duration (e.g. P3D) */
|
|
168
|
+
duration: string;
|
|
169
|
+
method?: string;
|
|
170
|
+
notes?: string;
|
|
171
|
+
}
|
|
172
|
+
interface FrozenStorageMethod extends StorageMethod {
|
|
173
|
+
thawing?: string;
|
|
174
|
+
}
|
|
175
|
+
interface MakeAheadComponent extends StorageMethod {
|
|
176
|
+
component: string;
|
|
177
|
+
storage: "roomTemp" | "refrigerated" | "frozen";
|
|
178
|
+
}
|
|
179
|
+
interface Substitution {
|
|
180
|
+
ingredient: string;
|
|
181
|
+
critical?: boolean;
|
|
182
|
+
notes?: string;
|
|
183
|
+
alternatives?: Alternative[];
|
|
184
|
+
}
|
|
185
|
+
interface Alternative {
|
|
186
|
+
name: string;
|
|
187
|
+
ratio: string;
|
|
188
|
+
notes?: string;
|
|
189
|
+
impact?: string;
|
|
190
|
+
dietary?: string[];
|
|
191
|
+
}
|
|
192
|
+
interface NutritionFacts {
|
|
193
|
+
calories?: string;
|
|
194
|
+
fatContent?: string;
|
|
195
|
+
carbohydrateContent?: string;
|
|
196
|
+
proteinContent?: string;
|
|
197
|
+
fiberContent?: string;
|
|
198
|
+
sugarContent?: string;
|
|
199
|
+
sodiumContent?: string;
|
|
200
|
+
servingSize?: string;
|
|
201
|
+
[key: string]: string | number | null | string[] | undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface HowToStep {
|
|
205
|
+
'@type'?: 'HowToStep' | 'HowToSection' | string;
|
|
206
|
+
name?: string;
|
|
207
|
+
text?: string;
|
|
208
|
+
itemListElement?: Array<string | HowToStep>;
|
|
209
|
+
}
|
|
210
|
+
interface SchemaOrgRecipe {
|
|
211
|
+
'@type': string | string[];
|
|
212
|
+
name?: string;
|
|
213
|
+
description?: string;
|
|
214
|
+
image?: string | string[];
|
|
215
|
+
recipeIngredient?: string[];
|
|
216
|
+
recipeInstructions?: Array<string | HowToStep>;
|
|
217
|
+
recipeYield?: string | number;
|
|
218
|
+
prepTime?: string;
|
|
219
|
+
cookTime?: string;
|
|
220
|
+
totalTime?: string;
|
|
221
|
+
author?: unknown;
|
|
222
|
+
datePublished?: string;
|
|
223
|
+
aggregateRating?: unknown;
|
|
224
|
+
[key: string]: unknown;
|
|
225
|
+
}
|
|
226
|
+
interface FetchRequestInit {
|
|
227
|
+
headers?: Record<string, string>;
|
|
228
|
+
signal?: AbortSignal;
|
|
229
|
+
redirect?: 'follow' | 'error' | 'manual';
|
|
230
|
+
}
|
|
231
|
+
interface FetchResponse {
|
|
232
|
+
ok: boolean;
|
|
233
|
+
status: number;
|
|
234
|
+
statusText: string;
|
|
235
|
+
text(): Promise<string>;
|
|
236
|
+
}
|
|
237
|
+
type FetchImplementation = (url: string, init?: FetchRequestInit) => Promise<FetchResponse>;
|
|
238
|
+
interface FetchOptions {
|
|
239
|
+
timeout?: number;
|
|
240
|
+
userAgent?: string;
|
|
241
|
+
maxRetries?: number;
|
|
242
|
+
fetchFn?: FetchImplementation;
|
|
243
|
+
}
|
|
244
|
+
interface ScrapeRecipeOptions extends FetchOptions {
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Scrapes a recipe from a URL (Node.js only).
|
|
249
|
+
*
|
|
250
|
+
* ⚠️ Not available in browser environments due to CORS restrictions.
|
|
251
|
+
* For browser usage, fetch the HTML yourself and use extractRecipeFromHTML().
|
|
252
|
+
*
|
|
253
|
+
* @param url - The URL of the recipe page to scrape
|
|
254
|
+
* @param options - Fetch options (timeout, userAgent, maxRetries)
|
|
255
|
+
* @returns A Soustack recipe object
|
|
256
|
+
* @throws Error if no recipe is found
|
|
257
|
+
*/
|
|
258
|
+
declare function scrapeRecipe(url: string, options?: ScrapeRecipeOptions): Promise<Recipe>;
|
|
259
|
+
/**
|
|
260
|
+
* Extracts a recipe from HTML string (browser and Node.js compatible).
|
|
261
|
+
*
|
|
262
|
+
* This function works in both environments and doesn't require network access.
|
|
263
|
+
* Perfect for browser usage where you fetch HTML yourself (with cookies/session).
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* // In browser:
|
|
268
|
+
* const response = await fetch('https://example.com/recipe');
|
|
269
|
+
* const html = await response.text();
|
|
270
|
+
* const recipe = extractRecipeFromHTML(html);
|
|
271
|
+
* ```
|
|
272
|
+
*
|
|
273
|
+
* @param html - The HTML string containing Schema.org recipe data
|
|
274
|
+
* @returns A Soustack recipe object
|
|
275
|
+
* @throws Error if no recipe is found
|
|
276
|
+
*/
|
|
277
|
+
declare function extractRecipeFromHTML(html: string): Recipe;
|
|
278
|
+
/**
|
|
279
|
+
* Extract Schema.org recipe data from HTML string (browser-compatible).
|
|
280
|
+
*
|
|
281
|
+
* Returns the raw Schema.org recipe object, which can then be converted
|
|
282
|
+
* to Soustack format using fromSchemaOrg(). This gives you access to the
|
|
283
|
+
* original Schema.org data for inspection, debugging, or custom transformations.
|
|
284
|
+
*
|
|
285
|
+
* @param html - HTML string containing Schema.org recipe data
|
|
286
|
+
* @returns Schema.org recipe object, or null if not found
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* // In browser:
|
|
291
|
+
* const response = await fetch('https://example.com/recipe');
|
|
292
|
+
* const html = await response.text();
|
|
293
|
+
* const schemaOrgRecipe = extractSchemaOrgRecipeFromHTML(html);
|
|
294
|
+
*
|
|
295
|
+
* if (schemaOrgRecipe) {
|
|
296
|
+
* // Inspect or modify Schema.org data before converting
|
|
297
|
+
* console.log('Found recipe:', schemaOrgRecipe.name);
|
|
298
|
+
*
|
|
299
|
+
* // Convert to Soustack format
|
|
300
|
+
* const soustackRecipe = fromSchemaOrg(schemaOrgRecipe);
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
declare function extractSchemaOrgRecipeFromHTML(html: string): SchemaOrgRecipe | null;
|
|
305
|
+
|
|
306
|
+
declare function fetchPage(url: string, options?: FetchOptions): Promise<string>;
|
|
307
|
+
|
|
308
|
+
export { type FetchImplementation, type FetchOptions, type SchemaOrgRecipe, type ScrapeRecipeOptions, extractRecipeFromHTML, extractSchemaOrgRecipeFromHTML, fetchPage, scrapeRecipe };
|