@recipe-book/core 0.0.3 → 0.1.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 (185) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/LICENSE +21 -0
  3. package/README.md +50 -7
  4. package/dist/chunk-D5CV3UF7.cjs +2754 -0
  5. package/dist/chunk-D5CV3UF7.cjs.map +1 -0
  6. package/dist/errors.js +76 -0
  7. package/dist/errors.js.map +1 -0
  8. package/dist/format/duration.js +36 -0
  9. package/dist/format/duration.js.map +1 -0
  10. package/dist/format/ingredient.js +49 -0
  11. package/dist/format/ingredient.js.map +1 -0
  12. package/dist/format/number.js +120 -0
  13. package/dist/format/number.js.map +1 -0
  14. package/dist/format/options.js +31 -0
  15. package/dist/format/options.js.map +1 -0
  16. package/dist/format/quantity.js +144 -0
  17. package/dist/format/quantity.js.map +1 -0
  18. package/dist/format/servings.js +68 -0
  19. package/dist/format/servings.js.map +1 -0
  20. package/dist/format/temperature.js +35 -0
  21. package/dist/format/temperature.js.map +1 -0
  22. package/dist/index.cjs +476 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +994 -0
  25. package/dist/index.d.ts +994 -5
  26. package/dist/index.js +48 -1
  27. package/dist/index.js.map +1 -0
  28. package/dist/ingredient/parse.js +412 -0
  29. package/dist/ingredient/parse.js.map +1 -0
  30. package/dist/issues.js +26 -0
  31. package/dist/issues.js.map +1 -0
  32. package/dist/quantity/amount.js +78 -0
  33. package/dist/quantity/amount.js.map +1 -0
  34. package/dist/quantity/parse.js +255 -0
  35. package/dist/quantity/parse.js.map +1 -0
  36. package/dist/quantity/scale.js +23 -0
  37. package/dist/quantity/scale.js.map +1 -0
  38. package/dist/recipe/duration.js +28 -0
  39. package/dist/recipe/duration.js.map +1 -0
  40. package/dist/recipe/ids.js +68 -0
  41. package/dist/recipe/ids.js.map +1 -0
  42. package/dist/recipe/infer.js +24 -0
  43. package/dist/recipe/infer.js.map +1 -0
  44. package/dist/recipe/nutrient-units.js +18 -0
  45. package/dist/recipe/nutrient-units.js.map +1 -0
  46. package/dist/recipe/nutrition.js +26 -0
  47. package/dist/recipe/nutrition.js.map +1 -0
  48. package/dist/recipe/parse.js +854 -0
  49. package/dist/recipe/parse.js.map +1 -0
  50. package/dist/recipe/schema.js +32 -0
  51. package/dist/recipe/schema.js.map +1 -0
  52. package/dist/recipe/url.js +9 -0
  53. package/dist/recipe/url.js.map +1 -0
  54. package/dist/resolve.js +120 -0
  55. package/dist/resolve.js.map +1 -0
  56. package/dist/schema-org/find.js +158 -0
  57. package/dist/schema-org/find.js.map +1 -0
  58. package/dist/schema-org/from-json-ld.js +647 -0
  59. package/dist/schema-org/from-json-ld.js.map +1 -0
  60. package/dist/schema-org/nutrients.js +19 -0
  61. package/dist/schema-org/nutrients.js.map +1 -0
  62. package/dist/schema-org/text.js +125 -0
  63. package/dist/schema-org/text.js.map +1 -0
  64. package/dist/schema-org/to-json-ld.js +151 -0
  65. package/dist/schema-org/to-json-ld.js.map +1 -0
  66. package/dist/schema-org.cjs +1062 -0
  67. package/dist/schema-org.cjs.map +1 -0
  68. package/dist/schema-org.d.cts +223 -0
  69. package/dist/schema-org.d.ts +223 -0
  70. package/dist/schema-org.js +10 -0
  71. package/dist/schema-org.js.map +1 -0
  72. package/dist/steps/segment.js +45 -0
  73. package/dist/steps/segment.js.map +1 -0
  74. package/dist/steps/temperature.js +174 -0
  75. package/dist/steps/temperature.js.map +1 -0
  76. package/dist/suggest.js +50 -0
  77. package/dist/suggest.js.map +1 -0
  78. package/dist/types-BknM8KOd.d.cts +628 -0
  79. package/dist/types-BknM8KOd.d.ts +628 -0
  80. package/dist/units/convert.js +79 -0
  81. package/dist/units/convert.js.map +1 -0
  82. package/dist/units/lookup.js +86 -0
  83. package/dist/units/lookup.js.map +1 -0
  84. package/dist/units/registry.js +183 -0
  85. package/dist/units/registry.js.map +1 -0
  86. package/dist/units/system.js +91 -0
  87. package/dist/units/system.js.map +1 -0
  88. package/dist/units/temperature.js +40 -0
  89. package/dist/units/temperature.js.map +1 -0
  90. package/dist/validation.js +90 -0
  91. package/dist/validation.js.map +1 -0
  92. package/package.json +53 -8
  93. package/schema-org/package.json +5 -0
  94. package/dist/chunk-22RBQQWH.mjs +0 -1
  95. package/dist/chunk-3CSP4UZG.mjs +0 -1
  96. package/dist/chunk-3FFMHRB7.mjs +0 -26
  97. package/dist/chunk-4EB3HG6B.mjs +0 -50
  98. package/dist/chunk-4EHNJLGI.mjs +0 -50
  99. package/dist/chunk-4IA5EEKI.mjs +0 -81
  100. package/dist/chunk-4TATADPT.mjs +0 -51
  101. package/dist/chunk-6GI5KLI4.mjs +0 -22
  102. package/dist/chunk-6QJZOZ6W.mjs +0 -49
  103. package/dist/chunk-6YYQ4RYZ.mjs +0 -26
  104. package/dist/chunk-7BBUDI2L.mjs +0 -0
  105. package/dist/chunk-7KEMNCV7.mjs +0 -26
  106. package/dist/chunk-7MMJZZ7Z.mjs +0 -75
  107. package/dist/chunk-BES4V7DM.mjs +0 -26
  108. package/dist/chunk-BRDXRIAH.mjs +0 -50
  109. package/dist/chunk-C45UD45A.mjs +0 -1
  110. package/dist/chunk-CBLJXA6P.mjs +0 -1
  111. package/dist/chunk-CFR2ZBPB.mjs +0 -77
  112. package/dist/chunk-DYMNVFL2.mjs +0 -78
  113. package/dist/chunk-F6AQKXJU.mjs +0 -51
  114. package/dist/chunk-FW4M2R43.mjs +0 -51
  115. package/dist/chunk-GCCEDUST.mjs +0 -75
  116. package/dist/chunk-GE7KBYZR.mjs +0 -26
  117. package/dist/chunk-GN7ZDGYC.mjs +0 -51
  118. package/dist/chunk-GOO2SCQX.mjs +0 -75
  119. package/dist/chunk-HAVKBQLH.mjs +0 -51
  120. package/dist/chunk-HCBIPANU.mjs +0 -50
  121. package/dist/chunk-IEBAGBS7.mjs +0 -51
  122. package/dist/chunk-IQ6WN2V4.mjs +0 -75
  123. package/dist/chunk-JB4Z2FR7.mjs +0 -77
  124. package/dist/chunk-JMTQOTBM.mjs +0 -75
  125. package/dist/chunk-JRO6JUES.mjs +0 -51
  126. package/dist/chunk-KH3SRXET.mjs +0 -52
  127. package/dist/chunk-KNZMCAQZ.mjs +0 -51
  128. package/dist/chunk-KXFUCO3R.mjs +0 -1
  129. package/dist/chunk-LEBGCVCJ.mjs +0 -75
  130. package/dist/chunk-MAHWFCIL.mjs +0 -51
  131. package/dist/chunk-MPH76SLC.mjs +0 -51
  132. package/dist/chunk-MQGMFBJQ.mjs +0 -26
  133. package/dist/chunk-NMIVG7T4.mjs +0 -6
  134. package/dist/chunk-Q3TT3AZ4.mjs +0 -26
  135. package/dist/chunk-QY5HJQIL.mjs +0 -51
  136. package/dist/chunk-RCPLSNKM.mjs +0 -1
  137. package/dist/chunk-SR2KFBT3.mjs +0 -1
  138. package/dist/chunk-ULDLBAEG.mjs +0 -1
  139. package/dist/chunk-UQ3EUYFR.mjs +0 -1
  140. package/dist/chunk-V26OCCTL.mjs +0 -82
  141. package/dist/chunk-WBQAMGXK.mjs +0 -0
  142. package/dist/chunk-WSW6FMUL.mjs +0 -51
  143. package/dist/chunk-WU55SBIV.mjs +0 -75
  144. package/dist/chunk-XINK45P3.mjs +0 -51
  145. package/dist/chunk-XNOHIWI3.mjs +0 -1
  146. package/dist/chunk-XV4QN4LA.mjs +0 -6
  147. package/dist/chunk-YHBXEVUX.mjs +0 -51
  148. package/dist/chunk-YSZSCEI7.mjs +0 -75
  149. package/dist/chunk-YY3SUQ7P.mjs +0 -16
  150. package/dist/chunk-YZB3IG47.mjs +0 -80
  151. package/dist/chunk-ZPZT4LMQ.mjs +0 -25
  152. package/dist/const/conversion.d.mts +0 -7
  153. package/dist/const/conversion.d.ts +0 -7
  154. package/dist/const/conversion.js +0 -1
  155. package/dist/const/conversion.mjs +0 -1
  156. package/dist/const/unit.d.mts +0 -119
  157. package/dist/const/unit.d.ts +0 -119
  158. package/dist/const/unit.js +0 -1
  159. package/dist/const/unit.mjs +0 -1
  160. package/dist/index.d.mts +0 -5
  161. package/dist/index.mjs +0 -1
  162. package/dist/lib/assert.d.mts +0 -18
  163. package/dist/lib/assert.d.ts +0 -18
  164. package/dist/lib/assert.js +0 -1
  165. package/dist/lib/assert.mjs +0 -1
  166. package/dist/lib/ingredient.d.mts +0 -9
  167. package/dist/lib/ingredient.d.ts +0 -9
  168. package/dist/lib/ingredient.js +0 -1
  169. package/dist/lib/ingredient.mjs +0 -1
  170. package/dist/lib/recipe.d.mts +0 -12
  171. package/dist/lib/recipe.d.ts +0 -12
  172. package/dist/lib/recipe.js +0 -1
  173. package/dist/lib/recipe.mjs +0 -1
  174. package/dist/lib/runtime.d.mts +0 -3
  175. package/dist/lib/runtime.d.ts +0 -3
  176. package/dist/lib/runtime.js +0 -1
  177. package/dist/lib/runtime.mjs +0 -1
  178. package/dist/lib/unit.d.mts +0 -19
  179. package/dist/lib/unit.d.ts +0 -19
  180. package/dist/lib/unit.js +0 -1
  181. package/dist/lib/unit.mjs +0 -1
  182. package/dist/types.d.mts +0 -36
  183. package/dist/types.d.ts +0 -36
  184. package/dist/types.js +0 -1
  185. package/dist/types.mjs +0 -1
@@ -0,0 +1,647 @@
1
+ // src/schema-org/from-json-ld.ts
2
+ import {
3
+ describeValue,
4
+ quote
5
+ } from "../errors.js";
6
+ import { parseIngredientLine } from "../ingredient/parse.js";
7
+ import { issue, warning } from "../issues.js";
8
+ import { matchAmount } from "../quantity/parse.js";
9
+ import { parseIsoDuration } from "../recipe/duration.js";
10
+ import { parseRecipeWith } from "../recipe/parse.js";
11
+ import { disallowedScheme } from "../recipe/url.js";
12
+ import { asRecord, toResult } from "../validation.js";
13
+ import { entriesOf, locate, MAX_DEPTH } from "./find.js";
14
+ import { NUTRIENTS } from "./nutrients.js";
15
+ import { cleanLines, cleanText } from "./text.js";
16
+ var READ_FIELDS = [
17
+ "@context",
18
+ "@type",
19
+ "name",
20
+ "description",
21
+ "image",
22
+ "recipeYield",
23
+ "prepTime",
24
+ "cookTime",
25
+ "totalTime",
26
+ "recipeIngredient",
27
+ "recipeInstructions",
28
+ "tool",
29
+ "nutrition"
30
+ ];
31
+ var TIMES = [
32
+ ["prep", "prepTime"],
33
+ ["cook", "cookTime"],
34
+ ["total", "totalTime"]
35
+ ];
36
+ var MAX_URL_LENGTH = 4096;
37
+ var SERVINGS_WORD = /^(?:servings?|portions?|(?:serving|portion)\(s\)|people|persons?)\.?$/i;
38
+ function fromJsonLd(input) {
39
+ const { recipes, ids } = locate([input]);
40
+ const [found] = recipes;
41
+ if (!found) {
42
+ return {
43
+ ...toResult("invalid_recipe", void 0, [
44
+ issue(
45
+ "error",
46
+ "invalid_type",
47
+ [],
48
+ `Expected a schema.org Recipe, or an array, @graph, mainEntity, or ItemList that holds one, received ${describeValue(input)}.`
49
+ )
50
+ ]),
51
+ extra: {}
52
+ };
53
+ }
54
+ const node = asRecord(found.node);
55
+ const reader = {
56
+ recipe: found.node,
57
+ ids,
58
+ warnings: [],
59
+ sources: /* @__PURE__ */ new Map(),
60
+ droppable: /* @__PURE__ */ new Set(),
61
+ images: [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()],
62
+ repeated: 0,
63
+ deepest: 0
64
+ };
65
+ const name = text(node.name);
66
+ if (recipes.length > 1) {
67
+ warning(
68
+ reader.warnings,
69
+ "ignored_value",
70
+ [],
71
+ `Found ${recipes.length} recipes. Only the first${typeof name === "string" ? `, ${quote(name)},` : ""} is read.`
72
+ );
73
+ }
74
+ const model = readRecipe(reader, node, found.path);
75
+ const parse = () => parseRecipeWith(model, parseIngredientLine);
76
+ let result = parse();
77
+ const dropped = [];
78
+ if (!result.success) {
79
+ const targets = [];
80
+ for (const error of result.error.issues) {
81
+ const target = error.severity === "error" ? droppableAt(reader, error.path) : void 0;
82
+ if (target) {
83
+ targets.push(target);
84
+ dropped.push({
85
+ ...error,
86
+ severity: "warning",
87
+ message: `${error.message} It is ignored.`
88
+ });
89
+ }
90
+ }
91
+ targets.sort((a, b) => b.length - a.length).forEach((target) => remove(model, target));
92
+ if (dropped.length > 0) {
93
+ result = parse();
94
+ }
95
+ }
96
+ const order = ({ code, path }) => [
97
+ path.length > found.path.length ? READ_FIELDS.indexOf(String(path[found.path.length])) : -1,
98
+ ...path.slice(found.path.length + 1).filter((key) => typeof key === "number"),
99
+ ...code === "unknown_field" ? [Infinity] : []
100
+ ];
101
+ const issues = [
102
+ ...reader.warnings,
103
+ ...[
104
+ ...dropped,
105
+ ...result.success ? result.warnings : result.error.issues
106
+ ].map((issue2) => ({ ...issue2, path: sourceOf(reader, issue2.path) }))
107
+ ].sort((a, b) => compareOrder(order(a), order(b)));
108
+ const checked = /* @__PURE__ */ new Map();
109
+ const extra = Object.fromEntries(
110
+ Object.entries(node).filter(([key, value]) => {
111
+ if (READ_FIELDS.includes(key)) {
112
+ return false;
113
+ }
114
+ const path = [...found.path, key];
115
+ if (tooDeep(value, path.length, checked)) {
116
+ warning(
117
+ issues,
118
+ "invalid_type",
119
+ path,
120
+ `Expected a value to keep in extra, received ${describeValue(value)} nested more than ${MAX_DEPTH} levels deep. It is ignored.`
121
+ );
122
+ return false;
123
+ }
124
+ return true;
125
+ })
126
+ );
127
+ return {
128
+ ...toResult(
129
+ "invalid_recipe",
130
+ result.success ? result.data : void 0,
131
+ issues,
132
+ typeof name === "string" ? quote(name) : void 0
133
+ ),
134
+ extra
135
+ };
136
+ }
137
+ function tooDeep(value, length, checked) {
138
+ if (length > MAX_DEPTH) {
139
+ return true;
140
+ }
141
+ if (typeof value !== "object" || value === null || (checked.get(value) ?? 0) >= length) {
142
+ return false;
143
+ }
144
+ const deep = Object.values(value).some(
145
+ (child) => tooDeep(child, length + 1, checked)
146
+ );
147
+ if (!deep) {
148
+ checked.set(value, length);
149
+ }
150
+ return deep;
151
+ }
152
+ function readRecipe(reader, node, base) {
153
+ const at = (...path) => [...base, ...path];
154
+ const model = {
155
+ name: text(node.name),
156
+ description: text(node.description)
157
+ };
158
+ link(reader, [], base);
159
+ link(reader, ["name"], at("name"));
160
+ link(reader, ["description"], at("description"), true);
161
+ const image = readImage(reader, node.image, at("image"));
162
+ if (image) {
163
+ model.image = image.url;
164
+ link(reader, ["image"], image.source, true);
165
+ }
166
+ readYield(reader, model, node.recipeYield, at("recipeYield"));
167
+ const times = {};
168
+ for (const [key, field] of TIMES) {
169
+ const minutes = readTime(reader, node[field], at(field));
170
+ if (minutes !== void 0) {
171
+ times[key] = minutes;
172
+ }
173
+ }
174
+ model.times = times;
175
+ model.ingredients = readList(
176
+ reader,
177
+ "ingredients",
178
+ node.recipeIngredient,
179
+ at("recipeIngredient"),
180
+ readLine,
181
+ 'an ingredient line such as "2 cups flour"'
182
+ );
183
+ model.steps = readList(
184
+ reader,
185
+ "steps",
186
+ node.recipeInstructions,
187
+ at("recipeInstructions"),
188
+ readStep,
189
+ 'a step such as "Whisk the eggs." or a HowToStep'
190
+ );
191
+ model.equipment = readTools(reader, node.tool, at("tool"));
192
+ model.nutrition = readNutrition(reader, node.nutrition, at("nutrition"));
193
+ return model;
194
+ }
195
+ function readImage(reader, value, source) {
196
+ if (isAbsent(value)) {
197
+ return void 0;
198
+ }
199
+ const warnings = reader.warnings.length + reader.repeated;
200
+ const image = findImage(reader, value, source);
201
+ if (!image && reader.warnings.length + reader.repeated === warnings) {
202
+ ignore(
203
+ reader,
204
+ "invalid_type",
205
+ source,
206
+ `Expected an image URL, an ImageObject with a url, or a list of them, received ${describeValue(value)}.`
207
+ );
208
+ }
209
+ return image;
210
+ }
211
+ function findImage(reader, value, source, referenced = false) {
212
+ reader.deepest = Math.max(reader.deepest, source.length);
213
+ if (source.length > MAX_DEPTH) {
214
+ if (!isAbsent(value)) {
215
+ ignore(
216
+ reader,
217
+ "invalid_type",
218
+ source,
219
+ `Expected an image URL, an ImageObject with a url, or a list of them, received ${describeValue(value)} nested more than ${MAX_DEPTH} levels deep.`
220
+ );
221
+ }
222
+ return void 0;
223
+ }
224
+ if (typeof value === "string") {
225
+ const url = cleanText(value);
226
+ const scheme = disallowedScheme(url);
227
+ if (scheme) {
228
+ ignore(
229
+ reader,
230
+ "invalid_url",
231
+ source,
232
+ `Expected an http or https URL, or a relative one, received ${quote(`${scheme}\u2026`)}.`
233
+ );
234
+ return void 0;
235
+ }
236
+ if (url.length > MAX_URL_LENGTH) {
237
+ ignore(
238
+ reader,
239
+ "invalid_url",
240
+ source,
241
+ `Expected a URL of at most ${MAX_URL_LENGTH} UTF-16 code units, received ${url.length}.`
242
+ );
243
+ return void 0;
244
+ }
245
+ return url ? { url, source } : void 0;
246
+ }
247
+ if (Array.isArray(value)) {
248
+ for (const [entry, entrySource] of entriesOf(value, source)) {
249
+ const image2 = findImage(reader, entry, entrySource, referenced);
250
+ if (image2) {
251
+ return image2;
252
+ }
253
+ }
254
+ return void 0;
255
+ }
256
+ const images = reader.images[referenced ? 1 : 0];
257
+ const reads = images.get(value) ?? [];
258
+ const read = reads.find(
259
+ ([, , depth, length]) => length === source.length || Math.max(length, source.length) + depth <= MAX_DEPTH
260
+ );
261
+ if (read) {
262
+ reader.repeated += read[1];
263
+ reader.deepest = Math.max(reader.deepest, source.length + read[2]);
264
+ return read[0];
265
+ }
266
+ const record = asRecord(value);
267
+ if (!record) {
268
+ return void 0;
269
+ }
270
+ images.set(value, reads);
271
+ const warnings = reader.warnings.length + reader.repeated;
272
+ const deepest = reader.deepest;
273
+ reader.deepest = source.length;
274
+ const id = record["@id"];
275
+ const target = typeof id === "string" && !referenced ? reader.ids.get(id) : void 0;
276
+ let image = findImage(reader, record.url, [...source, "url"], referenced) ?? findImage(reader, record.contentUrl, [...source, "contentUrl"], referenced);
277
+ if (!image && target?.node === reader.recipe) {
278
+ ignore(
279
+ reader,
280
+ "invalid_type",
281
+ source,
282
+ "Expected an image URL, an ImageObject with a url, or a list of them, received a reference to the recipe itself."
283
+ );
284
+ } else if (!image && target) {
285
+ image = findImage(reader, target.node, target.path, true);
286
+ }
287
+ reads.push([
288
+ image,
289
+ reader.warnings.length + reader.repeated - warnings,
290
+ reader.deepest - source.length,
291
+ source.length
292
+ ]);
293
+ reader.deepest = Math.max(reader.deepest, deepest);
294
+ return image;
295
+ }
296
+ function readYield(reader, model, value, source) {
297
+ const batches = entriesOf(value, source).flatMap(
298
+ ([entry, entrySource]) => (typeof entry === "string" ? entry.split(/,\s+/) : [entry]).map(
299
+ (part) => readBatch(reader, part, entrySource)
300
+ )
301
+ ).filter((batch) => batch !== void 0);
302
+ const yields = batches.filter((batch) => batch.yield !== void 0);
303
+ const servings = batches.filter((batch) => batch.servings !== void 0);
304
+ const kept = yields.find((batch) => !batch.article) ?? yields.find((batch) => batch.article);
305
+ const keptServings = servings.find((batch) => batch.worded) ?? servings.find((batch) => batch.amount !== kept?.amount);
306
+ for (const batch of servings) {
307
+ if (keptServings && batch.amount !== keptServings.amount && (batch.worded || batch.amount !== kept?.amount)) {
308
+ warning(
309
+ reader.warnings,
310
+ "ignored_value",
311
+ batch.source,
312
+ `Only one number of servings is read, ${quote(keptServings.label)}, so this one is ignored.`
313
+ );
314
+ }
315
+ }
316
+ for (const batch of yields) {
317
+ if (batch !== kept) {
318
+ warning(
319
+ reader.warnings,
320
+ "ignored_value",
321
+ batch.source,
322
+ `Only one yield is read, ${quote(kept.label)}, so this one is ignored.`
323
+ );
324
+ }
325
+ }
326
+ if (keptServings) {
327
+ model.servings = keptServings.servings;
328
+ link(reader, ["servings"], keptServings.source, true);
329
+ }
330
+ if (kept) {
331
+ model.yield = kept.yield;
332
+ link(reader, ["yield"], kept.source, true);
333
+ }
334
+ }
335
+ function readBatch(reader, value, source) {
336
+ if (typeof value === "number") {
337
+ return { source, label: String(value), amount: value, servings: value };
338
+ }
339
+ if (typeof value === "string") {
340
+ const label = cleanText(value);
341
+ if (!label) {
342
+ return void 0;
343
+ }
344
+ const text2 = label.replace(
345
+ /^(?:(?:serves|servings?|yields?|makes)\s*:?\s*)?(?:about\s+)?/i,
346
+ ""
347
+ );
348
+ const match = matchAmount(text2);
349
+ if (!match || "code" in match) {
350
+ ignore(
351
+ reader,
352
+ match?.code ?? "invalid_quantity",
353
+ source,
354
+ match?.message ?? `Expected a batch such as "24 cookies" or "3\u20134 loaves", received ${describeValue(label)}.`
355
+ );
356
+ return void 0;
357
+ }
358
+ const amount2 = typeof match.amount === "number" ? match.amount : match.amount.min;
359
+ const rest = match.rest.trim();
360
+ return !rest || SERVINGS_WORD.test(rest) ? { source, label, amount: amount2, servings: match.amountText, worded: !!rest } : { source, label, amount: amount2, yield: text2, article: /^an?\s/i.test(text2) };
361
+ }
362
+ const record = asRecord(value);
363
+ const amount = toNumber(record?.value ?? record?.minValue);
364
+ if (record && amount !== void 0) {
365
+ const max = toNumber(record.maxValue);
366
+ const unit = typeof record.unitText === "string" ? cleanText(record.unitText) : "";
367
+ const range = {
368
+ amount,
369
+ ...max !== void 0 && max !== amount && { max }
370
+ };
371
+ const label = `${amount}${max !== amount && max ? `\u2013${max}` : ""}${unit && ` ${unit}`}`;
372
+ return !unit || SERVINGS_WORD.test(unit) ? { source, label, amount, servings: range, worded: !!unit } : { source, label, amount, yield: { ...range, unit } };
373
+ }
374
+ if (!isAbsent(value)) {
375
+ ignore(
376
+ reader,
377
+ "invalid_type",
378
+ source,
379
+ `Expected a yield such as "4 servings" or "24 cookies", received ${describeValue(value)}.`
380
+ );
381
+ }
382
+ return void 0;
383
+ }
384
+ function readTime(reader, value, source) {
385
+ if (isAbsent(value)) {
386
+ return void 0;
387
+ }
388
+ const minutes = typeof value === "string" ? parseIsoDuration(cleanText(value)) : void 0;
389
+ if (minutes === void 0 || minutes > Number.MAX_SAFE_INTEGER) {
390
+ ignore(
391
+ reader,
392
+ "invalid_duration",
393
+ source,
394
+ `Expected an ISO 8601 duration such as "PT30M", received ${describeValue(value)}${minutes === void 0 ? "" : ", which is too long"}.`
395
+ );
396
+ return void 0;
397
+ }
398
+ return minutes > 0 ? minutes : void 0;
399
+ }
400
+ function readList(reader, key, value, source, readItem, expected) {
401
+ const list = [];
402
+ link(reader, [key], source);
403
+ const add = (items, path, entry, entrySource, inSection) => {
404
+ if (isAbsent(entry)) {
405
+ return;
406
+ }
407
+ if (entrySource.length > MAX_DEPTH) {
408
+ ignore(
409
+ reader,
410
+ "invalid_type",
411
+ entrySource,
412
+ `Expected ${expected}, received ${describeValue(entry)} nested more than ${MAX_DEPTH} levels deep.`
413
+ );
414
+ return;
415
+ }
416
+ if (Array.isArray(entry)) {
417
+ const elements2 = entriesOf(entry, entrySource);
418
+ const positions = elements2.map(
419
+ ([element]) => toNumber(asRecord(element)?.position)
420
+ );
421
+ const order = [...elements2.keys()];
422
+ if (!positions.includes(void 0)) {
423
+ order.sort((a, b) => positions[a] - positions[b]);
424
+ }
425
+ for (const i of order) {
426
+ const [element, elementSource] = elements2[i];
427
+ add(items, path, element, elementSource, inSection);
428
+ }
429
+ return;
430
+ }
431
+ const record = asRecord(entry);
432
+ const elements = [...entrySource, "itemListElement"];
433
+ if (record?.itemListElement !== void 0 && record.text === void 0) {
434
+ const title = typeof record.name === "string" ? cleanText(record.name) : "";
435
+ if (title && !inSection) {
436
+ const section = { title, items: [] };
437
+ const sectionPath = [key, list.length];
438
+ list.push(section);
439
+ link(reader, sectionPath, entrySource);
440
+ link(reader, [...sectionPath, "title"], [...entrySource, "name"]);
441
+ add(
442
+ section.items,
443
+ [...sectionPath, "items"],
444
+ record.itemListElement,
445
+ elements,
446
+ true
447
+ );
448
+ } else {
449
+ add(items, path, record.itemListElement, elements, inSection);
450
+ }
451
+ return;
452
+ }
453
+ if (record && !isAbsent(record.item)) {
454
+ add(items, path, record.item, [...entrySource, "item"], inSection);
455
+ return;
456
+ }
457
+ const read = readItem(reader, entry, entrySource);
458
+ if (!read) {
459
+ ignore(
460
+ reader,
461
+ "invalid_type",
462
+ entrySource,
463
+ `Expected ${expected}, received ${describeValue(entry)}.`
464
+ );
465
+ return;
466
+ }
467
+ for (const item of read) {
468
+ const itemPath = [...path, items.length];
469
+ items.push(item.value);
470
+ link(reader, itemPath, item.source, true);
471
+ for (const [field, fieldSource, droppable] of item.fields) {
472
+ link(reader, [...itemPath, field], fieldSource, droppable);
473
+ }
474
+ }
475
+ };
476
+ add(list, [key], value, source, false);
477
+ return list;
478
+ }
479
+ function readLine(reader, value, source) {
480
+ if (typeof value === "string") {
481
+ return cleanLines(value).map((line) => ({
482
+ value: line,
483
+ source,
484
+ fields: []
485
+ }));
486
+ }
487
+ const record = asRecord(value);
488
+ const parts = [record?.value, record?.unitText, record?.name ?? record?.text].filter((part) => typeof part === "string" || typeof part === "number").join(" ");
489
+ return parts ? cleanLines(parts).map((line) => ({ value: line, source, fields: [] })) : void 0;
490
+ }
491
+ function readStep(reader, value, source) {
492
+ if (typeof value === "string") {
493
+ return cleanLines(value).map((line) => ({
494
+ value: line,
495
+ source,
496
+ fields: []
497
+ }));
498
+ }
499
+ const record = asRecord(value);
500
+ if (!record) {
501
+ return void 0;
502
+ }
503
+ const name = typeof record.name === "string" ? cleanText(record.name) : "";
504
+ const body = text(record.text) ?? name;
505
+ const image = readImage(reader, record.image, [...source, "image"]);
506
+ const lead = name.replace(/(?:…|\.\.\.)$/, "").trim();
507
+ const hasName = typeof body === "string" && !body.startsWith(lead) && !/^step\s*\d*[.:]?$/i.test(name);
508
+ const fields = [
509
+ ["text", [...source, name && body === name ? "name" : "text"], false]
510
+ ];
511
+ if (image) {
512
+ fields.push(["image", image.source, true]);
513
+ }
514
+ return [
515
+ {
516
+ value: {
517
+ text: body,
518
+ ...hasName && { name },
519
+ ...image && { image: image.url }
520
+ },
521
+ source,
522
+ fields
523
+ }
524
+ ];
525
+ }
526
+ function readTools(reader, value, source) {
527
+ const equipment = [];
528
+ link(reader, ["equipment"], source);
529
+ for (const [entry, entrySource] of entriesOf(value, source)) {
530
+ const record = asRecord(entry);
531
+ const item = record ? {
532
+ name: text(record.name),
533
+ ...record.requiredQuantity !== void 0 && {
534
+ quantity: toNumber(
535
+ asRecord(record.requiredQuantity)?.value ?? record.requiredQuantity
536
+ ) ?? record.requiredQuantity
537
+ },
538
+ ...record.url !== void 0 && { url: text(record.url) }
539
+ } : text(entry);
540
+ if (item === void 0) {
541
+ continue;
542
+ }
543
+ const path = ["equipment", equipment.length];
544
+ equipment.push(item);
545
+ link(reader, path, entrySource, true);
546
+ link(reader, [...path, "name"], [...entrySource, "name"]);
547
+ link(
548
+ reader,
549
+ [...path, "quantity"],
550
+ [...entrySource, "requiredQuantity"],
551
+ true
552
+ );
553
+ link(reader, [...path, "url"], [...entrySource, "url"], true);
554
+ }
555
+ return equipment;
556
+ }
557
+ function readNutrition(reader, value, source) {
558
+ link(reader, ["nutrition"], source);
559
+ if (isAbsent(value)) {
560
+ return void 0;
561
+ }
562
+ const record = asRecord(value);
563
+ if (!record) {
564
+ ignore(
565
+ reader,
566
+ "invalid_type",
567
+ source,
568
+ `Expected NutritionInformation, such as { "calories": "250 kcal" }, received ${describeValue(value)}.`
569
+ );
570
+ return void 0;
571
+ }
572
+ const nutrition = {};
573
+ const keys = Object.keys(NUTRIENTS);
574
+ for (const [field, amount] of Object.entries(record)) {
575
+ const key = keys.find((key2) => NUTRIENTS[key2] === field);
576
+ if (key) {
577
+ nutrition[key] = text(amount);
578
+ link(reader, ["nutrition", key], [...source, field], true);
579
+ } else if (!field.startsWith("@")) {
580
+ ignore(reader, "unknown_field", [...source, field], "Unknown field.");
581
+ }
582
+ }
583
+ return nutrition;
584
+ }
585
+ function text(value) {
586
+ return typeof value === "string" ? cleanText(value) || void 0 : value ?? void 0;
587
+ }
588
+ function toNumber(value) {
589
+ const number = typeof value === "string" && value.trim() ? Number(value) : value;
590
+ return typeof number === "number" && Number.isFinite(number) ? number : void 0;
591
+ }
592
+ function isAbsent(value) {
593
+ return value === void 0 || value === null || typeof value === "string" && !value.trim();
594
+ }
595
+ function ignore(reader, code, path, message) {
596
+ warning(reader.warnings, code, path, `${message} It is ignored.`);
597
+ }
598
+ function link(reader, path, source, droppable = false) {
599
+ const key = JSON.stringify(path);
600
+ reader.sources.set(key, source);
601
+ if (droppable) {
602
+ reader.droppable.add(key);
603
+ }
604
+ }
605
+ function sourceOf(reader, path) {
606
+ for (let n = path.length; n >= 0; n--) {
607
+ const source = reader.sources.get(JSON.stringify(path.slice(0, n)));
608
+ if (source) {
609
+ return source;
610
+ }
611
+ }
612
+ return [];
613
+ }
614
+ function compareOrder(a, b) {
615
+ for (const [i, n] of a.entries()) {
616
+ if (i === b.length) {
617
+ return 1;
618
+ }
619
+ const m = b[i];
620
+ if (n !== m) {
621
+ return n - m;
622
+ }
623
+ }
624
+ return a.length - b.length;
625
+ }
626
+ function droppableAt(reader, path) {
627
+ for (let n = path.length; n > 0; n--) {
628
+ const at = path.slice(0, n);
629
+ if (reader.droppable.has(JSON.stringify(at))) {
630
+ return at;
631
+ }
632
+ }
633
+ return void 0;
634
+ }
635
+ function remove(model, path) {
636
+ const parent = path.slice(0, -1).reduce(
637
+ (value, key) => value?.[key],
638
+ model
639
+ );
640
+ if (parent) {
641
+ delete parent[path[path.length - 1]];
642
+ }
643
+ }
644
+ export {
645
+ fromJsonLd
646
+ };
647
+ //# sourceMappingURL=from-json-ld.js.map