soustack 0.2.1 → 0.3.0

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/dist/index.d.ts CHANGED
@@ -1,13 +1,33 @@
1
1
  /**
2
- * Soustack Recipe Schema v0.1
2
+ * Soustack Recipe Schema v0.3.0
3
3
  * A portable, scalable, interoperable recipe format.
4
4
  */
5
5
  interface SoustackRecipe {
6
+ /** Document marker for Soustack recipes */
7
+ '@type'?: 'Recipe';
8
+ /** Optional $schema pointer for profile-aware validation */
9
+ $schema?: string;
10
+ /** Optional declared validation profile */
11
+ profile?: string;
12
+ /** Enabled module identifiers (e.g., "nutrition@1") */
13
+ modules?: string[];
14
+ /** Attribution module payload */
15
+ attribution?: AttributionModule;
16
+ /** Taxonomy module payload */
17
+ taxonomy?: TaxonomyModule;
18
+ /** Media module payload */
19
+ media?: MediaModule;
20
+ /** Times module payload */
21
+ times?: TimesModule;
6
22
  /** Unique identifier (slug or UUID) */
7
23
  id?: string;
24
+ /** Optional display title */
25
+ title?: string;
8
26
  /** The title of the recipe */
9
27
  name: string;
10
28
  /** Semantic versioning (e.g., 1.0.0) */
29
+ recipeVersion?: string;
30
+ /** Deprecated alias for recipeVersion */
11
31
  version?: string;
12
32
  description?: string;
13
33
  /** Primary category (e.g., "Main Course") */
@@ -29,6 +49,8 @@ interface SoustackRecipe {
29
49
  storage?: Storage;
30
50
  substitutions?: Substitution[];
31
51
  nutrition?: NutritionFacts;
52
+ metadata?: Record<string, unknown>;
53
+ [k: `x-${string}`]: unknown;
32
54
  }
33
55
  type Recipe = SoustackRecipe;
34
56
  interface Source {
@@ -69,25 +91,25 @@ interface Equipment {
69
91
  name: string;
70
92
  required?: boolean;
71
93
  label?: string;
72
- capacity?: Quantity;
94
+ capacity?: Quantity$1;
73
95
  scalingLimit?: number;
74
96
  alternatives?: string[];
75
97
  }
76
- interface Quantity {
98
+ interface Quantity$1 {
77
99
  amount: number;
78
100
  /** Unit string (e.g. "g", "cup") or null for count-based items (e.g. "2 eggs") */
79
101
  unit: string | null;
80
102
  }
81
- type IngredientItem = string | Ingredient | IngredientSubsection;
103
+ type IngredientItem = string | Ingredient$1 | IngredientSubsection;
82
104
  interface IngredientSubsection {
83
105
  subsection: string;
84
- items: (string | Ingredient)[];
106
+ items: (string | Ingredient$1)[];
85
107
  }
86
- interface Ingredient {
108
+ interface Ingredient$1 {
87
109
  id?: string;
88
110
  /** Full human-readable text (e.g. "2 cups flour") */
89
111
  item: string;
90
- quantity?: Quantity;
112
+ quantity?: Quantity$1;
91
113
  name?: string;
92
114
  aisle?: string;
93
115
  /** Required prep state (e.g. "diced") */
@@ -162,7 +184,7 @@ interface SoustackInstruction {
162
184
  }
163
185
  type Instruction = SoustackInstruction;
164
186
  interface StepTiming {
165
- duration: number;
187
+ duration: number | string;
166
188
  type: "active" | "passive";
167
189
  scaling?: "linear" | "fixed" | "sqrt";
168
190
  }
@@ -200,57 +222,66 @@ interface Alternative {
200
222
  dietary?: string[];
201
223
  }
202
224
  interface NutritionFacts {
203
- calories?: string;
204
- fatContent?: string;
205
- carbohydrateContent?: string;
206
- proteinContent?: string;
207
- fiberContent?: string;
208
- sugarContent?: string;
209
- sodiumContent?: string;
210
- servingSize?: string;
211
- [key: string]: string | number | null | string[] | undefined;
225
+ calories?: number;
226
+ protein_g?: number;
212
227
  }
213
-
214
- /**
215
- * A "Computed Recipe" is the result of running the parser.
216
- * It is flat, strict, and ready for the UI to render.
217
- */
218
- interface ComputedRecipe {
219
- metadata: {
220
- targetYield: number;
221
- baseYield: number;
222
- multiplier: number;
223
- };
224
- ingredients: ComputedIngredient[];
225
- instructions: ComputedInstruction[];
226
- timing: {
227
- active: number;
228
- passive: number;
229
- total: number;
230
- };
228
+ interface AttributionModule {
229
+ url?: string;
230
+ author?: string;
231
+ datePublished?: string;
231
232
  }
232
- interface ComputedIngredient {
233
- id: string;
234
- name: string;
235
- amount: number;
236
- unit: string | null;
237
- text: string;
238
- notes?: string;
233
+ interface TaxonomyModule {
234
+ keywords?: string[];
235
+ category?: string;
236
+ cuisine?: string;
239
237
  }
240
- interface ComputedInstruction {
241
- id: string;
242
- text: string;
243
- durationMinutes: number;
244
- type: 'active' | 'passive';
238
+ interface MediaModule {
239
+ images?: string[];
240
+ videos?: string[];
241
+ }
242
+ interface TimesModule {
243
+ prepMinutes?: number;
244
+ cookMinutes?: number;
245
+ totalMinutes?: number;
245
246
  }
246
- declare function scaleRecipe(recipe: Recipe, targetYieldAmount: number): ComputedRecipe;
247
247
 
248
- declare function validateRecipe(data: any): data is Recipe;
248
+ interface ScaleRecipeOptions {
249
+ multiplier?: number;
250
+ targetYield?: {
251
+ amount: number;
252
+ unit?: string;
253
+ };
254
+ }
255
+ declare function scaleRecipe(recipe: Recipe, options?: ScaleRecipeOptions): Recipe;
256
+
257
+ type ProfileName = "minimal" | "core";
258
+ interface NormalizedError {
259
+ path: string;
260
+ message: string;
261
+ keyword?: string;
262
+ }
263
+ interface NormalizedWarning {
264
+ path: string;
265
+ message: string;
266
+ }
267
+ interface ValidateOptions {
268
+ profile?: ProfileName;
269
+ schema?: string;
270
+ collectAllErrors?: boolean;
271
+ }
272
+ interface ValidationResult {
273
+ valid: boolean;
274
+ errors: NormalizedError[];
275
+ warnings: NormalizedWarning[];
276
+ normalized?: Recipe;
277
+ }
278
+ declare function validateRecipe(input: any, options?: ValidateOptions): ValidationResult;
279
+ declare function detectProfiles(recipe: any): ProfileName[];
249
280
 
250
281
  declare function fromSchemaOrg(input: unknown): Recipe | null;
251
282
 
252
283
  interface SchemaOrgRecipe$1 {
253
- '@context'?: string;
284
+ '@context'?: string | Array<string | Record<string, unknown>> | Record<string, unknown>;
254
285
  '@type'?: string | string[];
255
286
  name: string;
256
287
  description?: string;
@@ -271,6 +302,7 @@ interface SchemaOrgRecipe$1 {
271
302
  datePublished?: string;
272
303
  dateModified?: string;
273
304
  nutrition?: NutritionInformation;
305
+ video?: SchemaOrgImage;
274
306
  '@graph'?: unknown;
275
307
  }
276
308
  type SchemaOrgIngredientList = string | string[];
@@ -295,6 +327,12 @@ interface HowToStep$1 {
295
327
  name?: string;
296
328
  url?: string;
297
329
  image?: SchemaOrgImage;
330
+ '@id'?: string;
331
+ id?: string;
332
+ totalTime?: string;
333
+ performTime?: string;
334
+ prepTime?: string;
335
+ duration?: string;
298
336
  }
299
337
  interface HowToSection {
300
338
  '@type': 'HowToSection';
@@ -310,6 +348,14 @@ interface NutritionInformation {
310
348
  [key: string]: string | number | null | undefined;
311
349
  }
312
350
 
351
+ /**
352
+ * Convert a Soustack recipe to Schema.org JSON-LD format.
353
+ *
354
+ * BREAKING CHANGE in v0.3.0: This function now targets the "minimal" profile
355
+ * and only includes modules that are schemaOrgMappable (as defined in the
356
+ * modules registry). Non-mappable modules (e.g., nutrition@1, schedule@1)
357
+ * are excluded from the conversion.
358
+ */
313
359
  declare function toSchemaOrg(recipe: Recipe): SchemaOrgRecipe$1;
314
360
 
315
361
  interface HowToStep {
@@ -334,110 +380,72 @@ interface SchemaOrgRecipe {
334
380
  aggregateRating?: unknown;
335
381
  [key: string]: unknown;
336
382
  }
337
- interface FetchRequestInit {
338
- headers?: Record<string, string>;
339
- signal?: AbortSignal;
340
- redirect?: 'follow' | 'error' | 'manual';
341
- }
342
- interface FetchResponse {
343
- ok: boolean;
344
- status: number;
345
- statusText: string;
346
- text(): Promise<string>;
347
- }
348
- type FetchImplementation = (url: string, init?: FetchRequestInit) => Promise<FetchResponse>;
349
- interface FetchOptions {
350
- timeout?: number;
351
- userAgent?: string;
352
- maxRetries?: number;
353
- fetchFn?: FetchImplementation;
354
- }
355
- interface ScrapeRecipeOptions extends FetchOptions {
356
- }
357
383
 
358
- /**
359
- * Scrapes a recipe from a URL (Node.js only).
360
- *
361
- * ⚠️ Not available in browser environments due to CORS restrictions.
362
- * For browser usage, fetch the HTML yourself and use extractRecipeFromHTML().
363
- *
364
- * @param url - The URL of the recipe page to scrape
365
- * @param options - Fetch options (timeout, userAgent, maxRetries)
366
- * @returns A Soustack recipe object
367
- * @throws Error if no recipe is found
368
- */
369
- declare function scrapeRecipe(url: string, options?: ScrapeRecipeOptions): Promise<Recipe>;
370
- /**
371
- * Extracts a recipe from HTML string (browser and Node.js compatible).
372
- *
373
- * This function works in both environments and doesn't require network access.
374
- * Perfect for browser usage where you fetch HTML yourself (with cookies/session).
375
- *
376
- * @example
377
- * ```ts
378
- * // In browser:
379
- * const response = await fetch('https://example.com/recipe');
380
- * const html = await response.text();
381
- * const recipe = extractRecipeFromHTML(html);
382
- * ```
383
- *
384
- * @param html - The HTML string containing Schema.org recipe data
385
- * @returns A Soustack recipe object
386
- * @throws Error if no recipe is found
387
- */
388
- declare function extractRecipeFromHTML(html: string): Recipe;
389
- /**
390
- * Extract Schema.org recipe data from HTML string (browser-compatible).
391
- *
392
- * Returns the raw Schema.org recipe object, which can then be converted
393
- * to Soustack format using fromSchemaOrg(). This gives you access to the
394
- * original Schema.org data for inspection, debugging, or custom transformations.
395
- *
396
- * @param html - HTML string containing Schema.org recipe data
397
- * @returns Schema.org recipe object, or null if not found
398
- *
399
- * @example
400
- * ```ts
401
- * // In browser:
402
- * const response = await fetch('https://example.com/recipe');
403
- * const html = await response.text();
404
- * const schemaOrgRecipe = extractSchemaOrgRecipeFromHTML(html);
405
- *
406
- * if (schemaOrgRecipe) {
407
- * // Inspect or modify Schema.org data before converting
408
- * console.log('Found recipe:', schemaOrgRecipe.name);
409
- *
410
- * // Convert to Soustack format
411
- * const soustackRecipe = fromSchemaOrg(schemaOrgRecipe);
412
- * }
413
- * ```
414
- */
415
384
  declare function extractSchemaOrgRecipeFromHTML(html: string): SchemaOrgRecipe | null;
416
385
 
417
- declare function normalizeIngredientInput(input: string): string;
418
- declare function parseIngredient(text: string): ParsedIngredient;
419
- declare function parseIngredientLine(text: string): ParsedIngredient;
420
- declare function parseIngredients(texts: string[]): ParsedIngredient[];
421
-
422
- declare function parseDuration(iso: string): number | null;
423
- declare function parseDuration(iso: string | null | undefined): number | null;
424
- declare function formatDuration(minutes: number): string;
425
- declare function formatDuration(minutes: number | null | undefined): string;
426
- declare function parseHumanDuration(text: string): number | null;
427
- declare function parseHumanDuration(text: string | null | undefined): number | null;
428
- declare function smartParseDuration(input: string): number | null;
429
- declare function smartParseDuration(input: string | null | undefined): number | null;
386
+ declare const SOUSTACK_SPEC_VERSION = "0.3.0";
430
387
 
431
- declare function normalizeYield(text: string): string;
432
- declare function parseYield(text: string): ParsedYield | null;
433
- declare function formatYield(value: ParsedYield): string;
388
+ type ConvertTarget = 'metric';
389
+ type ConvertMode = 'volume' | 'mass';
390
+ type RoundMode = 'none' | 'sane';
391
+ interface LineItem {
392
+ ingredient: string;
393
+ quantity: number;
394
+ unit: string | null;
395
+ }
396
+ interface ConvertedLineItem extends LineItem {
397
+ notes?: string;
398
+ }
399
+ declare class UnknownUnitError extends Error {
400
+ readonly unit: string;
401
+ constructor(unit: string);
402
+ }
403
+ declare class UnsupportedConversionError extends Error {
404
+ readonly unit: string;
405
+ readonly mode: ConvertMode;
406
+ constructor(unit: string, mode: ConvertMode);
407
+ }
408
+ declare class MissingEquivalencyError extends Error {
409
+ readonly ingredient: string;
410
+ readonly unit: string;
411
+ constructor(ingredient: string, unit: string);
412
+ }
413
+ declare function convertLineItemToMetric(item: LineItem, mode: ConvertMode, opts?: {
414
+ round?: RoundMode;
415
+ }): ConvertedLineItem;
434
416
 
435
- /**
436
- * Normalize Schema.org image formats to Soustack format.
437
- * - String values pass through
438
- * - Arrays collapse to string or string[] after URL extraction
439
- * - ImageObjects extract their url/contentUrl
440
- */
441
- declare function normalizeImage(image: SchemaOrgImage | undefined | null): string | string[] | undefined;
417
+ interface Quantity {
418
+ amount: number;
419
+ unit?: string | null;
420
+ }
421
+ interface Ingredient {
422
+ id?: string;
423
+ item: string;
424
+ quantity?: Quantity;
425
+ name?: string;
426
+ prep?: string;
427
+ prepAction?: string;
428
+ prepActions?: string[];
429
+ form?: string;
430
+ prepTime?: number;
431
+ optional?: boolean;
432
+ notes?: string;
433
+ }
434
+ interface MiseEnPlaceTask {
435
+ category: 'prep' | 'state' | 'measure' | 'other';
436
+ action?: string;
437
+ form?: string;
438
+ items: Array<{
439
+ ingredient: string;
440
+ quantity?: Quantity;
441
+ optional?: boolean;
442
+ notes?: string;
443
+ }>;
444
+ }
445
+ interface MiseEnPlacePlan {
446
+ tasks: MiseEnPlaceTask[];
447
+ ungrouped: Ingredient[];
448
+ }
449
+ declare function miseEnPlace(ingredients: Ingredient[]): MiseEnPlacePlan;
442
450
 
443
- export { type Alternative, type ComputedIngredient, type ComputedInstruction, type ComputedRecipe, type Equipment, type FrozenStorageMethod, type Ingredient, type IngredientItem, type IngredientSubsection, type Instruction, type InstructionItem, type InstructionSubsection, type MakeAheadComponent, type NutritionFacts, type ParsedIngredient, type ParsedYield, type Quantity, type Recipe, type Scaling, type ScalingBakersPercentage, type ScalingBase, type ScalingDiscrete, type ScalingFixed, type ScalingLinear, type ScalingProportional, type SchemaOrgRecipe, type SimpleTime, type Source, type SoustackInstruction, type SoustackRecipe, type StepTiming, type Storage, type StorageMethod, type StructuredTime, type Substitution, type Time, type Yield, extractRecipeFromHTML, extractSchemaOrgRecipeFromHTML, formatDuration, formatYield, fromSchemaOrg, normalizeImage, normalizeIngredientInput, normalizeYield, parseDuration, parseHumanDuration, parseIngredient, parseIngredientLine, parseIngredients, parseYield, scaleRecipe, scrapeRecipe, smartParseDuration, toSchemaOrg, validateRecipe };
451
+ 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 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 Yield, convertLineItemToMetric, detectProfiles, extractSchemaOrgRecipeFromHTML, fromSchemaOrg, miseEnPlace, scaleRecipe, toSchemaOrg, validateRecipe };