recipe-scrapers-js 1.0.0-rc.1 → 1.0.0-rc.2
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/CHANGELOG.md +13 -2
- package/dist/index.d.mts +265 -152
- package/dist/index.mjs +67 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,15 +6,26 @@ All notable changes to this project will be documented in this file.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [1.0.0-rc.2] - 2025-12-20
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add `schemaVersion` to recipe schema
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Make `links` field optional, and thus `undefined` in the recipe object, unless link parsing is enabled
|
|
18
|
+
- Move schema transform & refinement validations into a helper function `applyRecipeValidations`
|
|
19
|
+
|
|
9
20
|
## [1.0.0-rc.1] - 2025-12-20
|
|
10
21
|
|
|
11
22
|
### Added
|
|
12
23
|
|
|
13
|
-
-
|
|
24
|
+
- Add `tsdown` configuration file
|
|
14
25
|
|
|
15
26
|
### Fixed
|
|
16
27
|
|
|
17
|
-
-
|
|
28
|
+
- Fix main/module/type entriess in package.json; add exports field
|
|
18
29
|
|
|
19
30
|
## [1.0.0-rc.0] - 2025-12-20
|
|
20
31
|
|
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,45 @@ import { CheerioAPI } from "cheerio";
|
|
|
3
3
|
import z$1, { z } from "zod";
|
|
4
4
|
import { ParseIngredientOptions } from "parse-ingredient";
|
|
5
5
|
|
|
6
|
+
//#region src/logger.d.ts
|
|
7
|
+
declare enum LogLevel {
|
|
8
|
+
VERBOSE = 0,
|
|
9
|
+
DEBUG = 1,
|
|
10
|
+
INFO = 2,
|
|
11
|
+
WARN = 3,
|
|
12
|
+
ERROR = 4,
|
|
13
|
+
}
|
|
14
|
+
declare class Logger {
|
|
15
|
+
private context;
|
|
16
|
+
private logLevel;
|
|
17
|
+
constructor(context: string, logLevel?: LogLevel);
|
|
18
|
+
verbose(...args: unknown[]): void;
|
|
19
|
+
debug(...args: unknown[]): void;
|
|
20
|
+
log(...args: unknown[]): void;
|
|
21
|
+
info(...args: unknown[]): void;
|
|
22
|
+
warn(...args: unknown[]): void;
|
|
23
|
+
error(...args: unknown[]): void;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/abstract-plugin.d.ts
|
|
27
|
+
declare abstract class AbstractPlugin {
|
|
28
|
+
readonly $: CheerioAPI;
|
|
29
|
+
/** The name of the plugin */
|
|
30
|
+
abstract name: string;
|
|
31
|
+
/** The priority of the plugin */
|
|
32
|
+
abstract priority: number;
|
|
33
|
+
constructor($: CheerioAPI);
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
6
36
|
//#region src/schemas/recipe.schema.d.ts
|
|
37
|
+
/**
|
|
38
|
+
* Current schema version for recipe objects.
|
|
39
|
+
* Increment this when making breaking changes to the schema.
|
|
40
|
+
*
|
|
41
|
+
* Version history:
|
|
42
|
+
* - 1.0.0: Initial schema version
|
|
43
|
+
*/
|
|
44
|
+
declare const RECIPE_SCHEMA_VERSION: "1.0.0";
|
|
7
45
|
/**
|
|
8
46
|
* Schema for a parsed ingredient from the parse-ingredient library.
|
|
9
47
|
* This represents the structured data extracted from an ingredient string.
|
|
@@ -123,10 +161,23 @@ declare const LinkSchema: z.ZodObject<{
|
|
|
123
161
|
text: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
124
162
|
}, z.core.$strip>;
|
|
125
163
|
/**
|
|
126
|
-
* Base RecipeObject schema without cross-field validations
|
|
127
|
-
*
|
|
164
|
+
* Base RecipeObject schema without cross-field validations.
|
|
165
|
+
* Use this schema when you need to extend the recipe object with custom fields.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* import { RecipeObjectBaseSchema, applyRecipeValidations } from 'recipe-scrapers-js'
|
|
170
|
+
*
|
|
171
|
+
* const MyCustomRecipeSchema = RecipeObjectBaseSchema.extend({
|
|
172
|
+
* customField: z.string(),
|
|
173
|
+
* })
|
|
174
|
+
*
|
|
175
|
+
* // Apply the standard recipe validations
|
|
176
|
+
* const MyValidatedRecipeSchema = applyRecipeValidations(MyCustomRecipeSchema)
|
|
177
|
+
* ```
|
|
128
178
|
*/
|
|
129
179
|
declare const RecipeObjectBaseSchema: z.ZodObject<{
|
|
180
|
+
schemaVersion: z.ZodDefault<z.ZodLiteral<"1.0.0">>;
|
|
130
181
|
host: z.ZodCustomStringFormat<"hostname">;
|
|
131
182
|
title: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
132
183
|
author: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
@@ -179,7 +230,7 @@ declare const RecipeObjectBaseSchema: z.ZodObject<{
|
|
|
179
230
|
keywords: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
180
231
|
dietaryRestrictions: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
181
232
|
equipment: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
182
|
-
links: z.
|
|
233
|
+
links: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
183
234
|
href: z.ZodURL;
|
|
184
235
|
text: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
185
236
|
}, z.core.$strip>>>;
|
|
@@ -187,15 +238,38 @@ declare const RecipeObjectBaseSchema: z.ZodObject<{
|
|
|
187
238
|
reviews: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
188
239
|
}, z.core.$strip>;
|
|
189
240
|
/**
|
|
190
|
-
*
|
|
241
|
+
* Applies recipe-specific transformations and validations to a schema.
|
|
242
|
+
* Use this when extending RecipeObjectBaseSchema with custom fields.
|
|
243
|
+
*
|
|
244
|
+
* @param schema - A Zod object schema that includes
|
|
245
|
+
* all RecipeObjectBaseSchema fields
|
|
246
|
+
* @returns A schema with transforms and field validations applied
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* const CustomSchema = RecipeObjectBaseSchema.extend({
|
|
251
|
+
* tags: z.array(z.string()),
|
|
252
|
+
* })
|
|
253
|
+
*
|
|
254
|
+
* const ValidatedCustomSchema = applyRecipeValidations(CustomSchema)
|
|
255
|
+
* ```
|
|
191
256
|
*/
|
|
192
|
-
declare
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
257
|
+
declare function applyRecipeValidations<T extends z.infer<typeof RecipeObjectBaseSchema>>(schema: z.ZodType<T>): z.ZodPipe<z.ZodType<T, unknown, z.core.$ZodTypeInternals<T, unknown>>, z.ZodTransform<Awaited<T>, T>>;
|
|
258
|
+
/**
|
|
259
|
+
* Strict RecipeObject schema with all validations enforced.
|
|
260
|
+
* This is the standard schema used by recipe scrapers.
|
|
261
|
+
*
|
|
262
|
+
* For custom extensions, use RecipeObjectBaseSchema.extend() and then
|
|
263
|
+
* apply validations with applyRecipeValidations().
|
|
264
|
+
*/
|
|
265
|
+
declare const RecipeObjectSchema: z.ZodPipe<z.ZodType<{
|
|
266
|
+
schemaVersion: "1.0.0";
|
|
267
|
+
host: string;
|
|
268
|
+
title: string;
|
|
269
|
+
author: string;
|
|
270
|
+
ingredients: {
|
|
271
|
+
name: string | null;
|
|
272
|
+
items: {
|
|
199
273
|
value: string;
|
|
200
274
|
parsed?: {
|
|
201
275
|
quantity: number | null;
|
|
@@ -205,7 +279,45 @@ declare const RecipeObjectSchema: z.ZodPipe<z.ZodObject<{
|
|
|
205
279
|
description: string;
|
|
206
280
|
isGroupHeader: boolean;
|
|
207
281
|
} | undefined;
|
|
208
|
-
}
|
|
282
|
+
}[];
|
|
283
|
+
}[];
|
|
284
|
+
instructions: {
|
|
285
|
+
name: string | null;
|
|
286
|
+
items: {
|
|
287
|
+
value: string;
|
|
288
|
+
}[];
|
|
289
|
+
}[];
|
|
290
|
+
canonicalUrl: string;
|
|
291
|
+
image: string;
|
|
292
|
+
totalTime: number | null;
|
|
293
|
+
cookTime: number | null;
|
|
294
|
+
prepTime: number | null;
|
|
295
|
+
ratings: number;
|
|
296
|
+
ratingsCount: number;
|
|
297
|
+
yields: string;
|
|
298
|
+
description: string;
|
|
299
|
+
language: string;
|
|
300
|
+
siteName: string | null;
|
|
301
|
+
cookingMethod: string | null;
|
|
302
|
+
category: string[];
|
|
303
|
+
cuisine: string[];
|
|
304
|
+
keywords: string[];
|
|
305
|
+
dietaryRestrictions: string[];
|
|
306
|
+
equipment: string[];
|
|
307
|
+
nutrients: Record<string, string>;
|
|
308
|
+
reviews: Record<string, string>;
|
|
309
|
+
links?: {
|
|
310
|
+
href: string;
|
|
311
|
+
text: string;
|
|
312
|
+
}[] | undefined;
|
|
313
|
+
}, unknown, z.core.$ZodTypeInternals<{
|
|
314
|
+
schemaVersion: "1.0.0";
|
|
315
|
+
host: string;
|
|
316
|
+
title: string;
|
|
317
|
+
author: string;
|
|
318
|
+
ingredients: {
|
|
319
|
+
name: string | null;
|
|
320
|
+
items: {
|
|
209
321
|
value: string;
|
|
210
322
|
parsed?: {
|
|
211
323
|
quantity: number | null;
|
|
@@ -215,40 +327,39 @@ declare const RecipeObjectSchema: z.ZodPipe<z.ZodObject<{
|
|
|
215
327
|
description: string;
|
|
216
328
|
isGroupHeader: boolean;
|
|
217
329
|
} | undefined;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
instructions:
|
|
221
|
-
name:
|
|
222
|
-
items:
|
|
223
|
-
value: string;
|
|
224
|
-
}, unknown, z.core.$ZodTypeInternals<{
|
|
330
|
+
}[];
|
|
331
|
+
}[];
|
|
332
|
+
instructions: {
|
|
333
|
+
name: string | null;
|
|
334
|
+
items: {
|
|
225
335
|
value: string;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
canonicalUrl:
|
|
229
|
-
image:
|
|
230
|
-
totalTime:
|
|
231
|
-
cookTime:
|
|
232
|
-
prepTime:
|
|
233
|
-
ratings:
|
|
234
|
-
ratingsCount:
|
|
235
|
-
yields:
|
|
236
|
-
description:
|
|
237
|
-
language:
|
|
238
|
-
siteName:
|
|
239
|
-
cookingMethod:
|
|
240
|
-
category:
|
|
241
|
-
cuisine:
|
|
242
|
-
keywords:
|
|
243
|
-
dietaryRestrictions:
|
|
244
|
-
equipment:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
},
|
|
336
|
+
}[];
|
|
337
|
+
}[];
|
|
338
|
+
canonicalUrl: string;
|
|
339
|
+
image: string;
|
|
340
|
+
totalTime: number | null;
|
|
341
|
+
cookTime: number | null;
|
|
342
|
+
prepTime: number | null;
|
|
343
|
+
ratings: number;
|
|
344
|
+
ratingsCount: number;
|
|
345
|
+
yields: string;
|
|
346
|
+
description: string;
|
|
347
|
+
language: string;
|
|
348
|
+
siteName: string | null;
|
|
349
|
+
cookingMethod: string | null;
|
|
350
|
+
category: string[];
|
|
351
|
+
cuisine: string[];
|
|
352
|
+
keywords: string[];
|
|
353
|
+
dietaryRestrictions: string[];
|
|
354
|
+
equipment: string[];
|
|
355
|
+
nutrients: Record<string, string>;
|
|
356
|
+
reviews: Record<string, string>;
|
|
357
|
+
links?: {
|
|
358
|
+
href: string;
|
|
359
|
+
text: string;
|
|
360
|
+
}[] | undefined;
|
|
361
|
+
}, unknown>>, z.ZodTransform<{
|
|
362
|
+
schemaVersion: "1.0.0";
|
|
252
363
|
host: string;
|
|
253
364
|
title: string;
|
|
254
365
|
author: string;
|
|
@@ -289,13 +400,14 @@ declare const RecipeObjectSchema: z.ZodPipe<z.ZodObject<{
|
|
|
289
400
|
keywords: string[];
|
|
290
401
|
dietaryRestrictions: string[];
|
|
291
402
|
equipment: string[];
|
|
292
|
-
links: {
|
|
293
|
-
href: string;
|
|
294
|
-
text: string;
|
|
295
|
-
}[];
|
|
296
403
|
nutrients: Record<string, string>;
|
|
297
404
|
reviews: Record<string, string>;
|
|
405
|
+
links?: {
|
|
406
|
+
href: string;
|
|
407
|
+
text: string;
|
|
408
|
+
}[] | undefined;
|
|
298
409
|
}, {
|
|
410
|
+
schemaVersion: "1.0.0";
|
|
299
411
|
host: string;
|
|
300
412
|
title: string;
|
|
301
413
|
author: string;
|
|
@@ -336,44 +448,13 @@ declare const RecipeObjectSchema: z.ZodPipe<z.ZodObject<{
|
|
|
336
448
|
keywords: string[];
|
|
337
449
|
dietaryRestrictions: string[];
|
|
338
450
|
equipment: string[];
|
|
339
|
-
links: {
|
|
340
|
-
href: string;
|
|
341
|
-
text: string;
|
|
342
|
-
}[];
|
|
343
451
|
nutrients: Record<string, string>;
|
|
344
452
|
reviews: Record<string, string>;
|
|
453
|
+
links?: {
|
|
454
|
+
href: string;
|
|
455
|
+
text: string;
|
|
456
|
+
}[] | undefined;
|
|
345
457
|
}>>;
|
|
346
|
-
type RecipeObjectValidated = z.infer<typeof RecipeObjectSchema>;
|
|
347
|
-
//#endregion
|
|
348
|
-
//#region src/logger.d.ts
|
|
349
|
-
declare enum LogLevel {
|
|
350
|
-
VERBOSE = 0,
|
|
351
|
-
DEBUG = 1,
|
|
352
|
-
INFO = 2,
|
|
353
|
-
WARN = 3,
|
|
354
|
-
ERROR = 4,
|
|
355
|
-
}
|
|
356
|
-
declare class Logger {
|
|
357
|
-
private context;
|
|
358
|
-
private logLevel;
|
|
359
|
-
constructor(context: string, logLevel?: LogLevel);
|
|
360
|
-
verbose(...args: unknown[]): void;
|
|
361
|
-
debug(...args: unknown[]): void;
|
|
362
|
-
log(...args: unknown[]): void;
|
|
363
|
-
info(...args: unknown[]): void;
|
|
364
|
-
warn(...args: unknown[]): void;
|
|
365
|
-
error(...args: unknown[]): void;
|
|
366
|
-
}
|
|
367
|
-
//#endregion
|
|
368
|
-
//#region src/abstract-plugin.d.ts
|
|
369
|
-
declare abstract class AbstractPlugin {
|
|
370
|
-
readonly $: CheerioAPI;
|
|
371
|
-
/** The name of the plugin */
|
|
372
|
-
abstract name: string;
|
|
373
|
-
/** The priority of the plugin */
|
|
374
|
-
abstract priority: number;
|
|
375
|
-
constructor($: CheerioAPI);
|
|
376
|
-
}
|
|
377
458
|
//#endregion
|
|
378
459
|
//#region src/types/recipe.interface.d.ts
|
|
379
460
|
type List = Set<string>;
|
|
@@ -405,6 +486,10 @@ type InstructionGroup = z.infer<typeof InstructionGroupSchema>;
|
|
|
405
486
|
* All recipe instructions as an array of groups
|
|
406
487
|
*/
|
|
407
488
|
type Instructions = InstructionGroup[];
|
|
489
|
+
/**
|
|
490
|
+
* The complete recipe object
|
|
491
|
+
*/
|
|
492
|
+
type RecipeObject = z.infer<typeof RecipeObjectSchema>;
|
|
408
493
|
/**
|
|
409
494
|
* A link with href and display text
|
|
410
495
|
*/
|
|
@@ -461,8 +546,9 @@ interface RecipeData {
|
|
|
461
546
|
/**
|
|
462
547
|
* An list of all links found in the page HTML defined within an anchor
|
|
463
548
|
* `<a>` element.
|
|
549
|
+
* Only present when `linksEnabled` option is set to `true`.
|
|
464
550
|
*/
|
|
465
|
-
links
|
|
551
|
+
links?: Link[];
|
|
466
552
|
/**
|
|
467
553
|
* A description of the recipe. This is normally a sentence or short
|
|
468
554
|
* paragraph describing the recipe. Often the website defines the
|
|
@@ -613,20 +699,7 @@ type RecipeFields = Omit<RecipeData, 'host'>;
|
|
|
613
699
|
/**
|
|
614
700
|
* The fields that aren't required for a recipe to be valid.
|
|
615
701
|
*/
|
|
616
|
-
type OptionalRecipeFields = Pick<RecipeFields, 'siteName' | 'category' | 'cookTime' | 'prepTime' | 'totalTime' | 'cuisine' | 'cookingMethod' | 'ratings' | 'ratingsCount' | 'equipment' | 'reviews' | 'nutrients' | 'dietaryRestrictions' | 'keywords'>;
|
|
617
|
-
/**
|
|
618
|
-
* The validated recipe object output from the schema.
|
|
619
|
-
* This represents a recipe that has passed all validation rules.
|
|
620
|
-
*/
|
|
621
|
-
interface RecipeObject extends Omit<RecipeData, 'category' | 'cuisine' | 'dietaryRestrictions' | 'equipment' | 'keywords' | 'nutrients' | 'reviews'> {
|
|
622
|
-
category: string[];
|
|
623
|
-
cuisine: string[];
|
|
624
|
-
dietaryRestrictions: string[];
|
|
625
|
-
equipment: string[];
|
|
626
|
-
keywords: string[];
|
|
627
|
-
nutrients: Record<string, string>;
|
|
628
|
-
reviews: Record<string, string>;
|
|
629
|
-
}
|
|
702
|
+
type OptionalRecipeFields = Pick<RecipeFields, 'siteName' | 'category' | 'cookTime' | 'prepTime' | 'totalTime' | 'cuisine' | 'cookingMethod' | 'ratings' | 'ratingsCount' | 'equipment' | 'reviews' | 'nutrients' | 'dietaryRestrictions' | 'keywords' | 'links'>;
|
|
630
703
|
//#endregion
|
|
631
704
|
//#region src/abstract-extractor-plugin.d.ts
|
|
632
705
|
declare abstract class ExtractorPlugin extends AbstractPlugin {
|
|
@@ -764,19 +837,21 @@ declare abstract class AbstractScraper {
|
|
|
764
837
|
scrape(): Promise<RecipeData>;
|
|
765
838
|
/**
|
|
766
839
|
* Converts the scraper's data into a JSON-serializable object.
|
|
840
|
+
* Note: schemaVersion is added during validation by parse() or safeParse().
|
|
767
841
|
*/
|
|
768
|
-
toRecipeObject(): Promise<RecipeObject
|
|
842
|
+
toRecipeObject(): Promise<Omit<RecipeObject, 'schemaVersion'>>;
|
|
769
843
|
/**
|
|
770
844
|
* Get the Zod schema to use for validation.
|
|
771
845
|
* Subclasses can override to provide custom schemas.
|
|
772
846
|
*/
|
|
773
|
-
protected getSchema(): z$1.ZodPipe<z$1.
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
847
|
+
protected getSchema(): z$1.ZodPipe<z$1.ZodType<{
|
|
848
|
+
schemaVersion: "1.0.0";
|
|
849
|
+
host: string;
|
|
850
|
+
title: string;
|
|
851
|
+
author: string;
|
|
852
|
+
ingredients: {
|
|
853
|
+
name: string | null;
|
|
854
|
+
items: {
|
|
780
855
|
value: string;
|
|
781
856
|
parsed?: {
|
|
782
857
|
quantity: number | null;
|
|
@@ -786,7 +861,45 @@ declare abstract class AbstractScraper {
|
|
|
786
861
|
description: string;
|
|
787
862
|
isGroupHeader: boolean;
|
|
788
863
|
} | undefined;
|
|
789
|
-
}
|
|
864
|
+
}[];
|
|
865
|
+
}[];
|
|
866
|
+
instructions: {
|
|
867
|
+
name: string | null;
|
|
868
|
+
items: {
|
|
869
|
+
value: string;
|
|
870
|
+
}[];
|
|
871
|
+
}[];
|
|
872
|
+
canonicalUrl: string;
|
|
873
|
+
image: string;
|
|
874
|
+
totalTime: number | null;
|
|
875
|
+
cookTime: number | null;
|
|
876
|
+
prepTime: number | null;
|
|
877
|
+
ratings: number;
|
|
878
|
+
ratingsCount: number;
|
|
879
|
+
yields: string;
|
|
880
|
+
description: string;
|
|
881
|
+
language: string;
|
|
882
|
+
siteName: string | null;
|
|
883
|
+
cookingMethod: string | null;
|
|
884
|
+
category: string[];
|
|
885
|
+
cuisine: string[];
|
|
886
|
+
keywords: string[];
|
|
887
|
+
dietaryRestrictions: string[];
|
|
888
|
+
equipment: string[];
|
|
889
|
+
nutrients: Record<string, string>;
|
|
890
|
+
reviews: Record<string, string>;
|
|
891
|
+
links?: {
|
|
892
|
+
href: string;
|
|
893
|
+
text: string;
|
|
894
|
+
}[] | undefined;
|
|
895
|
+
}, unknown, z$1.core.$ZodTypeInternals<{
|
|
896
|
+
schemaVersion: "1.0.0";
|
|
897
|
+
host: string;
|
|
898
|
+
title: string;
|
|
899
|
+
author: string;
|
|
900
|
+
ingredients: {
|
|
901
|
+
name: string | null;
|
|
902
|
+
items: {
|
|
790
903
|
value: string;
|
|
791
904
|
parsed?: {
|
|
792
905
|
quantity: number | null;
|
|
@@ -796,40 +909,39 @@ declare abstract class AbstractScraper {
|
|
|
796
909
|
description: string;
|
|
797
910
|
isGroupHeader: boolean;
|
|
798
911
|
} | undefined;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
instructions:
|
|
802
|
-
name:
|
|
803
|
-
items:
|
|
804
|
-
value: string;
|
|
805
|
-
}, unknown, z$1.core.$ZodTypeInternals<{
|
|
912
|
+
}[];
|
|
913
|
+
}[];
|
|
914
|
+
instructions: {
|
|
915
|
+
name: string | null;
|
|
916
|
+
items: {
|
|
806
917
|
value: string;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
canonicalUrl:
|
|
810
|
-
image:
|
|
811
|
-
totalTime:
|
|
812
|
-
cookTime:
|
|
813
|
-
prepTime:
|
|
814
|
-
ratings:
|
|
815
|
-
ratingsCount:
|
|
816
|
-
yields:
|
|
817
|
-
description:
|
|
818
|
-
language:
|
|
819
|
-
siteName:
|
|
820
|
-
cookingMethod:
|
|
821
|
-
category:
|
|
822
|
-
cuisine:
|
|
823
|
-
keywords:
|
|
824
|
-
dietaryRestrictions:
|
|
825
|
-
equipment:
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
},
|
|
918
|
+
}[];
|
|
919
|
+
}[];
|
|
920
|
+
canonicalUrl: string;
|
|
921
|
+
image: string;
|
|
922
|
+
totalTime: number | null;
|
|
923
|
+
cookTime: number | null;
|
|
924
|
+
prepTime: number | null;
|
|
925
|
+
ratings: number;
|
|
926
|
+
ratingsCount: number;
|
|
927
|
+
yields: string;
|
|
928
|
+
description: string;
|
|
929
|
+
language: string;
|
|
930
|
+
siteName: string | null;
|
|
931
|
+
cookingMethod: string | null;
|
|
932
|
+
category: string[];
|
|
933
|
+
cuisine: string[];
|
|
934
|
+
keywords: string[];
|
|
935
|
+
dietaryRestrictions: string[];
|
|
936
|
+
equipment: string[];
|
|
937
|
+
nutrients: Record<string, string>;
|
|
938
|
+
reviews: Record<string, string>;
|
|
939
|
+
links?: {
|
|
940
|
+
href: string;
|
|
941
|
+
text: string;
|
|
942
|
+
}[] | undefined;
|
|
943
|
+
}, unknown>>, z$1.ZodTransform<{
|
|
944
|
+
schemaVersion: "1.0.0";
|
|
833
945
|
host: string;
|
|
834
946
|
title: string;
|
|
835
947
|
author: string;
|
|
@@ -870,13 +982,14 @@ declare abstract class AbstractScraper {
|
|
|
870
982
|
keywords: string[];
|
|
871
983
|
dietaryRestrictions: string[];
|
|
872
984
|
equipment: string[];
|
|
873
|
-
links: {
|
|
874
|
-
href: string;
|
|
875
|
-
text: string;
|
|
876
|
-
}[];
|
|
877
985
|
nutrients: Record<string, string>;
|
|
878
986
|
reviews: Record<string, string>;
|
|
987
|
+
links?: {
|
|
988
|
+
href: string;
|
|
989
|
+
text: string;
|
|
990
|
+
}[] | undefined;
|
|
879
991
|
}, {
|
|
992
|
+
schemaVersion: "1.0.0";
|
|
880
993
|
host: string;
|
|
881
994
|
title: string;
|
|
882
995
|
author: string;
|
|
@@ -917,12 +1030,12 @@ declare abstract class AbstractScraper {
|
|
|
917
1030
|
keywords: string[];
|
|
918
1031
|
dietaryRestrictions: string[];
|
|
919
1032
|
equipment: string[];
|
|
920
|
-
links: {
|
|
921
|
-
href: string;
|
|
922
|
-
text: string;
|
|
923
|
-
}[];
|
|
924
1033
|
nutrients: Record<string, string>;
|
|
925
1034
|
reviews: Record<string, string>;
|
|
1035
|
+
links?: {
|
|
1036
|
+
href: string;
|
|
1037
|
+
text: string;
|
|
1038
|
+
}[] | undefined;
|
|
926
1039
|
}>>;
|
|
927
1040
|
/**
|
|
928
1041
|
* Extract and validate recipe data.
|
|
@@ -931,14 +1044,14 @@ declare abstract class AbstractScraper {
|
|
|
931
1044
|
* @returns Validated recipe object
|
|
932
1045
|
* @throws {ZodError} If validation fails
|
|
933
1046
|
*/
|
|
934
|
-
parse(): Promise<
|
|
1047
|
+
parse(): Promise<RecipeObject>;
|
|
935
1048
|
/**
|
|
936
1049
|
* Extract and validate recipe data without throwing.
|
|
937
1050
|
* Returns a result object indicating success or failure.
|
|
938
1051
|
*
|
|
939
1052
|
* @returns Result object with either data or error
|
|
940
1053
|
*/
|
|
941
|
-
safeParse(): Promise<z$1.ZodSafeParseResult<
|
|
1054
|
+
safeParse(): Promise<z$1.ZodSafeParseResult<RecipeObject>>;
|
|
942
1055
|
}
|
|
943
1056
|
//#endregion
|
|
944
1057
|
//#region src/scrapers/allrecipes.d.ts
|
|
@@ -961,4 +1074,4 @@ declare const scrapers: {
|
|
|
961
1074
|
*/
|
|
962
1075
|
declare function getScraper(url: string): typeof AllRecipes;
|
|
963
1076
|
//#endregion
|
|
964
|
-
export { ExtractorPlugin, IngredientGroup, IngredientGroupSchema, IngredientItem, IngredientItemSchema, Ingredients, IngredientsSchema, InstructionGroup, InstructionGroupSchema, InstructionItem, InstructionItemSchema, Instructions, InstructionsSchema, Link, LinkSchema, List, LogLevel, Logger, OptionalRecipeFields, ParsedIngredient, ParsedIngredientSchema, PostProcessorPlugin, RecipeData, RecipeFields, RecipeObject, RecipeObjectBaseSchema, RecipeObjectSchema,
|
|
1077
|
+
export { ExtractorPlugin, IngredientGroup, IngredientGroupSchema, IngredientItem, IngredientItemSchema, Ingredients, IngredientsSchema, InstructionGroup, InstructionGroupSchema, InstructionItem, InstructionItemSchema, Instructions, InstructionsSchema, Link, LinkSchema, List, LogLevel, Logger, OptionalRecipeFields, ParsedIngredient, ParsedIngredientSchema, PostProcessorPlugin, RECIPE_SCHEMA_VERSION, RecipeData, RecipeFields, RecipeObject, RecipeObjectBaseSchema, RecipeObjectSchema, ScraperOptions, applyRecipeValidations, getScraper, scrapers };
|
package/dist/index.mjs
CHANGED
|
@@ -56,6 +56,14 @@ const zNonEmptyArray = (schema, fieldName) => z.array(schema, `${fieldName} item
|
|
|
56
56
|
//#endregion
|
|
57
57
|
//#region src/schemas/recipe.schema.ts
|
|
58
58
|
/**
|
|
59
|
+
* Current schema version for recipe objects.
|
|
60
|
+
* Increment this when making breaking changes to the schema.
|
|
61
|
+
*
|
|
62
|
+
* Version history:
|
|
63
|
+
* - 1.0.0: Initial schema version
|
|
64
|
+
*/
|
|
65
|
+
const RECIPE_SCHEMA_VERSION = "1.0.0";
|
|
66
|
+
/**
|
|
59
67
|
* Schema for a parsed ingredient from the parse-ingredient library.
|
|
60
68
|
* This represents the structured data extracted from an ingredient string.
|
|
61
69
|
* @see https://github.com/jakeboone02/parse-ingredient
|
|
@@ -111,10 +119,23 @@ const LinkSchema = z.object({
|
|
|
111
119
|
text: zString("Link text")
|
|
112
120
|
});
|
|
113
121
|
/**
|
|
114
|
-
* Base RecipeObject schema without cross-field validations
|
|
115
|
-
*
|
|
122
|
+
* Base RecipeObject schema without cross-field validations.
|
|
123
|
+
* Use this schema when you need to extend the recipe object with custom fields.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* import { RecipeObjectBaseSchema, applyRecipeValidations } from 'recipe-scrapers-js'
|
|
128
|
+
*
|
|
129
|
+
* const MyCustomRecipeSchema = RecipeObjectBaseSchema.extend({
|
|
130
|
+
* customField: z.string(),
|
|
131
|
+
* })
|
|
132
|
+
*
|
|
133
|
+
* // Apply the standard recipe validations
|
|
134
|
+
* const MyValidatedRecipeSchema = applyRecipeValidations(MyCustomRecipeSchema)
|
|
135
|
+
* ```
|
|
116
136
|
*/
|
|
117
137
|
const RecipeObjectBaseSchema = z.object({
|
|
138
|
+
schemaVersion: z.literal(RECIPE_SCHEMA_VERSION).default(RECIPE_SCHEMA_VERSION).describe("Schema version for recipe data migrations"),
|
|
118
139
|
host: z.hostname("Host must be a valid hostname"),
|
|
119
140
|
title: zString("Title", { max: 500 }),
|
|
120
141
|
author: zString("Author", { max: 255 }),
|
|
@@ -137,27 +158,52 @@ const RecipeObjectBaseSchema = z.object({
|
|
|
137
158
|
keywords: z.array(zString("Keyword item"), "Keywords must be an array").default([]),
|
|
138
159
|
dietaryRestrictions: z.array(zString("Dietary restriction item"), "Dietary restrictions must be an array").default([]),
|
|
139
160
|
equipment: z.array(zString("Equipment item"), "Equipment must be an array").default([]),
|
|
140
|
-
links: z.array(LinkSchema, "Links must be an array").
|
|
161
|
+
links: z.array(LinkSchema, "Links must be an array").optional(),
|
|
141
162
|
nutrients: z.record(z.string(), z.string(), "Nutrients must be an object").default({}),
|
|
142
163
|
reviews: z.record(z.string(), z.string(), "Reviews must be an object").default({})
|
|
143
164
|
});
|
|
144
165
|
/**
|
|
145
|
-
*
|
|
166
|
+
* Applies recipe-specific transformations and validations to a schema.
|
|
167
|
+
* Use this when extending RecipeObjectBaseSchema with custom fields.
|
|
168
|
+
*
|
|
169
|
+
* @param schema - A Zod object schema that includes
|
|
170
|
+
* all RecipeObjectBaseSchema fields
|
|
171
|
+
* @returns A schema with transforms and field validations applied
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* const CustomSchema = RecipeObjectBaseSchema.extend({
|
|
176
|
+
* tags: z.array(z.string()),
|
|
177
|
+
* })
|
|
178
|
+
*
|
|
179
|
+
* const ValidatedCustomSchema = applyRecipeValidations(CustomSchema)
|
|
180
|
+
* ```
|
|
146
181
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}, {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}).refine((data) =>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
function applyRecipeValidations(schema) {
|
|
183
|
+
return schema.transform((data) => {
|
|
184
|
+
if (!data.totalTime && !isNull(data.cookTime) && !isNull(data.prepTime)) data.totalTime = data.cookTime + data.prepTime;
|
|
185
|
+
return data;
|
|
186
|
+
}).refine(({ totalTime, cookTime, prepTime }) => {
|
|
187
|
+
if (!isNull(totalTime) && !isNull(cookTime) && !isNull(prepTime)) return totalTime >= cookTime + prepTime;
|
|
188
|
+
return true;
|
|
189
|
+
}, {
|
|
190
|
+
message: "Total time should be at least the sum of cook time and prep time",
|
|
191
|
+
path: ["totalTime"]
|
|
192
|
+
}).refine((data) => {
|
|
193
|
+
return data.ratings === 0 || data.ratingsCount > 0;
|
|
194
|
+
}, {
|
|
195
|
+
message: "Ratings count should be greater than 0 when ratings exist",
|
|
196
|
+
path: ["ratingsCount"]
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Strict RecipeObject schema with all validations enforced.
|
|
201
|
+
* This is the standard schema used by recipe scrapers.
|
|
202
|
+
*
|
|
203
|
+
* For custom extensions, use RecipeObjectBaseSchema.extend() and then
|
|
204
|
+
* apply validations with applyRecipeValidations().
|
|
205
|
+
*/
|
|
206
|
+
const RecipeObjectSchema = applyRecipeValidations(RecipeObjectBaseSchema);
|
|
161
207
|
|
|
162
208
|
//#endregion
|
|
163
209
|
//#region src/exceptions/index.ts
|
|
@@ -1235,7 +1281,7 @@ var AbstractScraper = class {
|
|
|
1235
1281
|
return "en";
|
|
1236
1282
|
}
|
|
1237
1283
|
links() {
|
|
1238
|
-
if (!this.options.linksEnabled) return
|
|
1284
|
+
if (!this.options.linksEnabled) return void 0;
|
|
1239
1285
|
return this.$("a[href]").map((_, el) => {
|
|
1240
1286
|
const href = this.$(el).attr("href");
|
|
1241
1287
|
if (!href?.startsWith("http")) return null;
|
|
@@ -1282,6 +1328,7 @@ var AbstractScraper = class {
|
|
|
1282
1328
|
}
|
|
1283
1329
|
/**
|
|
1284
1330
|
* Converts the scraper's data into a JSON-serializable object.
|
|
1331
|
+
* Note: schemaVersion is added during validation by parse() or safeParse().
|
|
1285
1332
|
*/
|
|
1286
1333
|
async toRecipeObject() {
|
|
1287
1334
|
const { category, cuisine, dietaryRestrictions, equipment, keywords, nutrients, reviews, ...rest } = await this.scrape();
|
|
@@ -1600,4 +1647,4 @@ function getScraper(url) {
|
|
|
1600
1647
|
}
|
|
1601
1648
|
|
|
1602
1649
|
//#endregion
|
|
1603
|
-
export { ExtractorPlugin, IngredientGroupSchema, IngredientItemSchema, IngredientsSchema, InstructionGroupSchema, InstructionItemSchema, InstructionsSchema, LinkSchema, LogLevel, Logger, ParsedIngredientSchema, PostProcessorPlugin, RecipeObjectBaseSchema, RecipeObjectSchema, getScraper, scrapers };
|
|
1650
|
+
export { ExtractorPlugin, IngredientGroupSchema, IngredientItemSchema, IngredientsSchema, InstructionGroupSchema, InstructionItemSchema, InstructionsSchema, LinkSchema, LogLevel, Logger, ParsedIngredientSchema, PostProcessorPlugin, RECIPE_SCHEMA_VERSION, RecipeObjectBaseSchema, RecipeObjectSchema, applyRecipeValidations, getScraper, scrapers };
|