keepa-api 0.2.2 → 0.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.
Files changed (69) hide show
  1. package/README.md +4 -4
  2. package/dist/index.cjs +337 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +163 -0
  5. package/dist/index.d.ts +163 -8
  6. package/dist/index.js +287 -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
@@ -1,6 +1,6 @@
1
1
  # keepa-api
2
2
 
3
- Lightweight TypeScript SDK for the [Keepa](https://keepa.com) REST API. Mirrors the organizational style of [openai-node](https://github.com/openai/openai-node) — a single `Keepa` client class exposes resources (`products`, etc.) that wrap each endpoint with typed inputs and responses.
3
+ Lightweight TypeScript SDK for the [Keepa](https://keepa.com) REST API. Mirrors the organizational style of [openai-node](https://github.com/openai/openai-node) — a single `KeepaClient` class exposes resources (`products`, etc.) that wrap each endpoint with typed inputs and responses.
4
4
 
5
5
  Phase 1 ships the **Products** resource only. Categories, Search, and Bestsellers are planned but not yet implemented.
6
6
 
@@ -49,9 +49,9 @@ npm install /absolute/path/to/keepa-api-0.1.0.tgz
49
49
  ## Quickstart
50
50
 
51
51
  ```ts
52
- import Keepa from 'keepa-api';
52
+ import KeepaClient from 'keepa-api';
53
53
 
54
- const keepa = new Keepa({ apiKey: process.env.KEEPA_API_KEY });
54
+ const keepa = new KeepaClient({ apiKey: process.env.KEEPA_API_KEY });
55
55
 
56
56
  const products = await keepa.products.list({
57
57
  asins: ['B00MNV8E0C'],
@@ -65,7 +65,7 @@ The client reads `process.env.KEEPA_API_KEY` if you don't pass `apiKey` explicit
65
65
 
66
66
  ## API
67
67
 
68
- ### `new Keepa(options?)`
68
+ ### `new KeepaClient(options?)`
69
69
 
70
70
  | Option | Type | Default | Notes |
71
71
  |--------|------|---------|-------|
package/dist/index.cjs ADDED
@@ -0,0 +1,337 @@
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
+ DEFAULT_DAYS: () => DEFAULT_DAYS,
30
+ KEEPA_NO_DATA_SENTINEL: () => KEEPA_NO_DATA_SENTINEL,
31
+ KeepaClient: () => KeepaClient,
32
+ KeepaError: () => KeepaError,
33
+ MARKETPLACE_DOMAINS: () => MARKETPLACE_DOMAINS,
34
+ NetworkError: () => NetworkError,
35
+ PRODUCT_LIST_CONTEXT: () => PRODUCT_LIST_CONTEXT,
36
+ PRODUCT_PATH: () => PRODUCT_PATH,
37
+ ProductNotFoundError: () => ProductNotFoundError,
38
+ Products: () => Products,
39
+ RateLimitError: () => RateLimitError,
40
+ VERSION: () => VERSION,
41
+ default: () => KeepaClient,
42
+ extractBsr: () => extractBsr,
43
+ isFoundProduct: () => isFoundProduct,
44
+ isValidAsin: () => isValidAsin,
45
+ normalizeAsins: () => normalizeAsins,
46
+ resolveDomainId: () => resolveDomainId
47
+ });
48
+ module.exports = __toCommonJS(index_exports);
49
+
50
+ // src/core/error.ts
51
+ function scrubApiKey(text) {
52
+ return text.replace(/([?&])key=[^&\s]*/gi, "$1key=REDACTED");
53
+ }
54
+ var KeepaError = class extends Error {
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = "KeepaError";
58
+ }
59
+ };
60
+ var APIError = class _APIError extends KeepaError {
61
+ status;
62
+ context;
63
+ body;
64
+ constructor(status, context, body, message) {
65
+ const safeBody = scrubApiKey(body);
66
+ super(scrubApiKey(message ?? `Keepa ${context} error (${status}): ${safeBody}`));
67
+ this.name = "APIError";
68
+ this.status = status;
69
+ this.context = context;
70
+ this.body = safeBody;
71
+ }
72
+ static async from(response, context) {
73
+ const body = await response.text().catch(() => "");
74
+ switch (response.status) {
75
+ case 429:
76
+ return new RateLimitError(context, body);
77
+ case 401:
78
+ return new AuthenticationError(context, body);
79
+ default:
80
+ return new _APIError(response.status, context, body);
81
+ }
82
+ }
83
+ };
84
+ var RateLimitError = class extends APIError {
85
+ constructor(context, body) {
86
+ super(429, context, body, "Keepa rate limit exceeded, please wait or upgrade plan");
87
+ this.name = "RateLimitError";
88
+ }
89
+ };
90
+ var AuthenticationError = class extends APIError {
91
+ constructor(context, body) {
92
+ super(
93
+ 401,
94
+ context,
95
+ body,
96
+ `Keepa authentication failed for ${context}: invalid or missing API key`
97
+ );
98
+ this.name = "AuthenticationError";
99
+ }
100
+ };
101
+ var NetworkError = class extends KeepaError {
102
+ context;
103
+ cause;
104
+ constructor(context, cause) {
105
+ const raw = cause instanceof Error ? cause.message : String(cause);
106
+ super(`Keepa ${context} network error: ${scrubApiKey(raw)}`);
107
+ this.name = "NetworkError";
108
+ this.context = context;
109
+ this.cause = cause;
110
+ }
111
+ };
112
+
113
+ // src/core/request.ts
114
+ function buildUrl(baseURL, path, query) {
115
+ if (!query) return `${baseURL}${path}`;
116
+ const parts = [];
117
+ for (const [key, value] of Object.entries(query)) {
118
+ if (value === void 0) continue;
119
+ const stringValue = Array.isArray(value) ? value.join(",") : String(value);
120
+ parts.push(`${encodeURIComponent(key)}=${encodeKeepaValue(stringValue)}`);
121
+ }
122
+ return parts.length ? `${baseURL}${path}?${parts.join("&")}` : `${baseURL}${path}`;
123
+ }
124
+ function encodeKeepaValue(value) {
125
+ return encodeURIComponent(value).replace(/%2C/g, ",");
126
+ }
127
+ async function request(config, args) {
128
+ const query = { key: config.apiKey, ...args.query };
129
+ const url = buildUrl(config.baseURL, args.path, query);
130
+ let res;
131
+ try {
132
+ res = await config.fetch(url);
133
+ } catch (cause) {
134
+ throw new NetworkError(args.context, cause);
135
+ }
136
+ if (!res.ok) throw await APIError.from(res, args.context);
137
+ return await res.json();
138
+ }
139
+
140
+ // src/core/resource.ts
141
+ var APIResource = class {
142
+ _client;
143
+ constructor(client) {
144
+ this._client = client;
145
+ }
146
+ };
147
+
148
+ // src/lib/marketplace.ts
149
+ var MARKETPLACE_DOMAINS = {
150
+ US: 1,
151
+ GB: 2,
152
+ DE: 3,
153
+ FR: 4,
154
+ JP: 5,
155
+ CA: 6,
156
+ IT: 8,
157
+ ES: 9,
158
+ IN: 10,
159
+ MX: 11,
160
+ BR: 12
161
+ };
162
+ var SUPPORTED_LIST = Object.keys(MARKETPLACE_DOMAINS).join(", ");
163
+ function resolveDomainId(marketplace) {
164
+ const key = (marketplace ?? "US").toUpperCase();
165
+ const domainId = MARKETPLACE_DOMAINS[key];
166
+ if (!domainId) {
167
+ throw new Error(
168
+ `Invalid marketplace "${marketplace}". Supported: ${SUPPORTED_LIST}`
169
+ );
170
+ }
171
+ return domainId;
172
+ }
173
+
174
+ // src/lib/asin.ts
175
+ var ASIN_LENGTH = 10;
176
+ var ASIN_REGEX = /^[A-Z0-9]{10}$/;
177
+ function isValidAsin(value) {
178
+ return ASIN_REGEX.test(value);
179
+ }
180
+ function normalizeAsins(asins) {
181
+ const normalized = asins.map((asin) => asin.trim().toUpperCase());
182
+ const invalid = normalized.filter((asin) => !isValidAsin(asin));
183
+ if (invalid.length > 0) {
184
+ throw new Error(
185
+ `Invalid ASIN(s): ${invalid.join(", ")}. ASINs must be ${ASIN_LENGTH} alphanumeric characters (e.g. B00MNV8E0C).`
186
+ );
187
+ }
188
+ return normalized;
189
+ }
190
+
191
+ // src/resources/products/constant.ts
192
+ var PRODUCT_PATH = "/product";
193
+ var PRODUCT_LIST_CONTEXT = "products.list";
194
+ var DEFAULT_DAYS = 1;
195
+ var AMAZON_IMAGE_BASE = "https://m.media-amazon.com/images/I";
196
+ var KEEPA_NO_DATA_SENTINEL = -1;
197
+ var VALID_IMAGE_FILENAME = /^[A-Za-z0-9._-]+\.(jpg|jpeg|png|webp)$/i;
198
+
199
+ // src/resources/products/error.ts
200
+ var ProductNotFoundError = class extends KeepaError {
201
+ asin;
202
+ constructor(asin) {
203
+ super(`Keepa: no product found for ASIN ${asin}`);
204
+ this.name = "ProductNotFoundError";
205
+ this.asin = asin;
206
+ }
207
+ };
208
+
209
+ // src/resources/products/product.util.ts
210
+ function toKeepaProduct(raw) {
211
+ return {
212
+ asin: raw.asin,
213
+ title: raw.title,
214
+ description: raw.description,
215
+ parentAsin: raw.parentAsin,
216
+ categoryTree: raw.categoryTree,
217
+ rootCategory: raw.rootCategory,
218
+ salesRanks: raw.salesRanks,
219
+ variations: raw.variations,
220
+ features: raw.features,
221
+ images: rawImagesToUrls(raw.images),
222
+ bsr: extractBsr(raw.salesRanks, raw.rootCategory)
223
+ };
224
+ }
225
+ function rawImagesToUrls(images) {
226
+ if (!images || images.length === 0) return [];
227
+ return images.filter((img) => typeof img.l === "string" && VALID_IMAGE_FILENAME.test(img.l)).map((img) => `${AMAZON_IMAGE_BASE}/${img.l}`);
228
+ }
229
+ function isFoundProduct(product) {
230
+ return product.title != null;
231
+ }
232
+ function extractBsr(salesRanks, rootCategory) {
233
+ if (!salesRanks || rootCategory === void 0) return null;
234
+ const ranks = salesRanks[String(rootCategory)];
235
+ if (!ranks || ranks.length < 2) return null;
236
+ const start = ranks.length % 2 === 0 ? ranks.length - 1 : ranks.length - 2;
237
+ for (let i = start; i >= 1; i -= 2) {
238
+ const rank = ranks[i];
239
+ if (rank !== void 0 && rank !== KEEPA_NO_DATA_SENTINEL) return rank;
240
+ }
241
+ return null;
242
+ }
243
+
244
+ // src/resources/products/products.ts
245
+ var Products = class extends APIResource {
246
+ async list(params) {
247
+ if (params.asins.length === 0) {
248
+ throw new Error("At least one ASIN is required");
249
+ }
250
+ if (params.days !== void 0 && (!Number.isInteger(params.days) || params.days < 1)) {
251
+ throw new Error(`Invalid days: ${params.days}. Must be a positive integer.`);
252
+ }
253
+ const asins = normalizeAsins(params.asins);
254
+ const domain = resolveDomainId(params.marketplace);
255
+ const data = await this._client._request({
256
+ path: PRODUCT_PATH,
257
+ query: {
258
+ domain,
259
+ asin: asins,
260
+ days: params.days ?? DEFAULT_DAYS
261
+ },
262
+ context: PRODUCT_LIST_CONTEXT
263
+ });
264
+ return (data.products ?? []).map(toKeepaProduct);
265
+ }
266
+ /** Fetch a single product by ASIN. Throws `ProductNotFoundError` when Keepa
267
+ * has no record for the ASIN (no product returned, or a stub with no title). */
268
+ async retrieve(params) {
269
+ const { asin, ...rest } = params;
270
+ const [product] = await this.list({ ...rest, asins: [asin] });
271
+ if (!product || !isFoundProduct(product)) {
272
+ throw new ProductNotFoundError(asin);
273
+ }
274
+ return product;
275
+ }
276
+ };
277
+
278
+ // src/client.ts
279
+ var DEFAULT_BASE_URL = "https://api.keepa.com";
280
+ var KeepaClient = class {
281
+ apiKey;
282
+ baseURL;
283
+ fetch;
284
+ products;
285
+ constructor(options = {}) {
286
+ const envApiKey = typeof process !== "undefined" ? process.env?.KEEPA_API_KEY : void 0;
287
+ const apiKey = options.apiKey || envApiKey;
288
+ if (!apiKey) {
289
+ throw new Error(
290
+ "Missing Keepa API key. Pass it as `new KeepaClient({ apiKey })` or set KEEPA_API_KEY in your environment."
291
+ );
292
+ }
293
+ this.apiKey = apiKey;
294
+ this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
295
+ this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
296
+ this.products = new Products(this);
297
+ }
298
+ /** Internal: used by APIResource subclasses to perform a request. */
299
+ _request(args) {
300
+ const config = {
301
+ apiKey: this.apiKey,
302
+ baseURL: this.baseURL,
303
+ fetch: this.fetch
304
+ };
305
+ return request(config, args);
306
+ }
307
+ };
308
+
309
+ // src/version.ts
310
+ var VERSION = "0.1.0";
311
+ // Annotate the CommonJS export names for ESM import in node:
312
+ 0 && (module.exports = {
313
+ AMAZON_IMAGE_BASE,
314
+ APIError,
315
+ APIResource,
316
+ ASIN_LENGTH,
317
+ ASIN_REGEX,
318
+ AuthenticationError,
319
+ DEFAULT_DAYS,
320
+ KEEPA_NO_DATA_SENTINEL,
321
+ KeepaClient,
322
+ KeepaError,
323
+ MARKETPLACE_DOMAINS,
324
+ NetworkError,
325
+ PRODUCT_LIST_CONTEXT,
326
+ PRODUCT_PATH,
327
+ ProductNotFoundError,
328
+ Products,
329
+ RateLimitError,
330
+ VERSION,
331
+ extractBsr,
332
+ isFoundProduct,
333
+ isValidAsin,
334
+ normalizeAsins,
335
+ resolveDomainId
336
+ });
337
+ //# 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","/** Replace any `?key=...` or `&key=...` segment with a REDACTED placeholder.\n * Defense-in-depth: keeps the API key out of error messages even when an\n * underlying transport error or upstream response echoes the request URL.\n * Exported for use by callers that build their own error wrappers. */\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 accepts comma-separated lists (asin=A,B,C, category=1,2,3)\n// and the category endpoint specifically rejects %2C-encoded commas.\n// Currently private to this module: if a future helper builds Keepa URLs outside\n// `request()` (e.g. a paginator) and needs the same encoding, lift this into\n// `core/encoding.ts` rather than duplicating it.\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","// The full set of marketplaces Keepa currently supports. Codes are ISO 3166-1\n// alpha-2; the numeric id is Keepa's `domain` query parameter value. Domain 7\n// is reserved (formerly Amazon China, retired). Update the README marketplace\n// table alongside any change here.\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/** Returns true if the given string is a structurally valid ASIN\n * (10 uppercase alphanumeric characters). Note: a passing ASIN may still\n * not exist in Keepa's database — use `isFoundProduct` to check that. */\nexport function isValidAsin(value: string): boolean {\n return ASIN_REGEX.test(value);\n}\n\n/** Trim + uppercase a list of ASINs and validate each. Returns the normalized\n * list. Throws with all invalid ASINs listed in the error message. */\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// Structured tag of the form `<resource>.<method>`. Used as the `context` field on\n// errors thrown from this method — keeps log aggregators able to filter cleanly as\n// more methods land (categories.list, categories.search, bestsellers.retrieve, …).\nexport const PRODUCT_LIST_CONTEXT = 'products.list';\nexport const DEFAULT_DAYS = 1;\n\n// Region-neutral Amazon image CDN. Serves the same images globally regardless of marketplace.\nexport const AMAZON_IMAGE_BASE = 'https://m.media-amazon.com/images/I';\n\n// Keepa stores `-1` in salesRanks/price arrays to indicate \"no data captured at that timestamp\".\nexport const KEEPA_NO_DATA_SENTINEL = -1;\n\n// Allowlist for image filenames in imagesCSV. Defends against path-traversal / SSRF\n// shaped strings (e.g. \"../../etc/passwd\") in case the CSV is ever influenced by\n// untrusted input. Matches typical Keepa image hashes (e.g. \"61abcDEF.jpg\").\nexport const VALID_IMAGE_FILENAME = /^[A-Za-z0-9._-]+\\.(jpg|jpeg|png|webp)$/i;\n","import { KeepaError } from '../../core/error.js';\n\n/** Thrown by `Products.retrieve` when Keepa has no record for the requested ASIN\n * (either no product object returned, or a stub with no title). The `asin`\n * property carries the value the caller passed in, unchanged, so it can be\n * surfaced in user-facing messages without re-deriving it. */\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 KEEPA_NO_DATA_SENTINEL,\n VALID_IMAGE_FILENAME,\n} from './constant.js';\nimport type { KeepaProduct } from './product.type.js';\nimport type { KeepaImageRaw, KeepaProductRaw } from './product.raw.type.js';\n\n/** Map Keepa's raw wire shape into the consumer-friendly KeepaProduct. */\nexport function toKeepaProduct(raw: KeepaProductRaw): KeepaProduct {\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 };\n}\n\n/** Convert Keepa's raw images array into full Amazon image URLs (the large\n * variant). Filters entries whose filename doesn't match the expected\n * alphanumeric image-name pattern as defense-in-depth. */\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/** Returns true when Keepa returned an actual product record (not just a stub\n * for an unknown ASIN). Stubs come back with `title: null`; real listings\n * always have a string. Use as a `.filter()` predicate to drop empty matches. */\nexport function isFoundProduct(product: KeepaProduct): boolean {\n return product.title != null;\n}\n\n/** Extract the most recent real BSR from Keepa's `[ts, rank, ts, rank, ...]` salesRanks array.\n * Walks backward through rank entries (odd indices) and skips Keepa's `-1` sentinel which\n * marks \"no data captured at that timestamp\". Returns `null` if every entry is sentinel.\n * Most consumers won't need this — `Products.list` already fills `bsr` on every returned\n * product. Useful when working with a raw Keepa response. */\nexport function extractBsr(\n salesRanks: Record<string, number[]> | undefined,\n rootCategory: number | undefined,\n): number | null {\n if (!salesRanks || rootCategory === undefined) return null;\n const ranks = salesRanks[String(rootCategory)];\n if (!ranks || ranks.length < 2) return null;\n // Keepa pairs are [ts, rank, ts, rank, ...]. If length is even, the last index\n // is a rank; if odd (truncated/schema drift), it's a dangling timestamp — skip\n // back one slot so we always start on a rank.\n const start = ranks.length % 2 === 0 ? ranks.length - 1 : ranks.length - 2;\n for (let i = start; i >= 1; i -= 2) {\n const rank = ranks[i];\n if (rank !== undefined && rank !== KEEPA_NO_DATA_SENTINEL) return rank;\n }\n return 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 },\n context: PRODUCT_LIST_CONTEXT,\n });\n return (data.products ?? []).map(toKeepaProduct);\n }\n\n /** Fetch a single product by ASIN. Throws `ProductNotFoundError` when Keepa\n * has no record for the ASIN (no product returned, or a stub with no title). */\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 /** Keepa API key. Falls back to `process.env.KEEPA_API_KEY`. */\n apiKey?: string;\n /** Base URL for the Keepa API. Defaults to `https://api.keepa.com`. */\n baseURL?: string;\n /** Custom fetch implementation. Defaults to `globalThis.fetch`. */\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 / Cloudflare Workers / edge runtimes — guard it\n // so the constructor throws our friendly \"Missing Keepa API key\" message instead of a\n // raw ReferenceError on those targets.\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 a trailing slash so `${baseURL}${path}` never produces `//product`.\n this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n // Bind `globalThis` once: an unbound reference to Node's fetch throws\n // \"Illegal invocation\" when called with an undefined `this`. Bind once at\n // construction so every _request call uses the cached bound function.\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n\n this.products = new Products(this);\n }\n\n /** Internal: used by APIResource subclasses to perform a request. */\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":[]}
@@ -0,0 +1,163 @@
1
+ type QueryValue = string | number | string[] | number[] | undefined;
2
+ type QueryParams = Record<string, QueryValue>;
3
+ interface RequestArgs {
4
+ path: string;
5
+ query?: QueryParams;
6
+ context: string;
7
+ }
8
+
9
+ declare abstract class APIResource {
10
+ protected _client: KeepaClient;
11
+ constructor(client: KeepaClient);
12
+ }
13
+
14
+ declare const MARKETPLACE_DOMAINS: {
15
+ readonly US: 1;
16
+ readonly GB: 2;
17
+ readonly DE: 3;
18
+ readonly FR: 4;
19
+ readonly JP: 5;
20
+ readonly CA: 6;
21
+ readonly IT: 8;
22
+ readonly ES: 9;
23
+ readonly IN: 10;
24
+ readonly MX: 11;
25
+ readonly BR: 12;
26
+ };
27
+ type Marketplace = keyof typeof MARKETPLACE_DOMAINS;
28
+ type DomainId = (typeof MARKETPLACE_DOMAINS)[Marketplace];
29
+ declare function resolveDomainId(marketplace: string | undefined): DomainId;
30
+
31
+ interface ProductListParams {
32
+ asins: string[];
33
+ /** Marketplace code (case-insensitive at runtime). Defaults to 'US'. */
34
+ marketplace?: Marketplace;
35
+ /** Days of price history to include. Defaults to 1. */
36
+ days?: number;
37
+ }
38
+ interface ProductRetrieveParams {
39
+ asin: string;
40
+ /** Marketplace code (case-insensitive at runtime). Defaults to 'US'. */
41
+ marketplace?: Marketplace;
42
+ /** Days of price history to include. Defaults to 1. */
43
+ days?: number;
44
+ }
45
+ interface KeepaCategoryNode {
46
+ catId: number;
47
+ name: string;
48
+ }
49
+ interface KeepaVariationAttribute {
50
+ dimension: string;
51
+ value: string;
52
+ }
53
+ interface KeepaVariation {
54
+ asin: string;
55
+ attributes?: KeepaVariationAttribute[];
56
+ }
57
+ /** A Keepa product in the consumer-friendly shape. Derived from the raw Keepa
58
+ * response: `imagesCSV` is split into `images: string[]`, and the most-recent
59
+ * real BSR is surfaced as `bsr`. The raw `salesRanks` is preserved for
60
+ * consumers that need to walk the rank history themselves. */
61
+ interface KeepaProduct {
62
+ asin: string;
63
+ title?: string;
64
+ description?: string;
65
+ parentAsin?: string;
66
+ categoryTree?: KeepaCategoryNode[];
67
+ rootCategory?: number;
68
+ salesRanks?: Record<string, number[]>;
69
+ variations?: KeepaVariation[];
70
+ features?: string[];
71
+ /** Full image URLs derived from imagesCSV using a region-neutral Amazon CDN. */
72
+ images: string[];
73
+ /** Most recent real BSR for the rootCategory. Null when missing or every entry is
74
+ * Keepa's `-1` "no data captured" sentinel. */
75
+ bsr: number | null;
76
+ }
77
+
78
+ declare class Products extends APIResource {
79
+ list(params: ProductListParams): Promise<KeepaProduct[]>;
80
+ /** Fetch a single product by ASIN. Throws `ProductNotFoundError` when Keepa
81
+ * has no record for the ASIN (no product returned, or a stub with no title). */
82
+ retrieve(params: ProductRetrieveParams): Promise<KeepaProduct>;
83
+ }
84
+
85
+ interface ClientOptions {
86
+ /** Keepa API key. Falls back to `process.env.KEEPA_API_KEY`. */
87
+ apiKey?: string;
88
+ /** Base URL for the Keepa API. Defaults to `https://api.keepa.com`. */
89
+ baseURL?: string;
90
+ /** Custom fetch implementation. Defaults to `globalThis.fetch`. */
91
+ fetch?: typeof globalThis.fetch;
92
+ }
93
+ declare class KeepaClient {
94
+ readonly apiKey: string;
95
+ readonly baseURL: string;
96
+ readonly fetch: typeof globalThis.fetch;
97
+ readonly products: Products;
98
+ constructor(options?: ClientOptions);
99
+ /** Internal: used by APIResource subclasses to perform a request. */
100
+ _request<T>(args: RequestArgs): Promise<T>;
101
+ }
102
+
103
+ declare class KeepaError extends Error {
104
+ constructor(message: string);
105
+ }
106
+ declare class APIError extends KeepaError {
107
+ readonly status: number;
108
+ readonly context: string;
109
+ readonly body: string;
110
+ constructor(status: number, context: string, body: string, message?: string);
111
+ static from(response: Response, context: string): Promise<APIError>;
112
+ }
113
+ declare class RateLimitError extends APIError {
114
+ constructor(context: string, body: string);
115
+ }
116
+ declare class AuthenticationError extends APIError {
117
+ constructor(context: string, body: string);
118
+ }
119
+ declare class NetworkError extends KeepaError {
120
+ readonly context: string;
121
+ readonly cause: unknown;
122
+ constructor(context: string, cause: unknown);
123
+ }
124
+
125
+ declare const ASIN_LENGTH = 10;
126
+ declare const ASIN_REGEX: RegExp;
127
+ /** Returns true if the given string is a structurally valid ASIN
128
+ * (10 uppercase alphanumeric characters). Note: a passing ASIN may still
129
+ * not exist in Keepa's database — use `isFoundProduct` to check that. */
130
+ declare function isValidAsin(value: string): boolean;
131
+ /** Trim + uppercase a list of ASINs and validate each. Returns the normalized
132
+ * list. Throws with all invalid ASINs listed in the error message. */
133
+ declare function normalizeAsins(asins: string[]): string[];
134
+
135
+ /** Returns true when Keepa returned an actual product record (not just a stub
136
+ * for an unknown ASIN). Stubs come back with `title: null`; real listings
137
+ * always have a string. Use as a `.filter()` predicate to drop empty matches. */
138
+ declare function isFoundProduct(product: KeepaProduct): boolean;
139
+ /** Extract the most recent real BSR from Keepa's `[ts, rank, ts, rank, ...]` salesRanks array.
140
+ * Walks backward through rank entries (odd indices) and skips Keepa's `-1` sentinel which
141
+ * marks "no data captured at that timestamp". Returns `null` if every entry is sentinel.
142
+ * Most consumers won't need this — `Products.list` already fills `bsr` on every returned
143
+ * product. Useful when working with a raw Keepa response. */
144
+ declare function extractBsr(salesRanks: Record<string, number[]> | undefined, rootCategory: number | undefined): number | null;
145
+
146
+ /** Thrown by `Products.retrieve` when Keepa has no record for the requested ASIN
147
+ * (either no product object returned, or a stub with no title). The `asin`
148
+ * property carries the value the caller passed in, unchanged, so it can be
149
+ * surfaced in user-facing messages without re-deriving it. */
150
+ declare class ProductNotFoundError extends KeepaError {
151
+ readonly asin: string;
152
+ constructor(asin: string);
153
+ }
154
+
155
+ declare const PRODUCT_PATH = "/product";
156
+ declare const PRODUCT_LIST_CONTEXT = "products.list";
157
+ declare const DEFAULT_DAYS = 1;
158
+ declare const AMAZON_IMAGE_BASE = "https://m.media-amazon.com/images/I";
159
+ declare const KEEPA_NO_DATA_SENTINEL = -1;
160
+
161
+ declare const VERSION = "0.1.0";
162
+
163
+ export { AMAZON_IMAGE_BASE, APIError, APIResource, ASIN_LENGTH, ASIN_REGEX, AuthenticationError, type ClientOptions, DEFAULT_DAYS, type DomainId, KEEPA_NO_DATA_SENTINEL, type KeepaCategoryNode, KeepaClient, KeepaError, type KeepaProduct, type KeepaVariation, type KeepaVariationAttribute, MARKETPLACE_DOMAINS, type Marketplace, NetworkError, PRODUCT_LIST_CONTEXT, PRODUCT_PATH, type ProductListParams, ProductNotFoundError, type ProductRetrieveParams, Products, RateLimitError, VERSION, KeepaClient as default, extractBsr, isFoundProduct, isValidAsin, normalizeAsins, resolveDomainId };