keepa-api 0.2.3 → 0.3.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 (69) hide show
  1. package/README.md +31 -3
  2. package/dist/index.cjs +398 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +206 -0
  5. package/dist/index.d.ts +206 -8
  6. package/dist/index.js +346 -7
  7. package/dist/index.js.map +1 -1
  8. package/package.json +14 -5
  9. package/dist/client.d.ts +0 -21
  10. package/dist/client.d.ts.map +0 -1
  11. package/dist/client.js +0 -38
  12. package/dist/client.js.map +0 -1
  13. package/dist/core/error.d.ts +0 -27
  14. package/dist/core/error.d.ts.map +0 -1
  15. package/dist/core/error.js +0 -61
  16. package/dist/core/error.js.map +0 -1
  17. package/dist/core/request.d.ts +0 -15
  18. package/dist/core/request.d.ts.map +0 -1
  19. package/dist/core/request.js +0 -36
  20. package/dist/core/request.js.map +0 -1
  21. package/dist/core/resource.d.ts +0 -6
  22. package/dist/core/resource.d.ts.map +0 -1
  23. package/dist/core/resource.js +0 -7
  24. package/dist/core/resource.js.map +0 -1
  25. package/dist/index.d.ts.map +0 -1
  26. package/dist/lib/asin.d.ts +0 -10
  27. package/dist/lib/asin.d.ts.map +0 -1
  28. package/dist/lib/asin.js +0 -19
  29. package/dist/lib/asin.js.map +0 -1
  30. package/dist/lib/marketplace.d.ts +0 -17
  31. package/dist/lib/marketplace.d.ts.map +0 -1
  32. package/dist/lib/marketplace.js +0 -27
  33. package/dist/lib/marketplace.js.map +0 -1
  34. package/dist/resources/index.d.ts +0 -2
  35. package/dist/resources/index.d.ts.map +0 -1
  36. package/dist/resources/index.js +0 -2
  37. package/dist/resources/index.js.map +0 -1
  38. package/dist/resources/products/constant.d.ts +0 -7
  39. package/dist/resources/products/constant.d.ts.map +0 -1
  40. package/dist/resources/products/constant.js +0 -15
  41. package/dist/resources/products/constant.js.map +0 -1
  42. package/dist/resources/products/error.d.ts +0 -10
  43. package/dist/resources/products/error.d.ts.map +0 -1
  44. package/dist/resources/products/error.js +0 -14
  45. package/dist/resources/products/error.js.map +0 -1
  46. package/dist/resources/products/index.d.ts +0 -6
  47. package/dist/resources/products/index.d.ts.map +0 -1
  48. package/dist/resources/products/index.js +0 -8
  49. package/dist/resources/products/index.js.map +0 -1
  50. package/dist/resources/products/product.raw.type.d.ts +0 -36
  51. package/dist/resources/products/product.raw.type.d.ts.map +0 -1
  52. package/dist/resources/products/product.raw.type.js +0 -6
  53. package/dist/resources/products/product.raw.type.js.map +0 -1
  54. package/dist/resources/products/product.type.d.ts +0 -48
  55. package/dist/resources/products/product.type.d.ts.map +0 -1
  56. package/dist/resources/products/product.type.js +0 -2
  57. package/dist/resources/products/product.type.js.map +0 -1
  58. package/dist/resources/products/product.util.d.ts +0 -15
  59. package/dist/resources/products/product.util.d.ts.map +0 -1
  60. package/dist/resources/products/product.util.js +0 -56
  61. package/dist/resources/products/product.util.js.map +0 -1
  62. package/dist/resources/products/products.d.ts +0 -9
  63. package/dist/resources/products/products.d.ts.map +0 -1
  64. package/dist/resources/products/products.js +0 -40
  65. package/dist/resources/products/products.js.map +0 -1
  66. package/dist/version.d.ts +0 -2
  67. package/dist/version.d.ts.map +0 -1
  68. package/dist/version.js +0 -2
  69. package/dist/version.js.map +0 -1
package/README.md CHANGED
@@ -79,17 +79,46 @@ The client reads `process.env.KEEPA_API_KEY` if you don't pass `apiKey` explicit
79
79
  |-------|------|---------|-------|
80
80
  | `asins` | `string[]` | (required) | Validated + uppercased. Throws on malformed input. |
81
81
  | `marketplace` | `'US' \| 'GB' \| 'DE' \| 'FR' \| 'JP' \| 'CA' \| 'IT' \| 'ES' \| 'IN' \| 'MX' \| 'BR'` | `'US'` | Case-insensitive. Throws on unknown. |
82
- | `days` | `number` | `1` | Days of price history. Must be a positive integer (validated pre-flight). |
82
+ | `days` | `number` | `1` | Days of price history to scope csv data when `history: true`. Must be a positive integer (validated pre-flight). |
83
+ | `history` | `boolean` | `false` | When `true`, requests Keepa's csv history matrix and parses it into `product.history.price.{amazon,list}` (and the scalar `price`/`listPrice` counterparts) on each returned product. When `false`, those fields are empty/null. Affects token cost — leave off when you don't need it. |
83
84
 
84
85
  The SDK maps Keepa's raw wire shape into a friendlier `KeepaProduct`:
85
86
 
86
87
  - `images: string[]` — full image URLs (region-neutral CDN). Replaces Keepa's awkward `imagesCSV` string.
87
88
  - `bsr: number | null` — most recent real BSR for the product's `rootCategory`. `null` when missing or every history entry is Keepa's `-1` "no data captured" sentinel.
89
+ - `price: number | null` / `listPrice: number | null` — latest Amazon price and list price in the marketplace's **major unit** (dollars for US, pounds for GB, yen for JP, etc.). Derived from the last entry of `history.price.amazon` / `history.price.list`. `null` when `history: false` was used or Keepa has no data.
90
+ - `history.price.amazon: PriceHistoryEntry[]` / `history.price.list: PriceHistoryEntry[]` — full parsed price series. Empty `[]` when `history: false` was used. Prices are in the marketplace's major unit (same as the scalar fields).
88
91
 
89
92
  The raw `salesRanks` record is preserved for consumers that need to walk the full rank history. Other Keepa fields (`title`, `parentAsin`, `categoryTree`, `variations`, `features`, …) pass through unchanged.
90
93
 
91
94
  **Stub records:** Keepa returns one record per requested ASIN even when it has no data — these stubs have `title === null`. Filter with `isFoundProduct` (below).
92
95
 
96
+ ### Price and price history
97
+
98
+ Every `KeepaProduct` has the price fields — what changes with the `history` flag is whether they're populated:
99
+
100
+ ```ts
101
+ // Default: history fields exist but are empty/null. Cheapest call.
102
+ const [product] = await keepa.products.list({ asins: ['B00MNV8E0C'] });
103
+ product.price; // null
104
+ product.history.price.amazon; // []
105
+
106
+ // With history: arrays filled, scalar fields derive from the latest entry.
107
+ const [detailed] = await keepa.products.list({
108
+ asins: ['B00MNV8E0C'],
109
+ history: true,
110
+ days: 30,
111
+ });
112
+ detailed.price; // 18.99 — latest Amazon price (USD dollars)
113
+ detailed.listPrice; // 29.99 — latest list price (USD dollars)
114
+ detailed.history.price.amazon; // PriceHistoryEntry[] — Amazon's own price over time (csv[0])
115
+ detailed.history.price.list; // PriceHistoryEntry[] — list price / MSRP over time (csv[4])
116
+ ```
117
+
118
+ Each `PriceHistoryEntry` is `{ timestamp: Date, price: number }` where `price` is in the marketplace's major unit (dollars for `'US'`, pounds for `'GB'`, yen for `'JP'`, etc. — the consumer chose the marketplace, so the currency is known). Keepa's `-1` "no data captured" sentinel entries are filtered out, so iterating the arrays only yields real price points.
119
+
120
+ If you need a csv type other than `CsvType.AMAZON` or `CsvType.LISTPRICE` (e.g. `CsvType.NEW`, `CsvType.USED`, `CsvType.REFURBISHED`, `CsvType.RATING`, …) — pull the row off the raw Keepa response and pass it through `parsePriceHistory`. The full 36-value mapping is exported as `CsvType` (matches Keepa's own enum names exactly).
121
+
93
122
  ### Helpers
94
123
 
95
124
  ```ts
@@ -106,9 +135,8 @@ for (const product of real) {
106
135
  | Helper | Signature | Returns |
107
136
  |--------|-----------|---------|
108
137
  | `isFoundProduct(product)` | `(product: KeepaProduct) => boolean` | `true` only if Keepa returned real data (stubs have `title === null`). |
109
- | `parseImagesCsv(csv)` | `(csv: string \| undefined) => string[]` | Build the full image-URL list from a raw Keepa imagesCSV. Used internally to fill `images`; exported for advanced use. |
110
138
  | `extractBsr(salesRanks, rootCategory)` | `(salesRanks: Record<string, number[]> \| undefined, rootCategory: number \| undefined) => number \| null` | Most recent real BSR from Keepa's raw `[ts, rank, ...]` history. Used internally to fill `bsr`; exported for advanced use. |
111
- | `extractImageUrl(imagesCSV)` | `(imagesCSV: string \| undefined) => string \| null` | Single URL equivalent to `parseImagesCsv(imagesCSV)[0] ?? null`. Exported for advanced use. |
139
+ | `parsePriceHistory(series)` | `(series: number[] \| undefined) => PriceHistoryEntry[]` | Parse one row of Keepa's csv matrix (e.g. `csv[CsvType.AMAZON]`, `csv[CsvType.LISTPRICE]`) into `{ timestamp, price }` entries — `price` in the marketplace's major unit. `-1` sentinels filtered out. Used internally to fill `history.price.*`; exported so callers can parse other csv types via `CsvType`. |
112
140
 
113
141
  ### ASIN validation
114
142
 
package/dist/index.cjs ADDED
@@ -0,0 +1,398 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AMAZON_IMAGE_BASE: () => AMAZON_IMAGE_BASE,
24
+ APIError: () => APIError,
25
+ APIResource: () => APIResource,
26
+ ASIN_LENGTH: () => ASIN_LENGTH,
27
+ ASIN_REGEX: () => ASIN_REGEX,
28
+ AuthenticationError: () => AuthenticationError,
29
+ CsvType: () => CsvType,
30
+ DEFAULT_DAYS: () => DEFAULT_DAYS,
31
+ KEEPA_NO_DATA_SENTINEL: () => KEEPA_NO_DATA_SENTINEL,
32
+ KeepaClient: () => KeepaClient,
33
+ KeepaError: () => KeepaError,
34
+ MARKETPLACE_DOMAINS: () => MARKETPLACE_DOMAINS,
35
+ NetworkError: () => NetworkError,
36
+ PRODUCT_LIST_CONTEXT: () => PRODUCT_LIST_CONTEXT,
37
+ PRODUCT_PATH: () => PRODUCT_PATH,
38
+ ProductNotFoundError: () => ProductNotFoundError,
39
+ Products: () => Products,
40
+ RateLimitError: () => RateLimitError,
41
+ VERSION: () => VERSION,
42
+ default: () => KeepaClient,
43
+ extractBsr: () => extractBsr,
44
+ isFoundProduct: () => isFoundProduct,
45
+ isValidAsin: () => isValidAsin,
46
+ normalizeAsins: () => normalizeAsins,
47
+ parsePriceHistory: () => parsePriceHistory,
48
+ resolveDomainId: () => resolveDomainId
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/core/error.ts
53
+ function scrubApiKey(text) {
54
+ return text.replace(/([?&])key=[^&\s]*/gi, "$1key=REDACTED");
55
+ }
56
+ var KeepaError = class extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "KeepaError";
60
+ }
61
+ };
62
+ var APIError = class _APIError extends KeepaError {
63
+ status;
64
+ context;
65
+ body;
66
+ constructor(status, context, body, message) {
67
+ const safeBody = scrubApiKey(body);
68
+ super(scrubApiKey(message ?? `Keepa ${context} error (${status}): ${safeBody}`));
69
+ this.name = "APIError";
70
+ this.status = status;
71
+ this.context = context;
72
+ this.body = safeBody;
73
+ }
74
+ static async from(response, context) {
75
+ const body = await response.text().catch(() => "");
76
+ switch (response.status) {
77
+ case 429:
78
+ return new RateLimitError(context, body);
79
+ case 401:
80
+ return new AuthenticationError(context, body);
81
+ default:
82
+ return new _APIError(response.status, context, body);
83
+ }
84
+ }
85
+ };
86
+ var RateLimitError = class extends APIError {
87
+ constructor(context, body) {
88
+ super(429, context, body, "Keepa rate limit exceeded, please wait or upgrade plan");
89
+ this.name = "RateLimitError";
90
+ }
91
+ };
92
+ var AuthenticationError = class extends APIError {
93
+ constructor(context, body) {
94
+ super(
95
+ 401,
96
+ context,
97
+ body,
98
+ `Keepa authentication failed for ${context}: invalid or missing API key`
99
+ );
100
+ this.name = "AuthenticationError";
101
+ }
102
+ };
103
+ var NetworkError = class extends KeepaError {
104
+ context;
105
+ cause;
106
+ constructor(context, cause) {
107
+ const raw = cause instanceof Error ? cause.message : String(cause);
108
+ super(`Keepa ${context} network error: ${scrubApiKey(raw)}`);
109
+ this.name = "NetworkError";
110
+ this.context = context;
111
+ this.cause = cause;
112
+ }
113
+ };
114
+
115
+ // src/core/request.ts
116
+ function buildUrl(baseURL, path, query) {
117
+ if (!query) return `${baseURL}${path}`;
118
+ const parts = [];
119
+ for (const [key, value] of Object.entries(query)) {
120
+ if (value === void 0) continue;
121
+ const stringValue = Array.isArray(value) ? value.join(",") : String(value);
122
+ parts.push(`${encodeURIComponent(key)}=${encodeKeepaValue(stringValue)}`);
123
+ }
124
+ return parts.length ? `${baseURL}${path}?${parts.join("&")}` : `${baseURL}${path}`;
125
+ }
126
+ function encodeKeepaValue(value) {
127
+ return encodeURIComponent(value).replace(/%2C/g, ",");
128
+ }
129
+ async function request(config, args) {
130
+ const query = { key: config.apiKey, ...args.query };
131
+ const url = buildUrl(config.baseURL, args.path, query);
132
+ let res;
133
+ try {
134
+ res = await config.fetch(url);
135
+ } catch (cause) {
136
+ throw new NetworkError(args.context, cause);
137
+ }
138
+ if (!res.ok) throw await APIError.from(res, args.context);
139
+ return await res.json();
140
+ }
141
+
142
+ // src/core/resource.ts
143
+ var APIResource = class {
144
+ _client;
145
+ constructor(client) {
146
+ this._client = client;
147
+ }
148
+ };
149
+
150
+ // src/lib/marketplace.ts
151
+ var MARKETPLACE_DOMAINS = {
152
+ US: 1,
153
+ GB: 2,
154
+ DE: 3,
155
+ FR: 4,
156
+ JP: 5,
157
+ CA: 6,
158
+ IT: 8,
159
+ ES: 9,
160
+ IN: 10,
161
+ MX: 11,
162
+ BR: 12
163
+ };
164
+ var SUPPORTED_LIST = Object.keys(MARKETPLACE_DOMAINS).join(", ");
165
+ function resolveDomainId(marketplace) {
166
+ const key = (marketplace ?? "US").toUpperCase();
167
+ const domainId = MARKETPLACE_DOMAINS[key];
168
+ if (!domainId) {
169
+ throw new Error(
170
+ `Invalid marketplace "${marketplace}". Supported: ${SUPPORTED_LIST}`
171
+ );
172
+ }
173
+ return domainId;
174
+ }
175
+
176
+ // src/lib/asin.ts
177
+ var ASIN_LENGTH = 10;
178
+ var ASIN_REGEX = /^[A-Z0-9]{10}$/;
179
+ function isValidAsin(value) {
180
+ return ASIN_REGEX.test(value);
181
+ }
182
+ function normalizeAsins(asins) {
183
+ const normalized = asins.map((asin) => asin.trim().toUpperCase());
184
+ const invalid = normalized.filter((asin) => !isValidAsin(asin));
185
+ if (invalid.length > 0) {
186
+ throw new Error(
187
+ `Invalid ASIN(s): ${invalid.join(", ")}. ASINs must be ${ASIN_LENGTH} alphanumeric characters (e.g. B00MNV8E0C).`
188
+ );
189
+ }
190
+ return normalized;
191
+ }
192
+
193
+ // src/resources/products/constant.ts
194
+ var PRODUCT_PATH = "/product";
195
+ var PRODUCT_LIST_CONTEXT = "products.list";
196
+ var DEFAULT_DAYS = 1;
197
+ var AMAZON_IMAGE_BASE = "https://m.media-amazon.com/images/I";
198
+ var KEEPA_NO_DATA_SENTINEL = -1;
199
+ var CsvType = {
200
+ AMAZON: 0,
201
+ NEW: 1,
202
+ USED: 2,
203
+ SALES: 3,
204
+ LISTPRICE: 4,
205
+ COLLECTIBLE: 5,
206
+ REFURBISHED: 6,
207
+ NEW_FBM_SHIPPING: 7,
208
+ LIGHTNING_DEAL: 8,
209
+ WAREHOUSE: 9,
210
+ NEW_FBA: 10,
211
+ COUNT_NEW: 11,
212
+ COUNT_USED: 12,
213
+ COUNT_REFURBISHED: 13,
214
+ COUNT_COLLECTIBLE: 14,
215
+ EXTRA_INFO_UPDATES: 15,
216
+ RATING: 16,
217
+ COUNT_REVIEWS: 17,
218
+ BUY_BOX_SHIPPING: 18,
219
+ USED_NEW_SHIPPING: 19,
220
+ USED_VERY_GOOD_SHIPPING: 20,
221
+ USED_GOOD_SHIPPING: 21,
222
+ USED_ACCEPTABLE_SHIPPING: 22,
223
+ COLLECTIBLE_NEW_SHIPPING: 23,
224
+ COLLECTIBLE_VERY_GOOD_SHIPPING: 24,
225
+ COLLECTIBLE_GOOD_SHIPPING: 25,
226
+ COLLECTIBLE_ACCEPTABLE_SHIPPING: 26,
227
+ REFURBISHED_SHIPPING: 27,
228
+ EBAY_NEW_SHIPPING: 28,
229
+ EBAY_USED_SHIPPING: 29,
230
+ TRADE_IN: 30,
231
+ RENTAL: 31,
232
+ BUY_BOX_USED_SHIPPING: 32,
233
+ PRIME_EXCL: 33,
234
+ COUNT_NEW_FBA: 34,
235
+ COUNT_NEW_FBM: 35
236
+ };
237
+ var KEEPA_EPOCH_UNIX_MS = 129384e7;
238
+ var VALID_IMAGE_FILENAME = /^[A-Za-z0-9._-]+\.(jpg|jpeg|png|webp)$/i;
239
+
240
+ // src/resources/products/error.ts
241
+ var ProductNotFoundError = class extends KeepaError {
242
+ asin;
243
+ constructor(asin) {
244
+ super(`Keepa: no product found for ASIN ${asin}`);
245
+ this.name = "ProductNotFoundError";
246
+ this.asin = asin;
247
+ }
248
+ };
249
+
250
+ // src/resources/products/product.util.ts
251
+ function toKeepaProduct(raw) {
252
+ const amazon = parsePriceHistory(raw.csv?.[CsvType.AMAZON]);
253
+ const list = parsePriceHistory(raw.csv?.[CsvType.LISTPRICE]);
254
+ return {
255
+ asin: raw.asin,
256
+ title: raw.title,
257
+ description: raw.description,
258
+ parentAsin: raw.parentAsin,
259
+ categoryTree: raw.categoryTree,
260
+ rootCategory: raw.rootCategory,
261
+ salesRanks: raw.salesRanks,
262
+ variations: raw.variations,
263
+ features: raw.features,
264
+ images: rawImagesToUrls(raw.images),
265
+ bsr: extractBsr(raw.salesRanks, raw.rootCategory),
266
+ price: amazon.at(-1)?.price ?? null,
267
+ listPrice: list.at(-1)?.price ?? null,
268
+ history: {
269
+ price: { amazon, list }
270
+ }
271
+ };
272
+ }
273
+ function pairKeepaSeries(series) {
274
+ if (!series) return [];
275
+ const points = [];
276
+ for (let i = 0; i + 1 < series.length; i += 2) {
277
+ const value = series[i + 1];
278
+ if (value === KEEPA_NO_DATA_SENTINEL) continue;
279
+ points.push({ timestamp: series[i], value });
280
+ }
281
+ return points;
282
+ }
283
+ function keepaMinutesToDate(minutes) {
284
+ return new Date(minutes * 6e4 + KEEPA_EPOCH_UNIX_MS);
285
+ }
286
+ function parsePriceHistory(series) {
287
+ return pairKeepaSeries(series).map(({ timestamp, value }) => ({
288
+ timestamp: keepaMinutesToDate(timestamp),
289
+ price: value / 100
290
+ }));
291
+ }
292
+ function rawImagesToUrls(images) {
293
+ if (!images || images.length === 0) return [];
294
+ return images.filter((img) => typeof img.l === "string" && VALID_IMAGE_FILENAME.test(img.l)).map((img) => `${AMAZON_IMAGE_BASE}/${img.l}`);
295
+ }
296
+ function isFoundProduct(product) {
297
+ return product.title != null;
298
+ }
299
+ function extractBsr(salesRanks, rootCategory) {
300
+ if (!salesRanks || rootCategory === void 0) return null;
301
+ return pairKeepaSeries(salesRanks[String(rootCategory)]).at(-1)?.value ?? null;
302
+ }
303
+
304
+ // src/resources/products/products.ts
305
+ var Products = class extends APIResource {
306
+ async list(params) {
307
+ if (params.asins.length === 0) {
308
+ throw new Error("At least one ASIN is required");
309
+ }
310
+ if (params.days !== void 0 && (!Number.isInteger(params.days) || params.days < 1)) {
311
+ throw new Error(`Invalid days: ${params.days}. Must be a positive integer.`);
312
+ }
313
+ const asins = normalizeAsins(params.asins);
314
+ const domain = resolveDomainId(params.marketplace);
315
+ const data = await this._client._request({
316
+ path: PRODUCT_PATH,
317
+ query: {
318
+ domain,
319
+ asin: asins,
320
+ days: params.days ?? DEFAULT_DAYS,
321
+ history: params.history ? 1 : 0
322
+ },
323
+ context: PRODUCT_LIST_CONTEXT
324
+ });
325
+ return (data.products ?? []).map(toKeepaProduct);
326
+ }
327
+ /** Throws `ProductNotFoundError` when Keepa returns no product or a stub. */
328
+ async retrieve(params) {
329
+ const { asin, ...rest } = params;
330
+ const [product] = await this.list({ ...rest, asins: [asin] });
331
+ if (!product || !isFoundProduct(product)) {
332
+ throw new ProductNotFoundError(asin);
333
+ }
334
+ return product;
335
+ }
336
+ };
337
+
338
+ // src/client.ts
339
+ var DEFAULT_BASE_URL = "https://api.keepa.com";
340
+ var KeepaClient = class {
341
+ apiKey;
342
+ baseURL;
343
+ fetch;
344
+ products;
345
+ constructor(options = {}) {
346
+ const envApiKey = typeof process !== "undefined" ? process.env?.KEEPA_API_KEY : void 0;
347
+ const apiKey = options.apiKey || envApiKey;
348
+ if (!apiKey) {
349
+ throw new Error(
350
+ "Missing Keepa API key. Pass it as `new KeepaClient({ apiKey })` or set KEEPA_API_KEY in your environment."
351
+ );
352
+ }
353
+ this.apiKey = apiKey;
354
+ this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
355
+ this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
356
+ this.products = new Products(this);
357
+ }
358
+ _request(args) {
359
+ const config = {
360
+ apiKey: this.apiKey,
361
+ baseURL: this.baseURL,
362
+ fetch: this.fetch
363
+ };
364
+ return request(config, args);
365
+ }
366
+ };
367
+
368
+ // src/version.ts
369
+ var VERSION = "0.1.0";
370
+ // Annotate the CommonJS export names for ESM import in node:
371
+ 0 && (module.exports = {
372
+ AMAZON_IMAGE_BASE,
373
+ APIError,
374
+ APIResource,
375
+ ASIN_LENGTH,
376
+ ASIN_REGEX,
377
+ AuthenticationError,
378
+ CsvType,
379
+ DEFAULT_DAYS,
380
+ KEEPA_NO_DATA_SENTINEL,
381
+ KeepaClient,
382
+ KeepaError,
383
+ MARKETPLACE_DOMAINS,
384
+ NetworkError,
385
+ PRODUCT_LIST_CONTEXT,
386
+ PRODUCT_PATH,
387
+ ProductNotFoundError,
388
+ Products,
389
+ RateLimitError,
390
+ VERSION,
391
+ extractBsr,
392
+ isFoundProduct,
393
+ isValidAsin,
394
+ normalizeAsins,
395
+ parsePriceHistory,
396
+ resolveDomainId
397
+ });
398
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/error.ts","../src/core/request.ts","../src/core/resource.ts","../src/lib/marketplace.ts","../src/lib/asin.ts","../src/resources/products/constant.ts","../src/resources/products/error.ts","../src/resources/products/product.util.ts","../src/resources/products/products.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["export { KeepaClient as default, KeepaClient, type ClientOptions } from './client.js';\n\nexport {\n KeepaError,\n APIError,\n RateLimitError,\n AuthenticationError,\n NetworkError,\n} from './core/error.js';\n\nexport { APIResource } from './core/resource.js';\n\nexport {\n MARKETPLACE_DOMAINS,\n resolveDomainId,\n type Marketplace,\n type DomainId,\n} from './lib/marketplace.js';\n\nexport {\n ASIN_LENGTH,\n ASIN_REGEX,\n isValidAsin,\n normalizeAsins,\n} from './lib/asin.js';\n\nexport * from './resources/index.js';\n\nexport { VERSION } from './version.js';\n","/** Defense-in-depth — keeps the API key out of error messages even when a\n * transport error or upstream response echoes the request URL back. */\nexport function scrubApiKey(text: string): string {\n return text.replace(/([?&])key=[^&\\s]*/gi, '$1key=REDACTED');\n}\n\nexport class KeepaError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'KeepaError';\n }\n}\n\nexport class APIError extends KeepaError {\n readonly status: number;\n readonly context: string;\n readonly body: string;\n\n constructor(status: number, context: string, body: string, message?: string) {\n const safeBody = scrubApiKey(body);\n super(scrubApiKey(message ?? `Keepa ${context} error (${status}): ${safeBody}`));\n this.name = 'APIError';\n this.status = status;\n this.context = context;\n this.body = safeBody;\n }\n\n static async from(response: Response, context: string): Promise<APIError> {\n const body = await response.text().catch(() => '');\n switch (response.status) {\n case 429:\n return new RateLimitError(context, body);\n case 401:\n return new AuthenticationError(context, body);\n default:\n return new APIError(response.status, context, body);\n }\n }\n}\n\nexport class RateLimitError extends APIError {\n constructor(context: string, body: string) {\n super(429, context, body, 'Keepa rate limit exceeded, please wait or upgrade plan');\n this.name = 'RateLimitError';\n }\n}\n\nexport class AuthenticationError extends APIError {\n constructor(context: string, body: string) {\n super(\n 401,\n context,\n body,\n `Keepa authentication failed for ${context}: invalid or missing API key`,\n );\n this.name = 'AuthenticationError';\n }\n}\n\nexport class NetworkError extends KeepaError {\n readonly context: string;\n override readonly cause: unknown;\n\n constructor(context: string, cause: unknown) {\n const raw = cause instanceof Error ? cause.message : String(cause);\n super(`Keepa ${context} network error: ${scrubApiKey(raw)}`);\n this.name = 'NetworkError';\n this.context = context;\n this.cause = cause;\n }\n}\n","import { APIError, NetworkError } from './error.js';\n\nexport type QueryValue = string | number | string[] | number[] | undefined;\nexport type QueryParams = Record<string, QueryValue>;\n\nexport interface RequestArgs {\n path: string;\n query?: QueryParams;\n context: string;\n}\n\nexport interface RequestConfig {\n baseURL: string;\n apiKey: string;\n fetch: typeof globalThis.fetch;\n}\n\nexport function buildUrl(baseURL: string, path: string, query?: QueryParams): string {\n if (!query) return `${baseURL}${path}`;\n const parts: string[] = [];\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n const stringValue = Array.isArray(value) ? value.join(',') : String(value);\n parts.push(`${encodeURIComponent(key)}=${encodeKeepaValue(stringValue)}`);\n }\n return parts.length ? `${baseURL}${path}?${parts.join('&')}` : `${baseURL}${path}`;\n}\n\n// Keep commas literal — Keepa's category endpoint rejects %2C-encoded commas\n// in comma-separated lists (asin=A,B,C, category=1,2,3).\nfunction encodeKeepaValue(value: string): string {\n return encodeURIComponent(value).replace(/%2C/g, ',');\n}\n\nexport async function request<T>(config: RequestConfig, args: RequestArgs): Promise<T> {\n const query: QueryParams = { key: config.apiKey, ...args.query };\n const url = buildUrl(config.baseURL, args.path, query);\n let res: Response;\n try {\n res = await config.fetch(url);\n } catch (cause) {\n throw new NetworkError(args.context, cause);\n }\n if (!res.ok) throw await APIError.from(res, args.context);\n return (await res.json()) as T;\n}\n","import type { KeepaClient } from '../client.js';\n\nexport abstract class APIResource {\n protected _client: KeepaClient;\n\n constructor(client: KeepaClient) {\n this._client = client;\n }\n}\n","// Keys are ISO 3166-1 alpha-2; values are Keepa's `domain` query parameter.\n// Domain 7 is skipped — reserved (formerly Amazon China, retired).\nexport const MARKETPLACE_DOMAINS = {\n US: 1,\n GB: 2,\n DE: 3,\n FR: 4,\n JP: 5,\n CA: 6,\n IT: 8,\n ES: 9,\n IN: 10,\n MX: 11,\n BR: 12,\n} as const;\n\nexport type Marketplace = keyof typeof MARKETPLACE_DOMAINS;\nexport type DomainId = (typeof MARKETPLACE_DOMAINS)[Marketplace];\n\nconst SUPPORTED_LIST = Object.keys(MARKETPLACE_DOMAINS).join(', ');\n\nexport function resolveDomainId(marketplace: string | undefined): DomainId {\n const key = (marketplace ?? 'US').toUpperCase() as Marketplace;\n const domainId = MARKETPLACE_DOMAINS[key];\n if (!domainId) {\n throw new Error(\n `Invalid marketplace \"${marketplace}\". Supported: ${SUPPORTED_LIST}`,\n );\n }\n return domainId;\n}\n","export const ASIN_LENGTH = 10;\nexport const ASIN_REGEX = /^[A-Z0-9]{10}$/;\n\n/** Structural check only — a passing ASIN may still not exist in Keepa's\n * database; use `isFoundProduct` for that. */\nexport function isValidAsin(value: string): boolean {\n return ASIN_REGEX.test(value);\n}\n\nexport function normalizeAsins(asins: string[]): string[] {\n const normalized = asins.map((asin) => asin.trim().toUpperCase());\n const invalid = normalized.filter((asin) => !isValidAsin(asin));\n if (invalid.length > 0) {\n throw new Error(\n `Invalid ASIN(s): ${invalid.join(', ')}. ASINs must be ${ASIN_LENGTH} alphanumeric characters (e.g. B00MNV8E0C).`,\n );\n }\n return normalized;\n}\n","export const PRODUCT_PATH = '/product';\n// `<resource>.<method>` tag carried on errors so log aggregators can filter cleanly.\nexport const PRODUCT_LIST_CONTEXT = 'products.list';\nexport const DEFAULT_DAYS = 1;\n\n// Region-neutral CDN — serves identical images regardless of the marketplace.\nexport const AMAZON_IMAGE_BASE = 'https://m.media-amazon.com/images/I';\n\n// Keepa stores `-1` in salesRanks/price arrays as \"no data captured at that timestamp\".\nexport const KEEPA_NO_DATA_SENTINEL = -1;\n\n// First-dim index into Keepa's csv history matrix. Names mirror Keepa's own enum\n// verbatim (see https://keepa.com/#!discuss/t/product-object/116).\nexport const CsvType = {\n AMAZON: 0,\n NEW: 1,\n USED: 2,\n SALES: 3,\n LISTPRICE: 4,\n COLLECTIBLE: 5,\n REFURBISHED: 6,\n NEW_FBM_SHIPPING: 7,\n LIGHTNING_DEAL: 8,\n WAREHOUSE: 9,\n NEW_FBA: 10,\n COUNT_NEW: 11,\n COUNT_USED: 12,\n COUNT_REFURBISHED: 13,\n COUNT_COLLECTIBLE: 14,\n EXTRA_INFO_UPDATES: 15,\n RATING: 16,\n COUNT_REVIEWS: 17,\n BUY_BOX_SHIPPING: 18,\n USED_NEW_SHIPPING: 19,\n USED_VERY_GOOD_SHIPPING: 20,\n USED_GOOD_SHIPPING: 21,\n USED_ACCEPTABLE_SHIPPING: 22,\n COLLECTIBLE_NEW_SHIPPING: 23,\n COLLECTIBLE_VERY_GOOD_SHIPPING: 24,\n COLLECTIBLE_GOOD_SHIPPING: 25,\n COLLECTIBLE_ACCEPTABLE_SHIPPING: 26,\n REFURBISHED_SHIPPING: 27,\n EBAY_NEW_SHIPPING: 28,\n EBAY_USED_SHIPPING: 29,\n TRADE_IN: 30,\n RENTAL: 31,\n BUY_BOX_USED_SHIPPING: 32,\n PRIME_EXCL: 33,\n COUNT_NEW_FBA: 34,\n COUNT_NEW_FBM: 35,\n} as const;\nexport type CsvType = (typeof CsvType)[keyof typeof CsvType];\n\n// 2011-01-01 00:00 UTC — origin of Keepa's csv minute timestamps. Convert with\n// `new Date(km * 60_000 + KEEPA_EPOCH_UNIX_MS)`.\nexport const KEEPA_EPOCH_UNIX_MS = 1_293_840_000_000;\n\n// Defense-in-depth allowlist for filenames in imagesCSV — rejects path-traversal\n// / SSRF-shaped strings if the CSV is ever influenced by untrusted input.\nexport const VALID_IMAGE_FILENAME = /^[A-Za-z0-9._-]+\\.(jpg|jpeg|png|webp)$/i;\n","import { KeepaError } from '../../core/error.js';\n\n/** Thrown when Keepa returns no product, or returns a stub (title === null).\n * `asin` carries the caller's original input unchanged. */\nexport class ProductNotFoundError extends KeepaError {\n readonly asin: string;\n\n constructor(asin: string) {\n super(`Keepa: no product found for ASIN ${asin}`);\n this.name = 'ProductNotFoundError';\n this.asin = asin;\n }\n}\n","import {\n AMAZON_IMAGE_BASE,\n CsvType,\n KEEPA_EPOCH_UNIX_MS,\n KEEPA_NO_DATA_SENTINEL,\n VALID_IMAGE_FILENAME,\n} from './constant.js';\nimport type { KeepaProduct, PriceHistoryEntry } from './product.type.js';\nimport type { KeepaImageRaw, KeepaProductRaw } from './product.raw.type.js';\n\nexport function toKeepaProduct(raw: KeepaProductRaw): KeepaProduct {\n const amazon = parsePriceHistory(raw.csv?.[CsvType.AMAZON]);\n const list = parsePriceHistory(raw.csv?.[CsvType.LISTPRICE]);\n return {\n asin: raw.asin,\n title: raw.title,\n description: raw.description,\n parentAsin: raw.parentAsin,\n categoryTree: raw.categoryTree,\n rootCategory: raw.rootCategory,\n salesRanks: raw.salesRanks,\n variations: raw.variations,\n features: raw.features,\n images: rawImagesToUrls(raw.images),\n bsr: extractBsr(raw.salesRanks, raw.rootCategory),\n price: amazon.at(-1)?.price ?? null,\n listPrice: list.at(-1)?.price ?? null,\n history: {\n price: { amazon, list },\n },\n };\n}\n\ninterface KeepaSeriesPoint {\n /** Keepa minutes — use `keepaMinutesToDate` for a JS Date. */\n timestamp: number;\n value: number;\n}\n\n// Pair up Keepa's flat `[ts, value, ts, value, ...]` series. Drops `-1` no-data\n// sentinels; the `i + 1 < length` bound silently skips a dangling odd element.\nfunction pairKeepaSeries(series: number[] | undefined): KeepaSeriesPoint[] {\n if (!series) return [];\n const points: KeepaSeriesPoint[] = [];\n for (let i = 0; i + 1 < series.length; i += 2) {\n const value = series[i + 1]!;\n if (value === KEEPA_NO_DATA_SENTINEL) continue;\n points.push({ timestamp: series[i]!, value });\n }\n return points;\n}\n\nfunction keepaMinutesToDate(minutes: number): Date {\n return new Date(minutes * 60_000 + KEEPA_EPOCH_UNIX_MS);\n}\n\n// Keepa stores prices as integers in the smallest unit (cents/pence/etc.) and\n// scales JPY/INR/BRL the same way, so /100 produces the marketplace's major unit\n// uniformly across all supported regions.\nexport function parsePriceHistory(series: number[] | undefined): PriceHistoryEntry[] {\n return pairKeepaSeries(series).map(({ timestamp, value }) => ({\n timestamp: keepaMinutesToDate(timestamp),\n price: value / 100,\n }));\n}\n\n// Defense-in-depth — rejects path-traversal / SSRF-shaped filenames in case the\n// raw CSV is ever influenced by untrusted input.\nfunction rawImagesToUrls(images: KeepaImageRaw[] | undefined): string[] {\n if (!images || images.length === 0) return [];\n return images\n .filter((img) => typeof img.l === 'string' && VALID_IMAGE_FILENAME.test(img.l))\n .map((img) => `${AMAZON_IMAGE_BASE}/${img.l}`);\n}\n\n/** Stubs for unknown ASINs come back with `title: null`. */\nexport function isFoundProduct(product: KeepaProduct): boolean {\n return product.title != null;\n}\n\n/** Latest non-sentinel rank from Keepa's `[ts, rank, ts, rank, ...]` salesRanks\n * series for the product's rootCategory. */\nexport function extractBsr(\n salesRanks: Record<string, number[]> | undefined,\n rootCategory: number | undefined,\n): number | null {\n if (!salesRanks || rootCategory === undefined) return null;\n return pairKeepaSeries(salesRanks[String(rootCategory)]).at(-1)?.value ?? null;\n}\n","import { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport { normalizeAsins } from '../../lib/asin.js';\nimport {\n PRODUCT_PATH,\n PRODUCT_LIST_CONTEXT,\n DEFAULT_DAYS,\n} from './constant.js';\nimport type {\n KeepaProduct,\n ProductListParams,\n ProductRetrieveParams,\n} from './product.type.js';\nimport type { KeepaProductResponseRaw } from './product.raw.type.js';\nimport { ProductNotFoundError } from './error.js';\nimport { toKeepaProduct, isFoundProduct } from './product.util.js';\n\nexport class Products extends APIResource {\n async list(params: ProductListParams): Promise<KeepaProduct[]> {\n if (params.asins.length === 0) {\n throw new Error('At least one ASIN is required');\n }\n if (\n params.days !== undefined &&\n (!Number.isInteger(params.days) || params.days < 1)\n ) {\n throw new Error(`Invalid days: ${params.days}. Must be a positive integer.`);\n }\n const asins = normalizeAsins(params.asins);\n const domain = resolveDomainId(params.marketplace);\n const data = await this._client._request<KeepaProductResponseRaw>({\n path: PRODUCT_PATH,\n query: {\n domain,\n asin: asins,\n days: params.days ?? DEFAULT_DAYS,\n history: params.history ? 1 : 0,\n },\n context: PRODUCT_LIST_CONTEXT,\n });\n return (data.products ?? []).map(toKeepaProduct);\n }\n\n /** Throws `ProductNotFoundError` when Keepa returns no product or a stub. */\n async retrieve(params: ProductRetrieveParams): Promise<KeepaProduct> {\n const { asin, ...rest } = params;\n const [product] = await this.list({ ...rest, asins: [asin] });\n if (!product || !isFoundProduct(product)) {\n throw new ProductNotFoundError(asin);\n }\n return product;\n }\n}\n","import { request } from './core/request.js';\nimport type { RequestArgs, RequestConfig } from './core/request.js';\nimport { Products } from './resources/products/products.js';\n\nexport interface ClientOptions {\n /** Falls back to `process.env.KEEPA_API_KEY` when omitted. */\n apiKey?: string;\n baseURL?: string;\n fetch?: typeof globalThis.fetch;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.keepa.com';\n\nexport class KeepaClient {\n readonly apiKey: string;\n readonly baseURL: string;\n readonly fetch: typeof globalThis.fetch;\n\n readonly products: Products;\n\n constructor(options: ClientOptions = {}) {\n // `process` is undefined in browsers / Workers / edge runtimes — guard so the\n // constructor throws our friendly error instead of a raw ReferenceError there.\n const envApiKey =\n typeof process !== 'undefined' ? process.env?.KEEPA_API_KEY : undefined;\n const apiKey = options.apiKey || envApiKey;\n if (!apiKey) {\n throw new Error(\n 'Missing Keepa API key. Pass it as `new KeepaClient({ apiKey })` or set KEEPA_API_KEY in your environment.',\n );\n }\n this.apiKey = apiKey;\n // Strip trailing slash so `${baseURL}${path}` never produces `//product`.\n this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n // Unbound Node fetch throws \"Illegal invocation\" when called with undefined `this`.\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n\n this.products = new Products(this);\n }\n\n _request<T>(args: RequestArgs): Promise<T> {\n const config: RequestConfig = {\n apiKey: this.apiKey,\n baseURL: this.baseURL,\n fetch: this.fetch,\n };\n return request<T>(config, args);\n }\n}\n\nexport default KeepaClient;\n","export const VERSION = '0.1.0';\n"],"mappings":"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","names":[]}