@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.
- package/CHANGELOG.md +116 -0
- package/LICENSE +21 -0
- package/README.md +50 -7
- package/dist/chunk-D5CV3UF7.cjs +2754 -0
- package/dist/chunk-D5CV3UF7.cjs.map +1 -0
- package/dist/errors.js +76 -0
- package/dist/errors.js.map +1 -0
- package/dist/format/duration.js +36 -0
- package/dist/format/duration.js.map +1 -0
- package/dist/format/ingredient.js +49 -0
- package/dist/format/ingredient.js.map +1 -0
- package/dist/format/number.js +120 -0
- package/dist/format/number.js.map +1 -0
- package/dist/format/options.js +31 -0
- package/dist/format/options.js.map +1 -0
- package/dist/format/quantity.js +144 -0
- package/dist/format/quantity.js.map +1 -0
- package/dist/format/servings.js +68 -0
- package/dist/format/servings.js.map +1 -0
- package/dist/format/temperature.js +35 -0
- package/dist/format/temperature.js.map +1 -0
- package/dist/index.cjs +476 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +994 -0
- package/dist/index.d.ts +994 -5
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -0
- package/dist/ingredient/parse.js +412 -0
- package/dist/ingredient/parse.js.map +1 -0
- package/dist/issues.js +26 -0
- package/dist/issues.js.map +1 -0
- package/dist/quantity/amount.js +78 -0
- package/dist/quantity/amount.js.map +1 -0
- package/dist/quantity/parse.js +255 -0
- package/dist/quantity/parse.js.map +1 -0
- package/dist/quantity/scale.js +23 -0
- package/dist/quantity/scale.js.map +1 -0
- package/dist/recipe/duration.js +28 -0
- package/dist/recipe/duration.js.map +1 -0
- package/dist/recipe/ids.js +68 -0
- package/dist/recipe/ids.js.map +1 -0
- package/dist/recipe/infer.js +24 -0
- package/dist/recipe/infer.js.map +1 -0
- package/dist/recipe/nutrient-units.js +18 -0
- package/dist/recipe/nutrient-units.js.map +1 -0
- package/dist/recipe/nutrition.js +26 -0
- package/dist/recipe/nutrition.js.map +1 -0
- package/dist/recipe/parse.js +854 -0
- package/dist/recipe/parse.js.map +1 -0
- package/dist/recipe/schema.js +32 -0
- package/dist/recipe/schema.js.map +1 -0
- package/dist/recipe/url.js +9 -0
- package/dist/recipe/url.js.map +1 -0
- package/dist/resolve.js +120 -0
- package/dist/resolve.js.map +1 -0
- package/dist/schema-org/find.js +158 -0
- package/dist/schema-org/find.js.map +1 -0
- package/dist/schema-org/from-json-ld.js +647 -0
- package/dist/schema-org/from-json-ld.js.map +1 -0
- package/dist/schema-org/nutrients.js +19 -0
- package/dist/schema-org/nutrients.js.map +1 -0
- package/dist/schema-org/text.js +125 -0
- package/dist/schema-org/text.js.map +1 -0
- package/dist/schema-org/to-json-ld.js +151 -0
- package/dist/schema-org/to-json-ld.js.map +1 -0
- package/dist/schema-org.cjs +1062 -0
- package/dist/schema-org.cjs.map +1 -0
- package/dist/schema-org.d.cts +223 -0
- package/dist/schema-org.d.ts +223 -0
- package/dist/schema-org.js +10 -0
- package/dist/schema-org.js.map +1 -0
- package/dist/steps/segment.js +45 -0
- package/dist/steps/segment.js.map +1 -0
- package/dist/steps/temperature.js +174 -0
- package/dist/steps/temperature.js.map +1 -0
- package/dist/suggest.js +50 -0
- package/dist/suggest.js.map +1 -0
- package/dist/types-BknM8KOd.d.cts +628 -0
- package/dist/types-BknM8KOd.d.ts +628 -0
- package/dist/units/convert.js +79 -0
- package/dist/units/convert.js.map +1 -0
- package/dist/units/lookup.js +86 -0
- package/dist/units/lookup.js.map +1 -0
- package/dist/units/registry.js +183 -0
- package/dist/units/registry.js.map +1 -0
- package/dist/units/system.js +91 -0
- package/dist/units/system.js.map +1 -0
- package/dist/units/temperature.js +40 -0
- package/dist/units/temperature.js.map +1 -0
- package/dist/validation.js +90 -0
- package/dist/validation.js.map +1 -0
- package/package.json +53 -8
- package/schema-org/package.json +5 -0
- package/dist/chunk-22RBQQWH.mjs +0 -1
- package/dist/chunk-3CSP4UZG.mjs +0 -1
- package/dist/chunk-3FFMHRB7.mjs +0 -26
- package/dist/chunk-4EB3HG6B.mjs +0 -50
- package/dist/chunk-4EHNJLGI.mjs +0 -50
- package/dist/chunk-4IA5EEKI.mjs +0 -81
- package/dist/chunk-4TATADPT.mjs +0 -51
- package/dist/chunk-6GI5KLI4.mjs +0 -22
- package/dist/chunk-6QJZOZ6W.mjs +0 -49
- package/dist/chunk-6YYQ4RYZ.mjs +0 -26
- package/dist/chunk-7BBUDI2L.mjs +0 -0
- package/dist/chunk-7KEMNCV7.mjs +0 -26
- package/dist/chunk-7MMJZZ7Z.mjs +0 -75
- package/dist/chunk-BES4V7DM.mjs +0 -26
- package/dist/chunk-BRDXRIAH.mjs +0 -50
- package/dist/chunk-C45UD45A.mjs +0 -1
- package/dist/chunk-CBLJXA6P.mjs +0 -1
- package/dist/chunk-CFR2ZBPB.mjs +0 -77
- package/dist/chunk-DYMNVFL2.mjs +0 -78
- package/dist/chunk-F6AQKXJU.mjs +0 -51
- package/dist/chunk-FW4M2R43.mjs +0 -51
- package/dist/chunk-GCCEDUST.mjs +0 -75
- package/dist/chunk-GE7KBYZR.mjs +0 -26
- package/dist/chunk-GN7ZDGYC.mjs +0 -51
- package/dist/chunk-GOO2SCQX.mjs +0 -75
- package/dist/chunk-HAVKBQLH.mjs +0 -51
- package/dist/chunk-HCBIPANU.mjs +0 -50
- package/dist/chunk-IEBAGBS7.mjs +0 -51
- package/dist/chunk-IQ6WN2V4.mjs +0 -75
- package/dist/chunk-JB4Z2FR7.mjs +0 -77
- package/dist/chunk-JMTQOTBM.mjs +0 -75
- package/dist/chunk-JRO6JUES.mjs +0 -51
- package/dist/chunk-KH3SRXET.mjs +0 -52
- package/dist/chunk-KNZMCAQZ.mjs +0 -51
- package/dist/chunk-KXFUCO3R.mjs +0 -1
- package/dist/chunk-LEBGCVCJ.mjs +0 -75
- package/dist/chunk-MAHWFCIL.mjs +0 -51
- package/dist/chunk-MPH76SLC.mjs +0 -51
- package/dist/chunk-MQGMFBJQ.mjs +0 -26
- package/dist/chunk-NMIVG7T4.mjs +0 -6
- package/dist/chunk-Q3TT3AZ4.mjs +0 -26
- package/dist/chunk-QY5HJQIL.mjs +0 -51
- package/dist/chunk-RCPLSNKM.mjs +0 -1
- package/dist/chunk-SR2KFBT3.mjs +0 -1
- package/dist/chunk-ULDLBAEG.mjs +0 -1
- package/dist/chunk-UQ3EUYFR.mjs +0 -1
- package/dist/chunk-V26OCCTL.mjs +0 -82
- package/dist/chunk-WBQAMGXK.mjs +0 -0
- package/dist/chunk-WSW6FMUL.mjs +0 -51
- package/dist/chunk-WU55SBIV.mjs +0 -75
- package/dist/chunk-XINK45P3.mjs +0 -51
- package/dist/chunk-XNOHIWI3.mjs +0 -1
- package/dist/chunk-XV4QN4LA.mjs +0 -6
- package/dist/chunk-YHBXEVUX.mjs +0 -51
- package/dist/chunk-YSZSCEI7.mjs +0 -75
- package/dist/chunk-YY3SUQ7P.mjs +0 -16
- package/dist/chunk-YZB3IG47.mjs +0 -80
- package/dist/chunk-ZPZT4LMQ.mjs +0 -25
- package/dist/const/conversion.d.mts +0 -7
- package/dist/const/conversion.d.ts +0 -7
- package/dist/const/conversion.js +0 -1
- package/dist/const/conversion.mjs +0 -1
- package/dist/const/unit.d.mts +0 -119
- package/dist/const/unit.d.ts +0 -119
- package/dist/const/unit.js +0 -1
- package/dist/const/unit.mjs +0 -1
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -1
- package/dist/lib/assert.d.mts +0 -18
- package/dist/lib/assert.d.ts +0 -18
- package/dist/lib/assert.js +0 -1
- package/dist/lib/assert.mjs +0 -1
- package/dist/lib/ingredient.d.mts +0 -9
- package/dist/lib/ingredient.d.ts +0 -9
- package/dist/lib/ingredient.js +0 -1
- package/dist/lib/ingredient.mjs +0 -1
- package/dist/lib/recipe.d.mts +0 -12
- package/dist/lib/recipe.d.ts +0 -12
- package/dist/lib/recipe.js +0 -1
- package/dist/lib/recipe.mjs +0 -1
- package/dist/lib/runtime.d.mts +0 -3
- package/dist/lib/runtime.d.ts +0 -3
- package/dist/lib/runtime.js +0 -1
- package/dist/lib/runtime.mjs +0 -1
- package/dist/lib/unit.d.mts +0 -19
- package/dist/lib/unit.d.ts +0 -19
- package/dist/lib/unit.js +0 -1
- package/dist/lib/unit.mjs +0 -1
- package/dist/types.d.mts +0 -36
- package/dist/types.d.ts +0 -36
- package/dist/types.js +0 -1
- package/dist/types.mjs +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,994 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { Q as Quantity, P as ParseResult, R as Recipe, U as UnitAlias, S as System, I as Ingredient, a as Servings, Y as Yield, b as Step, A as Amount, c as Range, d as UnitId, e as UnitIdOf, f as UnitDefinition } from './types-BknM8KOd.js';
|
|
2
|
+
export { C as CustomTime, D as Dimension, E as Equipment, g as IngredientSection, N as Note, h as Nutrition, i as RecipeBookError, j as RecipeBookErrorCode, k as RecipeIssue, l as RecipeIssueCode, m as RecipeValidationError, n as StepSection, T as Times, V as ValidationErrorCode, o as isRecipeBookError, u as units } from './types-BknM8KOd.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An ingredient read from a line of text. It has no `id`, so pass it through
|
|
6
|
+
* `parseRecipe` before resolving it.
|
|
7
|
+
*/
|
|
8
|
+
type IngredientLine = {
|
|
9
|
+
name: string;
|
|
10
|
+
plural?: string | undefined;
|
|
11
|
+
quantity?: Quantity | undefined;
|
|
12
|
+
equivalents?: Quantity[] | undefined;
|
|
13
|
+
note?: string | undefined;
|
|
14
|
+
optional?: boolean | undefined;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Parses an ingredient line, such as `'1 ½ cups (190 g) flour, sifted'`, into
|
|
18
|
+
* ingredient input. Never throws. Pass the result through `parseRecipe` before
|
|
19
|
+
* resolving, or pass this function to `parseRecipe` as
|
|
20
|
+
* `{ parseIngredientLine }` to put lines in recipe input.
|
|
21
|
+
*
|
|
22
|
+
* Reads a leading amount (`2`, `1–2`, `a`, `one`) and unit, and a second
|
|
23
|
+
* measure in parentheses that hold nothing but measures, or after a slash or
|
|
24
|
+
* semicolon, as `equivalents`. With no amount, a unit before "of" reads as 1
|
|
25
|
+
* when it starts the line, after any leading "optional" and any space or
|
|
26
|
+
* colon, or "(optional)" and any space, and after any size word such as
|
|
27
|
+
* "big", so `'tsp. of salt'` gets `quantity: { amount: 1, unit: 'tsp' }` and
|
|
28
|
+
* `'big pinch of salt'` gets `quantity: { amount: 1, unit: 'pinch' }` and
|
|
29
|
+
* `note: 'big'`, but `'Optional - pinch of salt'` gets none. With no
|
|
30
|
+
* leading amount or unit, the first measure in parentheses that hold nothing
|
|
31
|
+
* but measures is the `quantity` instead, so `'Butter (100g)'` gets
|
|
32
|
+
* `quantity: { amount: 100, unit: 'g' }`. Measures after a leading "optional"
|
|
33
|
+
* in parentheses are read the same way, except straight after the amount,
|
|
34
|
+
* where the first is dropped (`'2 (optional, 15 oz) cans beans'`). A measure
|
|
35
|
+
* in the same dimension, mass or volume, after "plus", "and" or "+", or
|
|
36
|
+
* straight after the first, is added to it (`'1 lb 2 oz flour'` is 1.125 lb).
|
|
37
|
+
* In parentheses, any other sum (across dimensions, in count units such as
|
|
38
|
+
* "pinch", or of three measures) makes every measure in them part of the note
|
|
39
|
+
* (`'1 cup (8 oz + 1 tbsp) milk'`).
|
|
40
|
+
* With no leading amount or unit, an amount and unit after the name and a
|
|
41
|
+
* colon are read when nothing else follows but a note after a comma, so
|
|
42
|
+
* `'nagaimo: 160 g, grated'` gets `name: 'nagaimo'`,
|
|
43
|
+
* `quantity: { amount: 160, unit: 'g' }` and `note: 'grated'`. A name with a
|
|
44
|
+
* digit, or that starts with the word "for", "to", "a", "an", "yield",
|
|
45
|
+
* "yields" or "total", isn't read that way, and neither is one that is just
|
|
46
|
+
* "makes", "serves", "serving", "servings", "serving size", "protein",
|
|
47
|
+
* "carbs", "carbohydrate", "carbohydrates", "fiber", "fibre", "sodium",
|
|
48
|
+
* "cholesterol", "saturated fat" or "fat". So `'For the sauce: 1 cup'` and
|
|
49
|
+
* `'Protein: 12 g'` keep the whole line as the name, but
|
|
50
|
+
* `'Protein powder: 30 g'` gets `name: 'Protein powder'`.
|
|
51
|
+
* It also reads a note after the first comma outside parentheses that doesn't
|
|
52
|
+
* follow "boneless" or "skinless", phrases such as "to taste", and "optional"
|
|
53
|
+
* at the start of the line or of text in parentheses (the innermost when they
|
|
54
|
+
* nest), or as its own comma-separated item after the first comma. After a
|
|
55
|
+
* leading "optional" and any space or colon, or "(optional)" and any space,
|
|
56
|
+
* the rest is read as if it were the whole line, so
|
|
57
|
+
* `'Optional - 2 cups flour'` gets `optional: true` and
|
|
58
|
+
* `name: '- 2 cups flour'` with no quantity. With a comma there, not even
|
|
59
|
+
* "optional" is read: `'optional, 2 cups flour'` becomes
|
|
60
|
+
* `{ name: 'optional, 2 cups flour' }` with a warning. Text in parentheses
|
|
61
|
+
* before the first comma that isn't a measure or a leading "optional"
|
|
62
|
+
* (only the innermost when they nest) is a note too, ahead of the text after
|
|
63
|
+
* the comma, so `'1 onion (diced), peeled'` gets `note: 'diced, peeled'`. When
|
|
64
|
+
* parentheses before the first comma nest, the outer ones stay in the name with
|
|
65
|
+
* their text, so `'2 onions (diced (fine)), peeled'` gets
|
|
66
|
+
* `name: 'onions (diced)'` and `note: 'fine, peeled'`, and
|
|
67
|
+
* `'115 g butter ((1/2 cup))'` gets `name: 'butter ()'`.
|
|
68
|
+
* A bare count gets a `plural` from the word before "of", a leading container
|
|
69
|
+
* such as "jars", or else the last word. There's none when that word gets no
|
|
70
|
+
* other form, as when it ends in parentheses (`'2 onions (diced (fine))'`),
|
|
71
|
+
* has no plural ending after a count above 1 or a range (`'12 shrimp'`), or
|
|
72
|
+
* ends in "s" after a count of 1 or less (`'1 eggs'`, `'½ onions'`). A
|
|
73
|
+
* line it can't read becomes `{ name: line }` with a warning; only blank text
|
|
74
|
+
* or a value that isn't a string is an error.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* parseIngredientLine('1 ½ cups (190 g) flour, sifted').data
|
|
78
|
+
* // { name: 'flour', quantity: { amount: 1.5, unit: 'cup' }, equivalents: [{ amount: 190, unit: 'g' }], note: 'sifted' }
|
|
79
|
+
* parseIngredientLine('2 large eggs').data // { name: 'large egg', plural: 'large eggs', quantity: { amount: 2 } }
|
|
80
|
+
*/
|
|
81
|
+
declare function parseIngredientLine(text: string): ParseResult<IngredientLine>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validates and normalizes recipe data: resolves unit aliases, parses quantity
|
|
85
|
+
* text and ISO 8601 durations, groups items into sections, derives ids, and
|
|
86
|
+
* infers the source system. Accepts `unknown`, so JSON needs no cast, and reads
|
|
87
|
+
* only an object's own fields. Never throws for bad data; an error thrown by a
|
|
88
|
+
* getter or proxy in the input passes through.
|
|
89
|
+
*
|
|
90
|
+
* Unknown fields, duplicate ids, missing or empty `ingredients` or `steps`, and
|
|
91
|
+
* empty sections are warnings and don't fail the parse. Each issue has the path
|
|
92
|
+
* of the value it is about.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* import { parseRecipe } from '@recipe-book/core'
|
|
96
|
+
*
|
|
97
|
+
* const result = parseRecipe({
|
|
98
|
+
* name: 'Pancakes',
|
|
99
|
+
* ingredients: [{ name: 'flour', quantity: '1 ½ cups' }],
|
|
100
|
+
* steps: ['Whisk the batter.'],
|
|
101
|
+
* })
|
|
102
|
+
* result.success && result.data.ingredients[0]?.items[0]?.quantity // { amount: 1.5, unit: 'cup' }
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* import { parseRecipe } from '@recipe-book/core'
|
|
106
|
+
*
|
|
107
|
+
* const result = parseRecipe({
|
|
108
|
+
* name: 'Pancakes',
|
|
109
|
+
* ingredients: [{ name: 'flour', quantity: '1 ½ cupz' }],
|
|
110
|
+
* steps: ['Whisk the batter.'],
|
|
111
|
+
* })
|
|
112
|
+
* if (!result.success) {
|
|
113
|
+
* console.error(result.error.message)
|
|
114
|
+
* // "Pancakes" has 1 problem:
|
|
115
|
+
* // ✖ ingredients[0].quantity Unknown unit "cupz". Did you mean "cups"?
|
|
116
|
+
* // See https://recipe-book.siraj.page/errors/invalid_recipe
|
|
117
|
+
* }
|
|
118
|
+
*/
|
|
119
|
+
declare function parseRecipe(input: unknown): ParseResult<Recipe>;
|
|
120
|
+
/**
|
|
121
|
+
* Parses recipe data as `parseRecipe(input)` does, and reads ingredients
|
|
122
|
+
* written as text with `options.parseIngredientLine`. Without it, a text
|
|
123
|
+
* ingredient is an `invalid_type` error.
|
|
124
|
+
*
|
|
125
|
+
* @throws {RecipeBookError} `invalid_argument` when
|
|
126
|
+
* `options.parseIngredientLine` is present but isn't a function.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* import { parseIngredientLine, parseRecipe } from '@recipe-book/core'
|
|
130
|
+
*
|
|
131
|
+
* const result = parseRecipe(
|
|
132
|
+
* { name: 'Toast', ingredients: ['2 slices bread', '1 tbsp butter, softened'] },
|
|
133
|
+
* { parseIngredientLine },
|
|
134
|
+
* )
|
|
135
|
+
* result.success && result.data.ingredients[0]?.items[1] // { id: 'butter', name: 'butter', quantity: { amount: 1, unit: 'tbsp' }, equivalents: [], note: 'softened', optional: false }
|
|
136
|
+
*/
|
|
137
|
+
declare function parseRecipe(input: unknown, options: ParseRecipeOptions | undefined): ParseResult<Recipe>;
|
|
138
|
+
/** Options for `parseRecipe`. */
|
|
139
|
+
type ParseRecipeOptions = {
|
|
140
|
+
/**
|
|
141
|
+
* Reads ingredients written as text, such as `'2 cups flour'`. Pass
|
|
142
|
+
* `parseIngredientLine`. Without it, a text ingredient is an `invalid_type` error.
|
|
143
|
+
* A function of your own must return what `parseIngredientLine` returns.
|
|
144
|
+
*/
|
|
145
|
+
parseIngredientLine?: ((text: string) => ParseResult<IngredientLine>) | undefined;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parses quantity text, such as `'1 ½ cups'`, `'2-3 tbsp'`, `'a pinch'`, or
|
|
150
|
+
* `'3'` for a bare count. Never throws.
|
|
151
|
+
*
|
|
152
|
+
* Accepts whole numbers, decimals, fractions (`1/2`, `½`, also with the
|
|
153
|
+
* fraction slash `⁄`), mixed numbers (`1 1/2`, `1½`) and ranges (`2-3`, `2–3`,
|
|
154
|
+
* `2 to 3`), each followed by an optional unit alias, and `a` or `an` for 1
|
|
155
|
+
* before a unit alias, as in `'a pinch'`.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* parseQuantity('1 ½ cups') // { success: true, data: { amount: 1.5, unit: 'cup' }, warnings: [] }
|
|
159
|
+
*
|
|
160
|
+
* const result = parseQuantity('2 cupz')
|
|
161
|
+
* if (!result.success) {
|
|
162
|
+
* result.error.message // '"2 cupz" has 1 problem:\n ✖ (root) Unknown unit "cupz". Did you mean "cups"?\nSee https://recipe-book.siraj.page/errors/invalid_quantity'
|
|
163
|
+
* }
|
|
164
|
+
*/
|
|
165
|
+
declare function parseQuantity(text: string): ParseResult<Quantity>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The Standard Schema v1 interface (https://standardschema.dev), copied from
|
|
169
|
+
* `@standard-schema/spec` 1.1.0 as the spec recommends so core stays
|
|
170
|
+
* dependency-free. `test/schema.test.ts` checks that it stays compatible.
|
|
171
|
+
*/
|
|
172
|
+
interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
173
|
+
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
|
|
174
|
+
}
|
|
175
|
+
declare namespace StandardSchemaV1 {
|
|
176
|
+
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
|
|
177
|
+
readonly validate: (value: unknown, options?: StandardSchemaV1.Options) => Result<Output> | Promise<Result<Output>>;
|
|
178
|
+
}
|
|
179
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
180
|
+
interface SuccessResult<Output> {
|
|
181
|
+
readonly value: Output;
|
|
182
|
+
readonly issues?: undefined;
|
|
183
|
+
}
|
|
184
|
+
interface Options {
|
|
185
|
+
readonly libraryOptions?: Record<string, unknown> | undefined;
|
|
186
|
+
}
|
|
187
|
+
interface FailureResult {
|
|
188
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
189
|
+
}
|
|
190
|
+
interface Issue {
|
|
191
|
+
readonly message: string;
|
|
192
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
|
193
|
+
}
|
|
194
|
+
interface PathSegment {
|
|
195
|
+
readonly key: PropertyKey;
|
|
196
|
+
}
|
|
197
|
+
type Types<Input = unknown, Output = Input> = StandardTypedV1.Types<Input, Output>;
|
|
198
|
+
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
|
|
199
|
+
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
|
|
200
|
+
}
|
|
201
|
+
interface StandardTypedV1<Input = unknown, Output = Input> {
|
|
202
|
+
readonly '~standard': StandardTypedV1.Props<Input, Output>;
|
|
203
|
+
}
|
|
204
|
+
declare namespace StandardTypedV1 {
|
|
205
|
+
interface Props<Input = unknown, Output = Input> {
|
|
206
|
+
readonly version: 1;
|
|
207
|
+
readonly vendor: string;
|
|
208
|
+
readonly types?: Types<Input, Output> | undefined;
|
|
209
|
+
}
|
|
210
|
+
interface Types<Input = unknown, Output = Input> {
|
|
211
|
+
readonly input: Input;
|
|
212
|
+
readonly output: Output;
|
|
213
|
+
}
|
|
214
|
+
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema['~standard']['types']>['input'];
|
|
215
|
+
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema['~standard']['types']>['output'];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
219
|
+
type VulgarFraction = '¼' | '½' | '¾' | '⅐' | '⅑' | '⅒' | '⅓' | '⅔' | '⅕' | '⅖' | '⅗' | '⅘' | '⅙' | '⅚' | '⅛' | '⅜' | '⅝' | '⅞';
|
|
220
|
+
/**
|
|
221
|
+
* Quantity text. It must end in a known unit alias, optionally followed by a
|
|
222
|
+
* period, or in a digit or fraction for a bare count, so a misspelled unit is a
|
|
223
|
+
* compile error. The alias must be in its listed case: `'2 Tbsp'` doesn't
|
|
224
|
+
* type-check, though `parseRecipe` reads it. `parseRecipe` checks the full
|
|
225
|
+
* grammar at runtime; see `parseQuantity`.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* import type { QuantityString } from '@recipe-book/core'
|
|
229
|
+
*
|
|
230
|
+
* const ok: QuantityString[] = ['1 ½ cups', '2-3 tbsp.', 'a pinch', '250g', '3']
|
|
231
|
+
* // @ts-expect-error: 'cupz' is not a unit
|
|
232
|
+
* const typo: QuantityString = '2 cupz'
|
|
233
|
+
*/
|
|
234
|
+
type QuantityString = `${string}${Digit | VulgarFraction}` | `${string} ${UnitAlias}` | `${string} ${UnitAlias}.` | `${number}${UnitAlias}` | `${number}${UnitAlias}.`;
|
|
235
|
+
/** A quantity as authors write it: text, a bare count, or an object with a unit alias. */
|
|
236
|
+
type QuantityInput = QuantityString | number | {
|
|
237
|
+
amount: number | {
|
|
238
|
+
min: number;
|
|
239
|
+
max: number;
|
|
240
|
+
};
|
|
241
|
+
unit?: UnitAlias | `${UnitAlias}.` | undefined;
|
|
242
|
+
};
|
|
243
|
+
/** Minutes, or an ISO 8601 duration such as `'PT1H30M'`. */
|
|
244
|
+
type DurationInput = number | `P${string}`;
|
|
245
|
+
/**
|
|
246
|
+
* An ingredient as authors write it. `id` is derived from `name` when absent,
|
|
247
|
+
* and `optional` defaults to `false`.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* import type { IngredientInput } from '@recipe-book/core'
|
|
251
|
+
*
|
|
252
|
+
* const butter: IngredientInput = { name: 'butter', quantity: '2 tbsp', note: 'melted' }
|
|
253
|
+
*/
|
|
254
|
+
type IngredientInput = {
|
|
255
|
+
id?: string | undefined;
|
|
256
|
+
name: string;
|
|
257
|
+
plural?: string | undefined;
|
|
258
|
+
quantity?: QuantityInput | undefined;
|
|
259
|
+
equivalents?: readonly QuantityInput[] | undefined;
|
|
260
|
+
note?: string | undefined;
|
|
261
|
+
/** An http or https URL, or a relative one, including protocol-relative `//host/path`. */
|
|
262
|
+
url?: string | undefined;
|
|
263
|
+
optional?: boolean | undefined;
|
|
264
|
+
};
|
|
265
|
+
/** A group of ingredients. Any entry with `items` is read as a section; sections can't be nested. */
|
|
266
|
+
type IngredientSectionInput<Line extends string = never> = {
|
|
267
|
+
id?: string | undefined;
|
|
268
|
+
title?: string | undefined;
|
|
269
|
+
items: readonly (Line | IngredientInput)[];
|
|
270
|
+
};
|
|
271
|
+
/** A step with a short `name` or an image. A plain string is a step with only `text`. */
|
|
272
|
+
type StepInput = {
|
|
273
|
+
id?: string | undefined;
|
|
274
|
+
text: string;
|
|
275
|
+
name?: string | undefined;
|
|
276
|
+
/** An http or https URL, or a relative one, including protocol-relative `//host/path`. */
|
|
277
|
+
image?: string | undefined;
|
|
278
|
+
};
|
|
279
|
+
/** A group of steps, given as strings or `StepInput` objects. Sections can't be nested. */
|
|
280
|
+
type StepSectionInput = {
|
|
281
|
+
id?: string | undefined;
|
|
282
|
+
title?: string | undefined;
|
|
283
|
+
items: readonly (string | StepInput)[];
|
|
284
|
+
};
|
|
285
|
+
/** A number of servings, range text such as `'4–6'`, or `{ amount, max }`. */
|
|
286
|
+
type ServingsInput = number | string | {
|
|
287
|
+
amount: number;
|
|
288
|
+
max?: number | undefined;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* What a batch makes: text such as `'24 cookies'` or `'2–3 loaves'`, or an
|
|
292
|
+
* object with the singular and plural. Text keeps its unit as written, so a
|
|
293
|
+
* yield that scales to 1 needs the object form to read "1 loaf". A yield needs
|
|
294
|
+
* a unit; use `ServingsInput` for a number of servings.
|
|
295
|
+
*/
|
|
296
|
+
type YieldInput = string | {
|
|
297
|
+
amount: number;
|
|
298
|
+
max?: number | undefined;
|
|
299
|
+
unit: string;
|
|
300
|
+
plural?: string | undefined;
|
|
301
|
+
};
|
|
302
|
+
/** Durations, each in minutes or as an ISO 8601 duration such as `'PT1H30M'`. */
|
|
303
|
+
type TimesInput = {
|
|
304
|
+
prep?: DurationInput | undefined;
|
|
305
|
+
cook?: DurationInput | undefined;
|
|
306
|
+
total?: DurationInput | undefined;
|
|
307
|
+
custom?: readonly CustomTimeInput[] | undefined;
|
|
308
|
+
};
|
|
309
|
+
/** A named time, such as `{ label: 'Chill', minutes: 120 }` or `{ label: 'Rise', minutes: 'PT1H' }`. */
|
|
310
|
+
type CustomTimeInput = {
|
|
311
|
+
id?: string | undefined;
|
|
312
|
+
label: string;
|
|
313
|
+
minutes: DurationInput;
|
|
314
|
+
};
|
|
315
|
+
/** A note with a title. A plain string is a note with only `text`. */
|
|
316
|
+
type NoteInput = {
|
|
317
|
+
id?: string | undefined;
|
|
318
|
+
title?: string | undefined;
|
|
319
|
+
/** Plain text. Renderers show it as text, never as HTML. */
|
|
320
|
+
text: string;
|
|
321
|
+
};
|
|
322
|
+
/** A tool with details. A plain string is a tool with only a `name`. */
|
|
323
|
+
type EquipmentInput = {
|
|
324
|
+
id?: string | undefined;
|
|
325
|
+
name: string;
|
|
326
|
+
/** Used when `quantity` is above 1: "2 baking sheets". */
|
|
327
|
+
plural?: string | undefined;
|
|
328
|
+
/** A whole number, such as `2` for two baking sheets. */
|
|
329
|
+
quantity?: number | undefined;
|
|
330
|
+
/** An http or https URL, or a relative one, including protocol-relative `//host/path`. */
|
|
331
|
+
url?: string | undefined;
|
|
332
|
+
note?: string | undefined;
|
|
333
|
+
};
|
|
334
|
+
/** A number in the nutrient's unit, or text with a unit, such as `'12 g'`. */
|
|
335
|
+
type NutrientInput = number | string;
|
|
336
|
+
/**
|
|
337
|
+
* Nutrition for one of the recipe's `servings`. Calories are in kcal, sodium
|
|
338
|
+
* and cholesterol in mg, and the rest in g. Text with another unit is
|
|
339
|
+
* converted: `'1046 kJ'` is 250 kcal, and sodium `'1.2 g'` is 1200 mg.
|
|
340
|
+
*/
|
|
341
|
+
type NutritionInput = {
|
|
342
|
+
servingSize?: string | undefined;
|
|
343
|
+
calories?: NutrientInput | undefined;
|
|
344
|
+
fat?: NutrientInput | undefined;
|
|
345
|
+
saturatedFat?: NutrientInput | undefined;
|
|
346
|
+
transFat?: NutrientInput | undefined;
|
|
347
|
+
unsaturatedFat?: NutrientInput | undefined;
|
|
348
|
+
carbohydrates?: NutrientInput | undefined;
|
|
349
|
+
sugar?: NutrientInput | undefined;
|
|
350
|
+
fiber?: NutrientInput | undefined;
|
|
351
|
+
protein?: NutrientInput | undefined;
|
|
352
|
+
sodium?: NutrientInput | undefined;
|
|
353
|
+
cholesterol?: NutrientInput | undefined;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* A recipe as authors write it. Use `satisfies RecipeInput` for type checking,
|
|
357
|
+
* and `parseRecipe` to normalize it into a `Recipe`. Ingredients may be a flat
|
|
358
|
+
* list, sections, or a mix; steps may be strings, objects, or sections.
|
|
359
|
+
* `RecipeInput<string>` also allows ingredient lines, for input parsed with
|
|
360
|
+
* `{ parseIngredientLine }`.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* import type { RecipeInput } from '@recipe-book/core'
|
|
364
|
+
*
|
|
365
|
+
* export const pancakes = {
|
|
366
|
+
* name: 'Buttermilk pancakes',
|
|
367
|
+
* servings: 4,
|
|
368
|
+
* ingredients: [
|
|
369
|
+
* { name: 'all-purpose flour', quantity: '1 ½ cups', equivalents: ['190 g'] },
|
|
370
|
+
* { name: 'egg', plural: 'eggs', quantity: 2 },
|
|
371
|
+
* ],
|
|
372
|
+
* steps: ['Whisk the dry ingredients.', 'Fold in the wet ingredients.'],
|
|
373
|
+
* } satisfies RecipeInput
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* import { parseIngredientLine, parseRecipe, type RecipeInput } from '@recipe-book/core'
|
|
377
|
+
*
|
|
378
|
+
* export const toast = {
|
|
379
|
+
* name: 'Buttered toast',
|
|
380
|
+
* ingredients: ['2 slices bread', '3 tbsp butter, melted'],
|
|
381
|
+
* } satisfies RecipeInput<string>
|
|
382
|
+
* parseRecipe(toast, { parseIngredientLine })
|
|
383
|
+
*/
|
|
384
|
+
type RecipeInput<Line extends string = never> = {
|
|
385
|
+
id?: string | undefined;
|
|
386
|
+
name: string;
|
|
387
|
+
description?: string | undefined;
|
|
388
|
+
/** An http or https URL, or a relative one, including protocol-relative `//host/path`. */
|
|
389
|
+
image?: string | undefined;
|
|
390
|
+
servings?: ServingsInput | undefined;
|
|
391
|
+
yield?: YieldInput | undefined;
|
|
392
|
+
system?: System | undefined;
|
|
393
|
+
times?: TimesInput | undefined;
|
|
394
|
+
ingredients?: readonly (Line | IngredientInput | IngredientSectionInput<Line>)[] | undefined;
|
|
395
|
+
steps?: readonly (string | StepInput | StepSectionInput)[] | undefined;
|
|
396
|
+
notes?: readonly (string | NoteInput)[] | undefined;
|
|
397
|
+
equipment?: readonly (string | EquipmentInput)[] | undefined;
|
|
398
|
+
nutrition?: NutritionInput | undefined;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* The type of {@link recipeSchema}: a Standard Schema v1 whose `validate`
|
|
403
|
+
* returns its result directly, never a promise. `RecipeSchema<string>`, from
|
|
404
|
+
* {@link createRecipeSchema}, also takes ingredient lines.
|
|
405
|
+
*/
|
|
406
|
+
interface RecipeSchema<Line extends string = never> extends StandardSchemaV1<RecipeInput<Line>, Recipe> {
|
|
407
|
+
readonly '~standard': {
|
|
408
|
+
readonly version: 1;
|
|
409
|
+
readonly vendor: string;
|
|
410
|
+
readonly validate: (value: unknown, options?: StandardSchemaV1.Options) => StandardSchemaV1.Result<Recipe>;
|
|
411
|
+
readonly types?: StandardSchemaV1.Types<RecipeInput<Line>, Recipe> | undefined;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* `parseRecipe` as a [Standard Schema](https://standardschema.dev) v1, for
|
|
416
|
+
* libraries that accept one: tRPC, TanStack Form, react-hook-form, Hono, and
|
|
417
|
+
* others. Validation is synchronous, so `validate` returns its result
|
|
418
|
+
* directly. Only errors are reported as issues; warnings don't fail
|
|
419
|
+
* validation. It reads ingredient objects only; use
|
|
420
|
+
* {@link createRecipeSchema} for ingredient lines.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* import { recipeSchema } from '@recipe-book/core'
|
|
424
|
+
* import { initTRPC } from '@trpc/server'
|
|
425
|
+
*
|
|
426
|
+
* const t = initTRPC.create()
|
|
427
|
+
* export const appRouter = t.router({
|
|
428
|
+
* saveRecipe: t.procedure.input(recipeSchema).mutation(({ input }) => input.id), // input: Recipe
|
|
429
|
+
* })
|
|
430
|
+
*/
|
|
431
|
+
declare const recipeSchema: RecipeSchema;
|
|
432
|
+
/**
|
|
433
|
+
* Makes a schema like {@link recipeSchema} that parses with `options`. Pass
|
|
434
|
+
* `{ parseIngredientLine }` to accept ingredients written as text.
|
|
435
|
+
*
|
|
436
|
+
* @throws {RecipeBookError} `invalid_argument` when
|
|
437
|
+
* `options.parseIngredientLine` is present but isn't a function.
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
* import { createRecipeSchema, parseIngredientLine } from '@recipe-book/core'
|
|
441
|
+
* import { initTRPC } from '@trpc/server'
|
|
442
|
+
*
|
|
443
|
+
* const t = initTRPC.create()
|
|
444
|
+
* const recipeInput = createRecipeSchema({ parseIngredientLine })
|
|
445
|
+
* export const appRouter = t.router({
|
|
446
|
+
* saveRecipe: t.procedure.input(recipeInput).mutation(({ input }) => input.id), // input: Recipe
|
|
447
|
+
* })
|
|
448
|
+
*/
|
|
449
|
+
declare function createRecipeSchema(options: ParseRecipeOptions): RecipeSchema<string>;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Where a resolved quantity comes from: the ingredient's `quantity`, one of its
|
|
453
|
+
* `equivalents`, or a conversion into the display system. May grow in minor releases.
|
|
454
|
+
*/
|
|
455
|
+
type QuantityOrigin = 'authored' | 'equivalent' | 'converted';
|
|
456
|
+
/** An ingredient, scaled and expressed in the display system. */
|
|
457
|
+
type ResolvedIngredient = {
|
|
458
|
+
id: string;
|
|
459
|
+
/** The ingredient as authored, unscaled, not a copy; treat it as read-only. */
|
|
460
|
+
ingredient: Ingredient;
|
|
461
|
+
/**
|
|
462
|
+
* Absent for unquantified ingredients, such as "salt, to taste". May be the
|
|
463
|
+
* authored object; treat it as read-only.
|
|
464
|
+
*/
|
|
465
|
+
quantity?: Quantity | undefined;
|
|
466
|
+
/** `'authored'` for unquantified ingredients. */
|
|
467
|
+
origin: QuantityOrigin;
|
|
468
|
+
};
|
|
469
|
+
/** What {@link resolveRecipe} returns: the recipe at a scale, in one system, unrounded. */
|
|
470
|
+
type ResolvedRecipe = {
|
|
471
|
+
/** The recipe passed in, not a copy; treat it as read-only. */
|
|
472
|
+
recipe: Recipe;
|
|
473
|
+
/**
|
|
474
|
+
* The `scale` option, else `servings` divided by the scaling base. Always
|
|
475
|
+
* between 1e-6 and 1e6, and 1 when both options fall back.
|
|
476
|
+
*/
|
|
477
|
+
scale: number;
|
|
478
|
+
/**
|
|
479
|
+
* The recipe's servings at this scale. At 1×, the authored servings. When
|
|
480
|
+
* the `servings` option sets a scale other than 1, exactly that number with
|
|
481
|
+
* no `max`; with `scale`, both ends of a range scale.
|
|
482
|
+
*/
|
|
483
|
+
servings?: Servings | undefined;
|
|
484
|
+
/**
|
|
485
|
+
* The recipe's yield at this scale. At 1×, the authored yield. When the
|
|
486
|
+
* yield is the scaling base and the `servings` option sets a scale other
|
|
487
|
+
* than 1, exactly that number with no `max`; otherwise both ends of a range
|
|
488
|
+
* scale.
|
|
489
|
+
*/
|
|
490
|
+
yield?: Yield | undefined;
|
|
491
|
+
system: System;
|
|
492
|
+
/**
|
|
493
|
+
* True when an ingredient's quantity has a unit that belongs to only one
|
|
494
|
+
* system. Step text isn't read; `segmentStepText` finds its temperatures.
|
|
495
|
+
*/
|
|
496
|
+
canConvert: boolean;
|
|
497
|
+
ingredients: Array<{
|
|
498
|
+
id: string;
|
|
499
|
+
title?: string | undefined;
|
|
500
|
+
items: ResolvedIngredient[];
|
|
501
|
+
}>;
|
|
502
|
+
/** Steps numbered from 1, continuing across sections. */
|
|
503
|
+
steps: Array<{
|
|
504
|
+
id: string;
|
|
505
|
+
title?: string | undefined;
|
|
506
|
+
items: Array<Step & {
|
|
507
|
+
number: number;
|
|
508
|
+
}>;
|
|
509
|
+
}>;
|
|
510
|
+
};
|
|
511
|
+
/** Options for {@link resolveRecipe}. */
|
|
512
|
+
type ResolveRecipeOptions = {
|
|
513
|
+
/**
|
|
514
|
+
* Scales by `servings / base`, where the base is the recipe's servings
|
|
515
|
+
* amount, else its yield amount, else 1. Defaults to the base. A value
|
|
516
|
+
* outside a millionth to a million times the base, or that isn't a number,
|
|
517
|
+
* also uses the default.
|
|
518
|
+
*/
|
|
519
|
+
servings?: number | undefined;
|
|
520
|
+
/**
|
|
521
|
+
* A direct multiplier, for 1×, 2×, and 3× controls. Wins over `servings`.
|
|
522
|
+
* A value outside 1e-6 to 1e6, or that isn't a number, is ignored.
|
|
523
|
+
*/
|
|
524
|
+
scale?: number | undefined;
|
|
525
|
+
/** Defaults to `recipe.system`, then `'metric'`. An unknown system also uses the default. */
|
|
526
|
+
system?: System | undefined;
|
|
527
|
+
/** Moves scaled amounts to a better unit on their own ladder (6 tsp to 2 tbsp). Default `true`. */
|
|
528
|
+
rebalance?: boolean | undefined;
|
|
529
|
+
};
|
|
530
|
+
/** Options for {@link resolveIngredient}. */
|
|
531
|
+
type ResolveIngredientOptions = {
|
|
532
|
+
scale: number;
|
|
533
|
+
system: System;
|
|
534
|
+
/** Moves scaled amounts to a better unit on their own ladder (6 tsp to 2 tbsp). Default `true`. */
|
|
535
|
+
rebalance?: boolean | undefined;
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Scales a recipe to a number of servings, or by a multiplier, and expresses
|
|
539
|
+
* each ingredient in a measurement system. Pure, and never throws for an
|
|
540
|
+
* unchanged recipe from `parseRecipe`: invalid options fall back to their
|
|
541
|
+
* defaults, and an error thrown by a getter or proxy in the options passes
|
|
542
|
+
* through. Nothing is rounded; format for display.
|
|
543
|
+
*
|
|
544
|
+
* Each ingredient uses the first of `[quantity, ...equivalents]` that is native to
|
|
545
|
+
* the system (a bare count is native to both), scaled. When none is, `quantity`
|
|
546
|
+
* is scaled, then converted with `toSystem`. Units never change dimension.
|
|
547
|
+
* At any scale but 1, the chosen quantity may move along its unit's ladder, so
|
|
548
|
+
* 3 tsp at 2× is 2 tbsp; pass `rebalance: false` to keep the chosen units.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* // Serves 4–6; flour is 1 ½ cups with a 190 g equivalent.
|
|
552
|
+
* const resolved = resolveRecipe(pancakes, { servings: 8, system: 'metric' })
|
|
553
|
+
* resolved.scale // 2
|
|
554
|
+
* resolved.servings // { amount: 8 }
|
|
555
|
+
* const flour = resolved.ingredients[0]?.items[0]
|
|
556
|
+
* flour?.quantity // { amount: 380, unit: 'g' }
|
|
557
|
+
* flour?.origin // 'equivalent'
|
|
558
|
+
*
|
|
559
|
+
* resolveRecipe(pancakes, { scale: 2 }).servings // { amount: 8, max: 12 }
|
|
560
|
+
*/
|
|
561
|
+
declare function resolveRecipe(recipe: Recipe, options?: ResolveRecipeOptions): ResolvedRecipe;
|
|
562
|
+
/**
|
|
563
|
+
* Resolves one ingredient the way {@link resolveRecipe} does, at a raw
|
|
564
|
+
* scale factor. Unlike `resolveRecipe`, it doesn't limit the scale to
|
|
565
|
+
* 1e-6 to 1e6, so a huge scale can overflow an amount to `Infinity`, which
|
|
566
|
+
* the format functions reject.
|
|
567
|
+
*
|
|
568
|
+
* @throws {RecipeBookError} `invalid_argument` when `scale` is not a finite
|
|
569
|
+
* number greater than 0, or `system` is not `'metric'` or `'us'`.
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* // salt is 3 tsp.
|
|
573
|
+
* resolveIngredient(salt, { scale: 2, system: 'us' }).quantity // { amount: 2, unit: 'tbsp' }
|
|
574
|
+
*/
|
|
575
|
+
declare function resolveIngredient(ingredient: Ingredient, options: ResolveIngredientOptions): ResolvedIngredient;
|
|
576
|
+
|
|
577
|
+
/** The amount type an operation returns for an input amount type. */
|
|
578
|
+
type SameAmount<A extends Amount> = A extends number ? number : Range;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Multiplies a quantity by `factor`. Both ends of a range scale, the unit is
|
|
582
|
+
* kept, and nothing is rounded.
|
|
583
|
+
*
|
|
584
|
+
* @throws {RecipeBookError} `invalid_argument` when `factor` is not a finite
|
|
585
|
+
* number greater than 0, `quantity` isn't a quantity object, the amount is
|
|
586
|
+
* not a finite number greater than 0 or a range of them, a range's `min`
|
|
587
|
+
* isn't less than its `max`, or the quantity has `max` set, as a `Servings`
|
|
588
|
+
* or `Yield` has. `resolveRecipe` scales those. The unit isn't checked.
|
|
589
|
+
* Also thrown when an end times `factor` overflows to `Infinity` or
|
|
590
|
+
* underflows to 0: past about 1.8e308, or below about 5e-324.
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* scaleQuantity({ amount: 1.5, unit: 'cup' }, 2) // { amount: 3, unit: 'cup' }
|
|
594
|
+
* scaleQuantity({ amount: { min: 2, max: 3 }, unit: 'tbsp' }, 2) // { amount: { min: 4, max: 6 }, unit: 'tbsp' }
|
|
595
|
+
*/
|
|
596
|
+
declare function scaleQuantity<A extends Amount>(quantity: Quantity & {
|
|
597
|
+
amount: A;
|
|
598
|
+
}, factor: number): {
|
|
599
|
+
amount: SameAmount<A>;
|
|
600
|
+
unit?: UnitId | undefined;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Converts a quantity to another unit of the same dimension. Both ends of a
|
|
605
|
+
* range convert. Nothing is rounded, so the amount can be off in its last
|
|
606
|
+
* digit: 5 tbsp is 14.999999999999998 tsp.
|
|
607
|
+
*
|
|
608
|
+
* @throws {RecipeBookError} `incompatible_units` across dimensions (cup to g),
|
|
609
|
+
* between a count unit and a different unit, or for a bare count. Check with
|
|
610
|
+
* {@link canConvert} first when the units come from data.
|
|
611
|
+
* @throws {RecipeBookError} `unknown_unit` when either unit is not a unit id.
|
|
612
|
+
* @throws {RecipeBookError} `invalid_argument` when `quantity` isn't a quantity object, the amount is not a finite number greater than 0 or a range of them, a range's `min` isn't less than its `max`, the quantity has `max` set, as a `Servings` or `Yield` has, or the result overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* convert({ amount: 1, unit: 'cup' }, 'ml') // { amount: 236.5882365, unit: 'ml' }
|
|
616
|
+
* convert({ amount: { min: 2, max: 3 }, unit: 'tbsp' }, 'tsp') // { amount: { min: 6, max: 9 }, unit: 'tsp' }
|
|
617
|
+
*/
|
|
618
|
+
declare function convert<A extends Amount, To extends UnitId>(quantity: Quantity & {
|
|
619
|
+
amount: A;
|
|
620
|
+
}, to: To): {
|
|
621
|
+
amount: SameAmount<A>;
|
|
622
|
+
unit: To;
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* True when {@link convert} can convert between the two units: same unit, or
|
|
626
|
+
* mass to mass, or volume to volume. Never throws, so it is safe on untrusted data.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* canConvert('cup', 'ml') // true
|
|
630
|
+
* canConvert('cup', 'g') // false
|
|
631
|
+
* canConvert(undefined, 'g') // false: a bare count has no size
|
|
632
|
+
*/
|
|
633
|
+
declare function canConvert(from: UnitId | undefined, to: UnitId): boolean;
|
|
634
|
+
/**
|
|
635
|
+
* Expresses a quantity in a measurement system, choosing the display unit
|
|
636
|
+
* from that system's ladder: g and kg, ml and l, oz and lb, or tsp, tbsp, and
|
|
637
|
+
* cup. Nothing is rounded, so the amount can be off in its last digit, as with
|
|
638
|
+
* {@link convert}.
|
|
639
|
+
*
|
|
640
|
+
* Returns the quantity unchanged when its unit is already native to `system`
|
|
641
|
+
* (tsp and tbsp are native to both), when it is a count unit, or when it has
|
|
642
|
+
* no unit.
|
|
643
|
+
*
|
|
644
|
+
* @throws {RecipeBookError} `invalid_argument` for an unknown system, an invalid amount, a quantity with `max` set, as a `Servings` or `Yield` has, or a result that overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.
|
|
645
|
+
* @throws {RecipeBookError} `unknown_unit` when the unit is not a unit id.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* toSystem({ amount: 250, unit: 'g' }, 'us') // { amount: 8.818…, unit: 'oz' }
|
|
649
|
+
* toSystem({ amount: 0.5, unit: 'cup' }, 'metric') // { amount: 118.29…, unit: 'ml' }
|
|
650
|
+
* toSystem({ amount: 3, unit: 'tsp' }, 'metric') // unchanged
|
|
651
|
+
*/
|
|
652
|
+
declare function toSystem<A extends Amount>(quantity: Quantity & {
|
|
653
|
+
amount: A;
|
|
654
|
+
}, system: System): {
|
|
655
|
+
amount: SameAmount<A>;
|
|
656
|
+
unit?: UnitId | undefined;
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Finds the unit that author text names. Surrounding whitespace is ignored,
|
|
661
|
+
* inner whitespace is collapsed, a period that ends a word is dropped, and
|
|
662
|
+
* case is ignored except for `'T'` (tbsp), `'t'` (tsp), and `'c'` (cup).
|
|
663
|
+
*
|
|
664
|
+
* @returns The unit id, or `undefined` when the text names no known unit.
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* findUnit('Tablespoons') // 'tbsp'
|
|
668
|
+
* findUnit('T') // 'tbsp'
|
|
669
|
+
* findUnit('fl. oz.') // 'fl-oz'
|
|
670
|
+
* findUnit('knob') // undefined
|
|
671
|
+
*/
|
|
672
|
+
declare function findUnit<Alias extends UnitAlias>(text: Alias): UnitIdOf<Alias>;
|
|
673
|
+
declare function findUnit<Alias extends UnitAlias>(text: `${Alias}.`): UnitIdOf<Alias>;
|
|
674
|
+
declare function findUnit(text: string): UnitId | undefined;
|
|
675
|
+
/**
|
|
676
|
+
* Returns the registry entry for a unit id.
|
|
677
|
+
*
|
|
678
|
+
* @throws {RecipeBookError} `unknown_unit` when `id` is not a unit id.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* getUnit('cup').toBase // 236.5882365
|
|
682
|
+
*/
|
|
683
|
+
declare function getUnit<Id extends UnitId>(id: Id): UnitDefinition & {
|
|
684
|
+
readonly id: Id;
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
/** Degrees Celsius or Fahrenheit. */
|
|
688
|
+
type TemperatureUnit = 'C' | 'F';
|
|
689
|
+
/**
|
|
690
|
+
* An oven or food temperature. Temperatures are not quantities: converting
|
|
691
|
+
* them is affine, and scaling a recipe never changes them.
|
|
692
|
+
*/
|
|
693
|
+
type Temperature = {
|
|
694
|
+
value: number;
|
|
695
|
+
unit: TemperatureUnit;
|
|
696
|
+
};
|
|
697
|
+
/**
|
|
698
|
+
* Converts a temperature between Celsius and Fahrenheit. Nothing is rounded,
|
|
699
|
+
* so the value can be off in its last digits: −18 °C is
|
|
700
|
+
* −0.3999999999999986 °F.
|
|
701
|
+
*
|
|
702
|
+
* @throws {RecipeBookError} `invalid_argument` for a non-finite value, an unknown unit, or a result that overflows to `Infinity` or `-Infinity`, which only a value beyond about ±2e307 can do.
|
|
703
|
+
*
|
|
704
|
+
* @example
|
|
705
|
+
* convertTemperature({ value: 350, unit: 'F' }, 'C') // { value: 176.66…, unit: 'C' }
|
|
706
|
+
* convertTemperature({ value: 180, unit: 'C' }, 'F') // { value: 356, unit: 'F' }
|
|
707
|
+
*/
|
|
708
|
+
declare function convertTemperature(temperature: Temperature, to: TemperatureUnit): Temperature;
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Options every `format*` function takes. Every default is a fixed value,
|
|
712
|
+
* never read from the runtime, so server and client render the same text.
|
|
713
|
+
*/
|
|
714
|
+
type FormatOptions = {
|
|
715
|
+
/**
|
|
716
|
+
* A language tag that `Intl` accepts, such as `'en-US'`, the default. Labels
|
|
717
|
+
* and plurals are English in 0.1; only numbers follow the locale, and they
|
|
718
|
+
* are never grouped. Numbers in other languages come from the runtime's
|
|
719
|
+
* `Intl` data, so the server and client need the same locale support to
|
|
720
|
+
* render the same text. A locale the runtime doesn't support falls back to
|
|
721
|
+
* English, but a string `Intl` rejects throws. `Intl` rejects `'en_US'`, and
|
|
722
|
+
* some BCP 47 tags too, such as `'i-klingon'` and `'x-private'`.
|
|
723
|
+
*/
|
|
724
|
+
locale?: string | undefined;
|
|
725
|
+
/** `'short'` (`tbsp`) or `'long'` (`tablespoons`). Default `'short'`. */
|
|
726
|
+
unitDisplay?: 'short' | 'long' | undefined;
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Options for {@link formatQuantity}, {@link formatQuantityToParts},
|
|
731
|
+
* `formatIngredient`, `formatServings`, and `formatYield`.
|
|
732
|
+
*/
|
|
733
|
+
type FormatQuantityOptions = FormatOptions & {
|
|
734
|
+
/**
|
|
735
|
+
* Write US units and counts as fractions, such as `1 ½`. Default `true`.
|
|
736
|
+
* `false` writes up to two decimals instead (`1.5 cups`, `0.33 cups`), or
|
|
737
|
+
* more for an amount that would otherwise read as 0. Metric units always
|
|
738
|
+
* use decimals.
|
|
739
|
+
*/
|
|
740
|
+
fractions?: boolean | undefined;
|
|
741
|
+
/**
|
|
742
|
+
* `'kitchen'` (default) rounds to amounts you can measure: US units to the
|
|
743
|
+
* nearest ⅛ or ⅓, counts to the nearest ¼, metric units to sensible
|
|
744
|
+
* decimals. `'none'` writes up to three decimals, or more for an amount
|
|
745
|
+
* that would otherwise read as 0.
|
|
746
|
+
*/
|
|
747
|
+
rounding?: 'kitchen' | 'none' | undefined;
|
|
748
|
+
};
|
|
749
|
+
/**
|
|
750
|
+
* One piece of a formatted quantity, in the style of `Intl` `formatToParts`.
|
|
751
|
+
* Joining every `value` gives the {@link formatQuantity} text. Unlike `Intl`,
|
|
752
|
+
* a range's dash is its own `rangeSeparator` part.
|
|
753
|
+
*/
|
|
754
|
+
type QuantityPart = {
|
|
755
|
+
/** May gain members in a minor release, so give an exhaustive `switch` a default case. */
|
|
756
|
+
type: 'amount' | 'rangeSeparator' | 'unit' | 'literal';
|
|
757
|
+
value: string;
|
|
758
|
+
};
|
|
759
|
+
/**
|
|
760
|
+
* Formats a quantity for display, with kitchen rounding by default.
|
|
761
|
+
*
|
|
762
|
+
* - US units (tsp, tbsp, cup, fl oz, pint, quart, gallon, oz, lb) round to the
|
|
763
|
+
* nearest ⅛ or ⅓ and counts to the nearest ¼, written as fractions. An
|
|
764
|
+
* amount below the smallest step is written as a decimal instead, never as 0.
|
|
765
|
+
* - g, mL, cL, and dL use one decimal below 10 and none from 10; kg and L up
|
|
766
|
+
* to two decimals; mg none. Small amounts gain digits rather than show 0.
|
|
767
|
+
* - Ranges use an en dash ("2–3 tbsp"), and the amount and unit are joined by a
|
|
768
|
+
* no-break space (U+00A0).
|
|
769
|
+
* - The unit is singular for 1 and for a fraction below 1 ("1 cup", "½ cup"),
|
|
770
|
+
* and plural otherwise, including decimals and ranges ("0.5 cups",
|
|
771
|
+
* "1 ½ cups", "½–1 cups").
|
|
772
|
+
*
|
|
773
|
+
* For `en` alone or with a region, such as `'en-GB'`, output depends only on
|
|
774
|
+
* the arguments: the locale defaults to `'en-US'`, and those numbers never go
|
|
775
|
+
* through `Intl`. Other tags, including English ones with extensions such as
|
|
776
|
+
* `'en-US-u-nu-arab'`, read the runtime's `Intl` data.
|
|
777
|
+
*
|
|
778
|
+
* @throws {RecipeBookError} `invalid_argument` for an invalid quantity or option, or a quantity with `max` set, as a `Servings` or `Yield` has (print those with `formatServings` and `formatYield`); `unknown_unit` for an unknown unit id.
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* formatQuantity({ amount: 1.5, unit: 'cup' }) // "1 ½ cups"
|
|
782
|
+
* formatQuantity({ amount: { min: 2, max: 3 }, unit: 'tbsp' }) // "2–3 tbsp"
|
|
783
|
+
* formatQuantity({ amount: 118.29, unit: 'ml' }) // "118 mL"
|
|
784
|
+
* formatQuantity({ amount: 0.5, unit: 'tsp' }, { unitDisplay: 'long' }) // "½ teaspoon"
|
|
785
|
+
*/
|
|
786
|
+
declare function formatQuantity(quantity: Quantity, options?: FormatQuantityOptions): string;
|
|
787
|
+
/**
|
|
788
|
+
* Formats a quantity as parts, so a UI can style the amount and unit
|
|
789
|
+
* separately without parsing the string.
|
|
790
|
+
*
|
|
791
|
+
* @throws {RecipeBookError} The same errors as {@link formatQuantity}, including `invalid_argument` for a quantity with `max` set.
|
|
792
|
+
*
|
|
793
|
+
* @example
|
|
794
|
+
* formatQuantityToParts({ amount: { min: 2, max: 3 }, unit: 'tbsp' })
|
|
795
|
+
* // [{ type: 'amount', value: '2' }, { type: 'rangeSeparator', value: '–' },
|
|
796
|
+
* // { type: 'amount', value: '3' }, { type: 'literal', value: '\u00A0' },
|
|
797
|
+
* // { type: 'unit', value: 'tbsp' }]
|
|
798
|
+
*/
|
|
799
|
+
declare function formatQuantityToParts(quantity: Quantity, options?: FormatQuantityOptions): QuantityPart[];
|
|
800
|
+
|
|
801
|
+
/** An ingredient line as text, in pieces for styling and as one `text` line. */
|
|
802
|
+
type FormattedIngredient = {
|
|
803
|
+
/** The formatted quantity, as from `formatQuantity`. Absent for an unquantified line. */
|
|
804
|
+
quantity?: string | undefined;
|
|
805
|
+
/** The name, or the ingredient's `plural` when a bare count takes the plural: "2 eggs", "1–2 eggs". */
|
|
806
|
+
name: string;
|
|
807
|
+
/** The ingredient's note, such as "sifted". Absent when it has none. */
|
|
808
|
+
note?: string | undefined;
|
|
809
|
+
/**
|
|
810
|
+
* The whole line: `'1 ½ cups flour, sifted'`, `'2 eggs'`, `'salt, to taste'`.
|
|
811
|
+
* It never says "optional"; mark optional ingredients in the UI.
|
|
812
|
+
*/
|
|
813
|
+
text: string;
|
|
814
|
+
};
|
|
815
|
+
/**
|
|
816
|
+
* Formats an ingredient line. Accepts a resolved ingredient, or any
|
|
817
|
+
* `{ ingredient, quantity? }`; without `quantity`, the ingredient's own
|
|
818
|
+
* quantity is used.
|
|
819
|
+
*
|
|
820
|
+
* Takes the same options and rounding as `formatQuantity`. A bare count that
|
|
821
|
+
* takes the plural, such as `2`, `1–2`, or `0.1`, uses the ingredient's
|
|
822
|
+
* `plural`, or `name` when it has none. The name follows a no-break space
|
|
823
|
+
* (U+00A0) after any bare count, `1 egg` included, and a regular space after
|
|
824
|
+
* a unit.
|
|
825
|
+
*
|
|
826
|
+
* @throws {RecipeBookError} `invalid_argument` when `item` is not `{ ingredient, quantity? }` or the ingredient's `plural` or `note` is not a string; otherwise the same errors as `formatQuantity`, including `invalid_argument` for a quantity with `max` set.
|
|
827
|
+
*
|
|
828
|
+
* @example
|
|
829
|
+
* formatIngredient(resolved).text // "250 g all-purpose flour"
|
|
830
|
+
* formatIngredient({ ingredient: eggs, quantity: { amount: 2 } }).text // "2 eggs"
|
|
831
|
+
* formatIngredient({ ingredient: salt }) // { name: 'salt', note: 'to taste', text: 'salt, to taste' }
|
|
832
|
+
*/
|
|
833
|
+
declare function formatIngredient(item: {
|
|
834
|
+
ingredient: Ingredient;
|
|
835
|
+
quantity?: Quantity | undefined;
|
|
836
|
+
}, options?: FormatQuantityOptions): FormattedIngredient;
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Formats a number of servings as only the number or range: "4", "4–6",
|
|
840
|
+
* "2 ½". The UI adds the word, as in "Serves 4–6" or "4–6 servings".
|
|
841
|
+
*
|
|
842
|
+
* Numbers format as `formatQuantity` formats a bare count. It takes
|
|
843
|
+
* `formatQuantity`'s options; `unitDisplay` has no effect.
|
|
844
|
+
*
|
|
845
|
+
* Scale with `resolveRecipe`, and print with `formatServings` and `formatYield`, not `formatQuantity`.
|
|
846
|
+
*
|
|
847
|
+
* @throws {RecipeBookError} `invalid_argument` for a value that isn't `{ amount, max? }` with `max` above `amount`, or for an invalid option.
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* formatServings({ amount: 4 }) // "4"
|
|
851
|
+
* formatServings({ amount: 4, max: 6 }) // "4–6"
|
|
852
|
+
* formatServings({ amount: 2.5 }) // "2 ½": 5 servings at scale 0.5
|
|
853
|
+
*/
|
|
854
|
+
declare function formatServings(servings: Servings, options?: FormatQuantityOptions): string;
|
|
855
|
+
/**
|
|
856
|
+
* Formats what a batch makes: the number or range, a no-break space
|
|
857
|
+
* (U+00A0), then the yield's word, as in "24 cookies", "2–3 loaves", or
|
|
858
|
+
* "1 loaf".
|
|
859
|
+
*
|
|
860
|
+
* Numbers format as `formatQuantity` formats a bare count. The word is `unit`
|
|
861
|
+
* for 1 and for a fraction below 1 ("1 loaf", "½ loaf"), and `plural`
|
|
862
|
+
* otherwise, including decimals and ranges ("1 ½ loaves", "1–2 loaves"). A
|
|
863
|
+
* yield read from text, such as `'2 loaves'`, has no `plural`, so its unit
|
|
864
|
+
* prints as written. The word is the author's and is never translated. It
|
|
865
|
+
* takes `formatQuantity`'s options; `unitDisplay` has no effect.
|
|
866
|
+
*
|
|
867
|
+
* Scale with `resolveRecipe`, and print with `formatServings` and `formatYield`, not `formatQuantity`.
|
|
868
|
+
*
|
|
869
|
+
* @throws {RecipeBookError} `invalid_argument` for a value that isn't `{ amount, max?, unit, plural? }` with `max` above `amount` and string words, or for an invalid option.
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* formatYield({ amount: 24, unit: 'cookie', plural: 'cookies' }) // "24 cookies"
|
|
873
|
+
* formatYield({ amount: 1, unit: 'loaf', plural: 'loaves' }) // "1 loaf"
|
|
874
|
+
* formatYield({ amount: 2, max: 3, unit: 'loaf', plural: 'loaves' }) // "2–3 loaves"
|
|
875
|
+
* formatYield({ amount: 1.5, unit: 'loaf', plural: 'loaves' }) // "1 ½ loaves": 3 loaves at scale 0.5
|
|
876
|
+
* formatYield({ amount: 2, unit: 'loaves' }) // "2 loaves"
|
|
877
|
+
*/
|
|
878
|
+
declare function formatYield(yield_: Yield, options?: FormatQuantityOptions): string;
|
|
879
|
+
|
|
880
|
+
/** Options for {@link formatTemperature}. */
|
|
881
|
+
type FormatTemperatureOptions = FormatOptions & {
|
|
882
|
+
/**
|
|
883
|
+
* `'kitchen'` (default) writes whole degrees. `'oven'` rounds to the nearest
|
|
884
|
+
* 10 °C or 25 °F. `'none'` writes up to three decimals.
|
|
885
|
+
*/
|
|
886
|
+
rounding?: 'kitchen' | 'oven' | 'none' | undefined;
|
|
887
|
+
};
|
|
888
|
+
/**
|
|
889
|
+
* Formats a temperature, such as "180 °C" or "350 degrees Fahrenheit". The
|
|
890
|
+
* number and unit are joined by a no-break space (U+00A0).
|
|
891
|
+
*
|
|
892
|
+
* Rounding is `'kitchen'` by default: whole degrees. `'oven'` rounds to the
|
|
893
|
+
* steps oven dials and recipes use, the nearest 10 °C or 25 °F, so a converted
|
|
894
|
+
* 176.67 °C reads "180 °C" and 356 °F reads "350 °F". `'none'` writes up to
|
|
895
|
+
* three decimals.
|
|
896
|
+
*
|
|
897
|
+
* @throws {RecipeBookError} `invalid_argument` for an invalid temperature or option.
|
|
898
|
+
*
|
|
899
|
+
* @example
|
|
900
|
+
* formatTemperature({ value: 176.66, unit: 'C' }, { rounding: 'oven' }) // "180 °C"
|
|
901
|
+
* formatTemperature(convertTemperature({ value: 180, unit: 'C' }, 'F'), { rounding: 'oven' }) // "350 °F"
|
|
902
|
+
* formatTemperature({ value: 350, unit: 'F' }, { unitDisplay: 'long' }) // "350 degrees Fahrenheit"
|
|
903
|
+
*/
|
|
904
|
+
declare function formatTemperature(temperature: Temperature, options?: FormatTemperatureOptions): string;
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Formats a duration in minutes, such as a recipe's `times.cook`: "45 min",
|
|
908
|
+
* "1 hr 15 min", or with `unitDisplay: 'long'`, "1 hour 15 minutes".
|
|
909
|
+
*
|
|
910
|
+
* The duration is rounded to the second and written in days, hours, minutes,
|
|
911
|
+
* and seconds, leaving out zero parts. Each number and unit is joined by a
|
|
912
|
+
* no-break space (U+00A0).
|
|
913
|
+
*
|
|
914
|
+
* @throws {RecipeBookError} `invalid_argument` when `minutes` is not a finite number, 0 or more, or for an invalid option.
|
|
915
|
+
*
|
|
916
|
+
* @example
|
|
917
|
+
* formatDuration(75) // "1 hr 15 min"
|
|
918
|
+
* formatDuration(1.5) // "1 min 30 sec"
|
|
919
|
+
* formatDuration(2880, { unitDisplay: 'long' }) // "2 days"
|
|
920
|
+
* formatDuration(0) // "0 min"
|
|
921
|
+
*/
|
|
922
|
+
declare function formatDuration(minutes: number, options?: FormatOptions): string;
|
|
923
|
+
|
|
924
|
+
/** A temperature read from step text, and whether it is for a fan oven. */
|
|
925
|
+
type TemperatureReading = {
|
|
926
|
+
temperature: Temperature;
|
|
927
|
+
/**
|
|
928
|
+
* True when the text marks it for a fan or convection oven, as in "170C
|
|
929
|
+
* fan". A marker after a segment's closing parenthesis, as in "170°C (about
|
|
930
|
+
* 340°F) fan", marks every reading in the segment. An unmarked reading in
|
|
931
|
+
* parentheses that restates a marked one, in the other unit and within 10 °C,
|
|
932
|
+
* is marked too, as in "170°C fan (about 340°F)". In "160°C fan (350°F)",
|
|
933
|
+
* 350 °F is 177 °C, so it isn't. Otherwise, a marker with other words between
|
|
934
|
+
* it and the reading, as in "325°F for convection", isn't read.
|
|
935
|
+
*/
|
|
936
|
+
fan: boolean;
|
|
937
|
+
};
|
|
938
|
+
/** Step text with nothing recognized in it. */
|
|
939
|
+
type TextSegment = {
|
|
940
|
+
type: 'text';
|
|
941
|
+
text: string;
|
|
942
|
+
};
|
|
943
|
+
/**
|
|
944
|
+
* A temperature in step text. When the author already wrote the setting more
|
|
945
|
+
* than one way, as in "400 degrees F (200 degrees C)" or "190C/170C fan/gas 5",
|
|
946
|
+
* the whole run is one segment: the first reading is `temperature`, the others
|
|
947
|
+
* are `alternatives`, and `text` keeps any gas mark uninterpreted. Readings
|
|
948
|
+
* with the same `fan` value are one setting in both units, within 10 °C of
|
|
949
|
+
* each other, so no two share a unit and a `fan` value.
|
|
950
|
+
*/
|
|
951
|
+
type TemperatureSegment = TemperatureReading & {
|
|
952
|
+
type: 'temperature';
|
|
953
|
+
/** The text exactly as authored, such as `'350°F'` or `'200C/180C fan forced'`. */
|
|
954
|
+
text: string;
|
|
955
|
+
/** The other readings in this segment, in authored order. Often empty. */
|
|
956
|
+
alternatives: TemperatureReading[];
|
|
957
|
+
};
|
|
958
|
+
/**
|
|
959
|
+
* A piece of step text. Recognizes temperatures in 0.1; durations and other
|
|
960
|
+
* kinds may be added in minor releases, so handle unknown `type`s as text.
|
|
961
|
+
*/
|
|
962
|
+
type StepSegment = TextSegment | TemperatureSegment;
|
|
963
|
+
/** Options for {@link segmentStepText}. */
|
|
964
|
+
type SegmentStepTextOptions = {
|
|
965
|
+
/**
|
|
966
|
+
* Scale for a temperature written with a degree sign or the word "degree" or
|
|
967
|
+
* "degrees" but no scale, such as "Bake at 350 degrees", "Bake in a
|
|
968
|
+
* 350-degree oven." or "Preheat oven to 350°." Default: none, so those stay
|
|
969
|
+
* text. A number with no degree sign or word, as in "Preheat oven to 350.",
|
|
970
|
+
* stays text even with a scale. With one, such a temperature counts only
|
|
971
|
+
* after a word such as "at" or "to", before "oven", or when a reading in the
|
|
972
|
+
* other unit restates it, as in "350° (175°C)", so "rotate the pan 180
|
|
973
|
+
* degrees" stays text.
|
|
974
|
+
*/
|
|
975
|
+
assumeTemperatureUnit?: TemperatureUnit | undefined;
|
|
976
|
+
};
|
|
977
|
+
/**
|
|
978
|
+
* Splits step text into plain text and the temperatures in it. Joining every
|
|
979
|
+
* segment's `text` gives back the input exactly.
|
|
980
|
+
*
|
|
981
|
+
* @throws {RecipeBookError} `invalid_argument` for text that isn't a string or an unknown `assumeTemperatureUnit`.
|
|
982
|
+
*
|
|
983
|
+
* @example
|
|
984
|
+
* segmentStepText('Bake at 350°F for 20 minutes.')
|
|
985
|
+
* // [
|
|
986
|
+
* // { type: 'text', text: 'Bake at ' },
|
|
987
|
+
* // { type: 'temperature', text: '350°F', temperature: { value: 350, unit: 'F' }, fan: false, alternatives: [] },
|
|
988
|
+
* // { type: 'text', text: ' for 20 minutes.' },
|
|
989
|
+
* // ]
|
|
990
|
+
*/
|
|
991
|
+
declare function segmentStepText(text: string, options?: SegmentStepTextOptions): StepSegment[];
|
|
992
|
+
|
|
993
|
+
export { Amount, Ingredient, ParseResult, Quantity, Range, Recipe, Servings, StandardSchemaV1, StandardTypedV1, Step, System, UnitAlias, UnitDefinition, UnitId, UnitIdOf, Yield, canConvert, convert, convertTemperature, createRecipeSchema, findUnit, formatDuration, formatIngredient, formatQuantity, formatQuantityToParts, formatServings, formatTemperature, formatYield, getUnit, parseIngredientLine, parseQuantity, parseRecipe, recipeSchema, resolveIngredient, resolveRecipe, scaleQuantity, segmentStepText, toSystem };
|
|
994
|
+
export type { CustomTimeInput, DurationInput, EquipmentInput, FormatOptions, FormatQuantityOptions, FormatTemperatureOptions, FormattedIngredient, IngredientInput, IngredientLine, IngredientSectionInput, NoteInput, NutrientInput, NutritionInput, ParseRecipeOptions, QuantityInput, QuantityOrigin, QuantityPart, QuantityString, RecipeInput, RecipeSchema, ResolveIngredientOptions, ResolveRecipeOptions, ResolvedIngredient, ResolvedRecipe, SegmentStepTextOptions, ServingsInput, StepInput, StepSectionInput, StepSegment, Temperature, TemperatureReading, TemperatureSegment, TemperatureUnit, TextSegment, TimesInput, YieldInput };
|