soustack 0.1.3 → 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.
@@ -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 };
@@ -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 };