@jclind/ingredient-parser 1.2.13 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,80 +1,136 @@
1
- # `@jclind/ingredient-parser`
1
+ # @jclind/ingredient-parser
2
2
 
3
- A package for parsing ingredient strings and retrieving data about the ingredient.
3
+ [![npm version](https://img.shields.io/npm/v/@jclind/ingredient-parser)](https://www.npmjs.com/package/@jclind/ingredient-parser)
4
+ [![license](https://img.shields.io/npm/l/@jclind/ingredient-parser)](https://github.com/jclind/ingredient-parser/blob/main/LICENSE)
4
5
 
5
- ## About
6
- This package is built with [recipe-ingredient-parser-v3](https://www.npmjs.com/package/recipe-ingredient-parser-v3) and also returns ingredient data along with parsing the ingredient. If you don't need the ingredient data, just the parsed ingredient, I recommend using [recipe-ingredient-parser-v3](https://www.npmjs.com/package/recipe-ingredient-parser-v3).
6
+ A TypeScript package for parsing ingredient strings and retrieving structured ingredient data from the Spoonacular API.
7
7
 
8
- ## Installation:
9
- `npm install @jclind/ingredient-parser`
10
-
11
- ## Usage
8
+ Built on top of [recipe-ingredient-parser-v3](https://www.npmjs.com/package/recipe-ingredient-parser-v3). If you only need ingredient parsing without ingredient metadata, nutrition, or API lookups, you may prefer using that package directly.
9
+
10
+ ## Installation
11
+
12
+ ```sh
13
+ npm install @jclind/ingredient-parser
12
14
  ```
13
- import { parseIngredient } from '@jclind/ingredient-parser';
14
15
 
15
- const ingredientString = '1 cup rice, washed';
16
- const apiKey = 'YOUR_API_KEY';
17
- const options = { returnNutritionData: true };
18
- parseIngredient(ingredientString, apiKey, options);
16
+ ## Quick Start
17
+
18
+ ```ts
19
+ import { parseIngredient } from '@jclind/ingredient-parser'
20
+
21
+ const ingredientString = '1 cup rice, washed'
22
+ const apiKey = 'YOUR_API_KEY'
23
+
24
+ const result = await parseIngredient(ingredientString, apiKey, {
25
+ returnNutritionData: true,
26
+ })
27
+
28
+ console.log(result)
19
29
  ```
20
- Returns an object `{id: (randomly generated id unique to every request), parsedIngredient, ingredientData}` with the following properties/values.
21
30
 
22
- `parsedIngredient`:
31
+ ## API
32
+
33
+ ### `parseIngredient(ingredientString, SPOONACULAR_API_KEY, options?)`
34
+
35
+ Parses an ingredient string and returns both the parsed ingredient data and ingredient metadata retrieved from Spoonacular.
36
+
37
+ ```ts
38
+ import { parseIngredient } from '@jclind/ingredient-parser'
39
+
40
+ parseIngredient(
41
+ ingredientString: string,
42
+ SPOONACULAR_API_KEY: string,
43
+ options?: {
44
+ returnNutritionData?: boolean
45
+ }
46
+ ): Promise<IngredientResponseType>
23
47
  ```
48
+
49
+ | Parameter | Type | Required | Description |
50
+ | --------------------- | -------- | -------- | ------------------------------------------------------- |
51
+ | `ingredientString` | `string` | Yes | Ingredient string formatted like `2 cups onions, diced` |
52
+ | `SPOONACULAR_API_KEY` | `string` | Yes | Your Spoonacular API key |
53
+ | `options` | `object` | No | Additional parsing options |
54
+
55
+ ### Options
56
+
57
+ | Option | Type | Default | Description |
58
+ | --------------------- | --------- | ------- | ------------------------------------------------------- |
59
+ | `returnNutritionData` | `boolean` | `false` | Includes Spoonacular nutrition data in `ingredientData` |
60
+
61
+ ---
62
+
63
+ ## Response Structure
64
+
65
+ Returns an object with the following shape:
66
+
67
+ ```ts
24
68
  {
25
- quantity: 1,
26
- unit: 'cup',
27
- unitPlural: 'cups',
28
- symbol: 'c',
29
- ingredient: 'rice',
30
- originalIngredientString: '1 cup rice, washed'
31
- minQty: 1,
32
- maxQty: 1,
33
- comment: 'washed'
69
+ id: string
70
+ parsedIngredient: ParsedIngredientType
71
+ ingredientData: IngredientDataType | null
72
+ error?: {
73
+ message: string
74
+ }
34
75
  }
35
76
  ```
36
77
 
37
- `ingredientData:`
38
- ```
78
+ ---
79
+
80
+ ### `parsedIngredient`
81
+
82
+ ```ts
39
83
  {
40
- _id: '63caeabf4762c87be39c3795',
41
- ingredientId: 20444,
42
- originalName: 'rice',
43
- name: 'rice',
44
- amount: 1,
45
- possibleUnits: [ 'g', 'oz', 'cup' ],
46
- consistency: 'solid',
47
- shoppingListUnits: [ 'ounces', 'pounds' ],
48
- aisle: 'Pasta and Rice',
49
- image: 'uncooked-white-rice.png',
50
- imagePath: 'https://spoonacular.com/cdn/ingredients_100x100/uncooked-white-rice.png',
51
- nutrition?: {
52
- nutrients: [Array],
53
- properties: [Array],
54
- flavonoids: [Array],
55
- caloricBreakdown: [Object],
56
- weightPerServing: [Object]
57
- },
58
- names: [ 'rice' ],
59
- dateAdded: 1674242751000,
60
- totalPriceUSACents: 75.71
84
+ quantity: 1,
85
+ unit: 'cup',
86
+ unitPlural: 'cups',
87
+ symbol: 'c',
88
+ ingredient: 'rice',
89
+ originalIngredientString: '1 cup rice, washed',
90
+ minQty: 1,
91
+ maxQty: 1,
92
+ comment: 'washed'
61
93
  }
62
94
  ```
63
95
 
64
- ## API
65
- `parseIngredient(ingredientString: string, SPOONACULAR_API_KEY: string) => { parsedIngredient, IngredientData }` Takes an ingredient string and a spoonacular API key and returns the parsed ingredient and the ingredient data.
96
+ ---
66
97
 
67
- - `ingredientString` (string) [required] : preferably formatted as such: (quantity) (unit) (ingredient), (comment separated by a comment) i.e. 2 cups onions, diced
68
- - `SPOONACULAR_API_KEY` (string) [required] : your unique [spoonacular](https://spoonacular.com/food-api) API key.
69
- - `options` (object) [optional] : an object containing additional options. Currently, the only supported option is `returnNutritionData`, which, if set to `true`, will include nutrition data in the ingredientData object.
98
+ ### `ingredientData`
70
99
 
71
- Note: You need to sign up for a free API key from [spoonacular website](https://spoonacular.com/food-api).
100
+ ```ts
101
+ {
102
+ _id: '63caeabf4762c87be39c3795',
103
+ ingredientId: 20444,
104
+ originalName: 'rice',
105
+ name: 'rice',
106
+ amount: 1,
107
+ possibleUnits: ['g', 'oz', 'cup'],
108
+ consistency: 'solid',
109
+ shoppingListUnits: ['ounces', 'pounds'],
110
+ aisle: 'Pasta and Rice',
111
+ image: 'uncooked-white-rice.png',
112
+ imagePath: 'https://spoonacular.com/cdn/ingredients_100x100/uncooked-white-rice.png',
113
+ nutrition?: {
114
+ nutrients: [Array],
115
+ properties: [Array],
116
+ flavonoids: [Array],
117
+ caloricBreakdown: [Object],
118
+ weightPerServing: [Object]
119
+ },
120
+ dateAdded: 1674242751000,
121
+ totalPriceUSACents: 75.71
122
+ }
123
+ ```
72
124
 
73
125
  ## Error Handling
74
- If the passed ingredient isn't formatted correctly or the ingredient is unknown, an error will be returned with the original object.
75
- With invalid text:
76
- ```
126
+
127
+ If the ingredient cannot be identified or the API key is invalid, the function returns an error object while still including the parsed ingredient structure.
128
+
129
+ ### Unknown Ingredient
130
+
131
+ ```ts
77
132
  parseIngredient('Invalid Text', YOUR_API_KEY)
133
+
78
134
  /*
79
135
  {
80
136
  error: { message: 'No Data Found, unknown ingredient: invalid text' },
@@ -93,9 +149,12 @@ parseIngredient('Invalid Text', YOUR_API_KEY)
93
149
  }
94
150
  */
95
151
  ```
96
- With an inavlid API_KEY:
97
- ```
152
+
153
+ ### Invalid API Key
154
+
155
+ ```ts
98
156
  parseIngredient('1 cup rice', INVALID_API_KEY)
157
+
99
158
  /*
100
159
  {
101
160
  error: { message: 'API Key Not Valid' },
@@ -106,7 +165,7 @@ parseIngredient('1 cup rice', INVALID_API_KEY)
106
165
  unitPlural: 'cups',
107
166
  symbol: 'c',
108
167
  ingredient: 'rice',
109
- originalIngredientString: '1 cup rice'
168
+ originalIngredientString: '1 cup rice',
110
169
  minQty: 1,
111
170
  maxQty: 1,
112
171
  comment: null
@@ -115,16 +174,38 @@ parseIngredient('1 cup rice', INVALID_API_KEY)
115
174
  */
116
175
  ```
117
176
 
118
- ## Typescript
119
- Typescript definitions are also included in the ingredient-parser package:
120
- ```
121
- import { parseIngredient, ParsedIngredientType, IngredientDataType, IngredientResponseType, } from '@jclind/ingredient-parser';
177
+ ## TypeScript
178
+
179
+ This package ships with full TypeScript definitions. No additional `@types` package required.
122
180
 
123
- const ingredientString: string = '1 cup rice, washed';
124
- const apiKey: string = 'YOUR_API_KEY';
181
+ ```ts
182
+ import {
183
+ parseIngredient,
184
+ ParsedIngredientType,
185
+ IngredientDataType,
186
+ IngredientResponseType,
187
+ } from '@jclind/ingredient-parser'
125
188
 
126
- const parsed: IngredientResponseType = parseIngredient(ingredientString, apiKey);
189
+ const ingredientString: string = '1 cup rice, washed'
190
+ const apiKey: string = 'YOUR_API_KEY'
191
+
192
+ const parsed: IngredientResponseType = await parseIngredient(
193
+ ingredientString,
194
+ apiKey,
195
+ )
127
196
 
128
197
  const parsedIngredient: ParsedIngredientType = parsed.parsedIngredient
129
- const ingredientData: IngredientDataType = parsed.ingredientData
198
+
199
+ const ingredientData: IngredientDataType | null = parsed.ingredientData
130
200
  ```
201
+
202
+ ## Notes
203
+
204
+ - A valid Spoonacular API key is required for ingredient lookups.
205
+ - Ingredient parsing is powered by `recipe-ingredient-parser-v3`.
206
+ - Nutrition data is optional and disabled by default to reduce API usage.
207
+ - Parsed ingredient results are returned even when ingredient lookup fails.
208
+
209
+ ## Issues & Contributing
210
+
211
+ Found a bug or have a feature request? [Open an issue](https://github.com/jclind/ingredient-parser/issues) on GitHub. PRs are welcome.
@@ -5,15 +5,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createIngredientServerHttp = exports.spoonacularHttp = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
+ // Webpack 5 can resolve require("axios") to the ESM entry without setting
9
+ // __esModule, causing __importDefault to double-wrap it. The result is that
10
+ // _axios is the module namespace object rather than the axios instance, so
11
+ // .create lands one level deeper than expected.
12
+ const axios = typeof axios_1.default.create === 'function'
13
+ ? axios_1.default
14
+ : axios_1.default.default;
8
15
  const DEFAULT_SERVER_URL = 'https://ingredient-parser-service-production-2635.up.railway.app';
9
- exports.spoonacularHttp = axios_1.default.create({
16
+ exports.spoonacularHttp = axios.create({
10
17
  baseURL: 'https://api.spoonacular.com/food/ingredients/',
11
18
  headers: {
12
19
  'Content-type': 'application/json',
13
20
  },
14
21
  timeout: 8000,
15
22
  });
16
- const createIngredientServerHttp = (serverUrl) => axios_1.default.create({
23
+ const createIngredientServerHttp = (serverUrl) => axios.create({
17
24
  baseURL: serverUrl || DEFAULT_SERVER_URL,
18
25
  headers: {
19
26
  'Content-type': 'application/json',
@@ -1,4 +1,5 @@
1
1
  import { IngredientData } from '../../types.js';
2
- export declare const parseAndEnrich: (ingredientString: string, spoonacularApiKey: string, serverUrl?: string) => Promise<IngredientData | null>;
2
+ export declare const getIngredientFromServer: (name: string, serverUrl?: string) => Promise<IngredientData | null>;
3
+ export declare const saveIngredientToServer: (name: string, ingredientData: IngredientData, serverUrl?: string) => void;
3
4
  export declare const searchIngredient: (name: string, spoonacularAPIKey: string) => Promise<import("axios").AxiosResponse<any, any, {}>>;
4
5
  export declare const getIngredientInformation: (ingrId: number, unit: boolean, spoonacularAPIKey: string) => Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -8,16 +8,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
11
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getIngredientInformation = exports.searchIngredient = exports.parseAndEnrich = void 0;
16
- const axios_1 = __importDefault(require("axios"));
12
+ exports.getIngredientInformation = exports.searchIngredient = exports.saveIngredientToServer = exports.getIngredientFromServer = void 0;
13
+ const axios_1 = require("axios");
17
14
  const http_js_1 = require("./http.js");
18
15
  function handleRequestError(error) {
19
16
  var _a, _b, _c;
20
- if (axios_1.default.isAxiosError(error)) {
17
+ if ((0, axios_1.isAxiosError)(error)) {
21
18
  const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
22
19
  const code = (_c = (_b = error.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.code;
23
20
  if (status === 401 || code === 401)
@@ -30,21 +27,28 @@ function handleRequestError(error) {
30
27
  }
31
28
  throw new Error('Network error, please try again');
32
29
  }
33
- const parseAndEnrich = (ingredientString, spoonacularApiKey, serverUrl) => __awaiter(void 0, void 0, void 0, function* () {
30
+ const getIngredientFromServer = (name, serverUrl) => __awaiter(void 0, void 0, void 0, function* () {
34
31
  var _a;
35
32
  try {
36
33
  const serverHttp = (0, http_js_1.createIngredientServerHttp)(serverUrl);
37
- const response = yield serverHttp.post('/parse', {
38
- ingredientString,
39
- spoonacularApiKey,
40
- });
34
+ const response = yield serverHttp.get(`/ingredient/${encodeURIComponent(name)}`);
41
35
  return (_a = response.data.data) !== null && _a !== void 0 ? _a : null;
42
36
  }
43
- catch (error) {
44
- handleRequestError(error);
37
+ catch (_b) {
38
+ // Treat any server error as a cache miss and fall through to Spoonacular
39
+ return null;
45
40
  }
46
41
  });
47
- exports.parseAndEnrich = parseAndEnrich;
42
+ exports.getIngredientFromServer = getIngredientFromServer;
43
+ const saveIngredientToServer = (name, ingredientData, serverUrl) => {
44
+ const serverHttp = (0, http_js_1.createIngredientServerHttp)(serverUrl);
45
+ serverHttp
46
+ .post('/ingredient', { name, ingredientData })
47
+ .catch(() => {
48
+ // Fire-and-forget — a failed write doesn't affect the caller
49
+ });
50
+ };
51
+ exports.saveIngredientToServer = saveIngredientToServer;
48
52
  const searchIngredient = (name, spoonacularAPIKey) => __awaiter(void 0, void 0, void 0, function* () {
49
53
  try {
50
54
  return yield http_js_1.spoonacularHttp.get(`search?query=${name}&number=1&apiKey=${spoonacularAPIKey}`);
@@ -1,2 +1,2 @@
1
1
  import { IngredientData } from '../../types.js';
2
- export declare function getIngredientInfo(ingredientString: string, spoonacularAPIKey: string, serverUrl?: string): Promise<IngredientData | null>;
2
+ export declare function getIngredientInfo(ingredientName: string, spoonacularAPIKey: string, serverUrl?: string): Promise<IngredientData | null>;
@@ -11,13 +11,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.getIngredientInfo = getIngredientInfo;
13
13
  const requests_js_1 = require("../api/requests.js");
14
- function getIngredientInfo(ingredientString, spoonacularAPIKey, serverUrl) {
14
+ const getSpoonacularIngrData_js_1 = require("./getSpoonacularIngrData.js");
15
+ function getIngredientInfo(ingredientName, spoonacularAPIKey, serverUrl) {
15
16
  return __awaiter(this, void 0, void 0, function* () {
16
- try {
17
- return yield (0, requests_js_1.parseAndEnrich)(ingredientString, spoonacularAPIKey, serverUrl);
18
- }
19
- catch (error) {
20
- throw new Error(error.message || 'Failed to fetch ingredient data');
21
- }
17
+ const cached = yield (0, requests_js_1.getIngredientFromServer)(ingredientName, serverUrl);
18
+ if (cached)
19
+ return cached;
20
+ const spoonacularData = yield (0, getSpoonacularIngrData_js_1.getSpoonacularIngrData)(ingredientName, spoonacularAPIKey);
21
+ if (!spoonacularData)
22
+ return null;
23
+ (0, requests_js_1.saveIngredientToServer)(ingredientName, spoonacularData, serverUrl);
24
+ return spoonacularData;
22
25
  });
23
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclind/ingredient-parser",
3
- "version": "1.2.13",
3
+ "version": "1.3.0",
4
4
  "description": "Parses given sentence including ingredient information and attempts to return quantity, measurement and ingredient data",
5
5
  "keywords": [
6
6
  "recipe",