keepa-api 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -311,6 +311,7 @@ function toKeepaProduct(raw) {
311
311
  price: { amazon, new: new_, list }
312
312
  },
313
313
  stats: {
314
+ buyBoxPrice: parsePrice(raw.stats?.buyBoxPrice),
314
315
  buyBoxSavingBasis: parsePrice(raw.stats?.buyBoxSavingBasis),
315
316
  buyBoxSavingBasisType: parseSavingBasisType(raw.stats?.buyBoxSavingBasisType)
316
317
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/error.ts","../src/core/rate-limit.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/resources/categories/categories.ts","../src/resources/bestsellers/bestsellers.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 type { RateLimitInfo } from './core/rate-limit.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","import type { RateLimitInfo } from './rate-limit.js';\n\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 from(\n status: number,\n context: string,\n body: string,\n rateLimit: RateLimitInfo | null = null,\n ): APIError {\n switch (status) {\n case 429:\n return new RateLimitError(context, body, rateLimit);\n case 401:\n return new AuthenticationError(context, body);\n default:\n return new APIError(status, context, body);\n }\n }\n}\n\nexport class RateLimitError extends APIError {\n /** Bucket snapshot at the moment the 429 was returned. Null if Keepa's body\n * didn't carry the usual rate-limit fields. */\n readonly rateLimit: RateLimitInfo | null;\n\n constructor(context: string, body: string, rateLimit: RateLimitInfo | null = null) {\n super(429, context, body, 'Keepa rate limit exceeded, please wait or upgrade plan');\n this.name = 'RateLimitError';\n this.rateLimit = rateLimit;\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","export interface RateLimitInfo {\n /** Tokens remaining in the bucket after this response. */\n tokensLeft: number;\n /** Milliseconds until the next token refills. */\n refillIn: number;\n /** Tokens added to the bucket per minute. */\n refillRate: number;\n /** Reduction applied to the refill rate during high system load. */\n tokenFlowReduction: number;\n /** Local time the snapshot was received. */\n receivedAt: Date;\n}\n\n// Keepa includes these fields on every response body — 200 and 429 alike — so a\n// single extractor works for both the success path and the rate-limit error path.\nexport function extractRateLimit(body: unknown): RateLimitInfo | null {\n if (!body || typeof body !== 'object') return null;\n const b = body as Record<string, unknown>;\n if (typeof b.tokensLeft !== 'number') return null;\n return {\n tokensLeft: b.tokensLeft,\n refillIn: typeof b.refillIn === 'number' ? b.refillIn : 0,\n refillRate: typeof b.refillRate === 'number' ? b.refillRate : 0,\n tokenFlowReduction: typeof b.tokenFlowReduction === 'number' ? b.tokenFlowReduction : 0,\n receivedAt: new Date(),\n };\n}\n","import { APIError, NetworkError } from './error.js';\nimport { extractRateLimit, type RateLimitInfo } from './rate-limit.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 /** Fired once per response (200 or 429) when Keepa's bucket fields are\n * present on the body. */\n onRateLimit?: (info: RateLimitInfo) => void;\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\n // Read the body once — Response bodies are single-use streams, and we need\n // the same payload for both rate-limit extraction and (on errors) the body\n // attached to the thrown error.\n const text = await res.text().catch(() => '');\n const body = safeJsonParse(text);\n\n const rl = extractRateLimit(body);\n if (rl) config.onRateLimit?.(rl);\n\n if (!res.ok) throw APIError.from(res.status, args.context, text, rl);\n return body as T;\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\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\nexport const SavingBasisType = {\n LIST_PRICE: 0,\n WAS_PRICE: 1,\n} as const;\nexport type SavingBasisType = (typeof SavingBasisType)[keyof typeof SavingBasisType];\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 SavingBasisType,\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 new_ = parsePriceHistory(raw.csv?.[CsvType.NEW]);\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 amazonPrice: amazon.at(-1)?.price ?? null,\n newPrice: new_.at(-1)?.price ?? null,\n listPrice: list.at(-1)?.price ?? null,\n monthlySold: parseMonthlySold(raw.monthlySold),\n referralFeePercent: parseReferralFeePercent(raw.referralFeePercent),\n history: {\n price: { amazon, new: new_, list },\n },\n stats: {\n buyBoxSavingBasis: parsePrice(raw.stats?.buyBoxSavingBasis),\n buyBoxSavingBasisType: parseSavingBasisType(raw.stats?.buyBoxSavingBasisType),\n },\n };\n}\n\n/** Keepa's smallest-currency unit (cents/pence/…) → marketplace's major unit.\n * Keepa scales JPY/INR/BRL by 100 too, so /100 produces the right unit across\n * every supported region. Returns null for missing values, non-finite numbers,\n * and the `-1` no-data sentinel. */\nexport function parsePrice(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value / 100;\n}\n\n/** Keepa returns `-1` when Amazon doesn't show the \"bought in past month\"\n * widget for the ASIN; surface that as null so callers can distinguish \"no\n * data\" from a genuine zero. */\nexport function parseMonthlySold(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\n/** Keepa returns `-1` when the referral fee percent is unknown; surface that as\n * null so callers can distinguish \"no data\" from a genuine zero. */\nexport function parseReferralFeePercent(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\nexport function parseSavingBasisType(value: unknown): SavingBasisType | null {\n return value === SavingBasisType.LIST_PRICE || value === SavingBasisType.WAS_PRICE\n ? value\n : null;\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\nexport function parsePriceHistory(series: number[] | undefined): PriceHistoryEntry[] {\n return pairKeepaSeries(series).flatMap(({ timestamp, value }) => {\n const price = parsePrice(value);\n return price === null ? [] : [{ timestamp: keepaMinutesToDate(timestamp), price }];\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 // Keepa's `stats` parameter is the number of days to compute stats over;\n // we pass `days` when the flag is on, 0 to disable.\n stats: params.stats ? (params.days ?? DEFAULT_DAYS) : 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 { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n CategoryListParams,\n CategorySearchHit,\n CategorySearchParams,\n KeepaCategory,\n} from './category.type.js';\n\ninterface CategoryListResponseRaw {\n categories?: Record<string, KeepaCategory>;\n}\n\ninterface CategorySearchResponseRaw {\n categories?: Record<string, CategorySearchHit>;\n}\n\nexport class Categories extends APIResource {\n /**\n * Resolve category metadata by browse-node id. Returns a map keyed by\n * `catId` for easy lookup; missing ids in the response (Keepa silently\n * omits unknown nodes) are simply absent from the result.\n */\n async list(\n params: CategoryListParams,\n ): Promise<Record<number, KeepaCategory>> {\n if (params.ids.length === 0) return {};\n const data = await this._client._request<CategoryListResponseRaw>({\n path: '/category',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.ids,\n parents: params.withParents ? 1 : 0,\n },\n context: 'categories.list',\n });\n const out: Record<number, KeepaCategory> = {};\n for (const [idStr, cat] of Object.entries(data.categories ?? {})) {\n out[Number(idStr)] = cat;\n }\n return out;\n }\n\n /**\n * Free-text search against Keepa's category index. Hits the\n * `/search?type=category` endpoint — Keepa's `/search` is category-\n * specific despite the generic-sounding path, so it lives here as a\n * Categories method rather than a separate resource.\n *\n * Empty array on \"no matches\" — Keepa doesn't distinguish that from a\n * missing `categories` field in the response.\n */\n async search(params: CategorySearchParams): Promise<CategorySearchHit[]> {\n const data = await this._client._request<CategorySearchResponseRaw>({\n path: '/search',\n query: {\n domain: resolveDomainId(params.marketplace),\n type: 'category',\n term: params.term,\n },\n context: 'categories.search',\n });\n return Object.values(data.categories ?? {});\n }\n}\n","import { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n BestSellerRetrieveParams,\n KeepaBestSellerList,\n} from './bestseller.type.js';\n\ninterface BestSellersResponseRaw {\n bestSellersList?: {\n asinList?: string[];\n categoryId?: number;\n } | null;\n}\n\nexport class BestSellers extends APIResource {\n /**\n * Fetch the best-seller list for a category. Returns null when Keepa has\n * no list (typical for non-leaf nodes and sparse leaves) rather than\n * throwing — callers usually want to surface \"no data\" as a row state,\n * not an error.\n */\n async retrieve(\n params: BestSellerRetrieveParams,\n ): Promise<KeepaBestSellerList | null> {\n const data = await this._client._request<BestSellersResponseRaw>({\n path: '/bestsellers',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.categoryId,\n sublist: params.sublist === false ? 0 : 1,\n },\n context: 'bestsellers.retrieve',\n });\n if (!data.bestSellersList) return null;\n return {\n categoryId: data.bestSellersList.categoryId ?? params.categoryId,\n asinList: data.bestSellersList.asinList ?? [],\n };\n }\n}\n","import { request } from './core/request.js';\nimport type { RequestArgs, RequestConfig } from './core/request.js';\nimport type { RateLimitInfo } from './core/rate-limit.js';\nimport { Products } from './resources/products/products.js';\nimport { Categories } from './resources/categories/categories.js';\nimport { BestSellers } from './resources/bestsellers/bestsellers.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 readonly categories: Categories;\n readonly bestSellers: BestSellers;\n\n /** Latest rate-limit snapshot from Keepa, updated after every response.\n * Null until the first request completes. */\n rateLimit: RateLimitInfo | null = null;\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 this.categories = new Categories(this);\n this.bestSellers = new BestSellers(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 onRateLimit: (info) => {\n this.rateLimit = info;\n },\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":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/error.ts","../src/core/rate-limit.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/resources/categories/categories.ts","../src/resources/bestsellers/bestsellers.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 type { RateLimitInfo } from './core/rate-limit.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","import type { RateLimitInfo } from './rate-limit.js';\n\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 from(\n status: number,\n context: string,\n body: string,\n rateLimit: RateLimitInfo | null = null,\n ): APIError {\n switch (status) {\n case 429:\n return new RateLimitError(context, body, rateLimit);\n case 401:\n return new AuthenticationError(context, body);\n default:\n return new APIError(status, context, body);\n }\n }\n}\n\nexport class RateLimitError extends APIError {\n /** Bucket snapshot at the moment the 429 was returned. Null if Keepa's body\n * didn't carry the usual rate-limit fields. */\n readonly rateLimit: RateLimitInfo | null;\n\n constructor(context: string, body: string, rateLimit: RateLimitInfo | null = null) {\n super(429, context, body, 'Keepa rate limit exceeded, please wait or upgrade plan');\n this.name = 'RateLimitError';\n this.rateLimit = rateLimit;\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","export interface RateLimitInfo {\n /** Tokens remaining in the bucket after this response. */\n tokensLeft: number;\n /** Milliseconds until the next token refills. */\n refillIn: number;\n /** Tokens added to the bucket per minute. */\n refillRate: number;\n /** Reduction applied to the refill rate during high system load. */\n tokenFlowReduction: number;\n /** Local time the snapshot was received. */\n receivedAt: Date;\n}\n\n// Keepa includes these fields on every response body — 200 and 429 alike — so a\n// single extractor works for both the success path and the rate-limit error path.\nexport function extractRateLimit(body: unknown): RateLimitInfo | null {\n if (!body || typeof body !== 'object') return null;\n const b = body as Record<string, unknown>;\n if (typeof b.tokensLeft !== 'number') return null;\n return {\n tokensLeft: b.tokensLeft,\n refillIn: typeof b.refillIn === 'number' ? b.refillIn : 0,\n refillRate: typeof b.refillRate === 'number' ? b.refillRate : 0,\n tokenFlowReduction: typeof b.tokenFlowReduction === 'number' ? b.tokenFlowReduction : 0,\n receivedAt: new Date(),\n };\n}\n","import { APIError, NetworkError } from './error.js';\nimport { extractRateLimit, type RateLimitInfo } from './rate-limit.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 /** Fired once per response (200 or 429) when Keepa's bucket fields are\n * present on the body. */\n onRateLimit?: (info: RateLimitInfo) => void;\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\n // Read the body once — Response bodies are single-use streams, and we need\n // the same payload for both rate-limit extraction and (on errors) the body\n // attached to the thrown error.\n const text = await res.text().catch(() => '');\n const body = safeJsonParse(text);\n\n const rl = extractRateLimit(body);\n if (rl) config.onRateLimit?.(rl);\n\n if (!res.ok) throw APIError.from(res.status, args.context, text, rl);\n return body as T;\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\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\nexport const SavingBasisType = {\n LIST_PRICE: 0,\n WAS_PRICE: 1,\n} as const;\nexport type SavingBasisType = (typeof SavingBasisType)[keyof typeof SavingBasisType];\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 SavingBasisType,\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 new_ = parsePriceHistory(raw.csv?.[CsvType.NEW]);\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 amazonPrice: amazon.at(-1)?.price ?? null,\n newPrice: new_.at(-1)?.price ?? null,\n listPrice: list.at(-1)?.price ?? null,\n monthlySold: parseMonthlySold(raw.monthlySold),\n referralFeePercent: parseReferralFeePercent(raw.referralFeePercent),\n history: {\n price: { amazon, new: new_, list },\n },\n stats: {\n buyBoxPrice: parsePrice(raw.stats?.buyBoxPrice),\n buyBoxSavingBasis: parsePrice(raw.stats?.buyBoxSavingBasis),\n buyBoxSavingBasisType: parseSavingBasisType(raw.stats?.buyBoxSavingBasisType),\n },\n };\n}\n\n/** Keepa's smallest-currency unit (cents/pence/…) → marketplace's major unit.\n * Keepa scales JPY/INR/BRL by 100 too, so /100 produces the right unit across\n * every supported region. Returns null for missing values, non-finite numbers,\n * and the `-1` no-data sentinel. */\nexport function parsePrice(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value / 100;\n}\n\n/** Keepa returns `-1` when Amazon doesn't show the \"bought in past month\"\n * widget for the ASIN; surface that as null so callers can distinguish \"no\n * data\" from a genuine zero. */\nexport function parseMonthlySold(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\n/** Keepa returns `-1` when the referral fee percent is unknown; surface that as\n * null so callers can distinguish \"no data\" from a genuine zero. */\nexport function parseReferralFeePercent(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\nexport function parseSavingBasisType(value: unknown): SavingBasisType | null {\n return value === SavingBasisType.LIST_PRICE || value === SavingBasisType.WAS_PRICE\n ? value\n : null;\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\nexport function parsePriceHistory(series: number[] | undefined): PriceHistoryEntry[] {\n return pairKeepaSeries(series).flatMap(({ timestamp, value }) => {\n const price = parsePrice(value);\n return price === null ? [] : [{ timestamp: keepaMinutesToDate(timestamp), price }];\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 // Keepa's `stats` parameter is the number of days to compute stats over;\n // we pass `days` when the flag is on, 0 to disable.\n stats: params.stats ? (params.days ?? DEFAULT_DAYS) : 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 { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n CategoryListParams,\n CategorySearchHit,\n CategorySearchParams,\n KeepaCategory,\n} from './category.type.js';\n\ninterface CategoryListResponseRaw {\n categories?: Record<string, KeepaCategory>;\n}\n\ninterface CategorySearchResponseRaw {\n categories?: Record<string, CategorySearchHit>;\n}\n\nexport class Categories extends APIResource {\n /**\n * Resolve category metadata by browse-node id. Returns a map keyed by\n * `catId` for easy lookup; missing ids in the response (Keepa silently\n * omits unknown nodes) are simply absent from the result.\n */\n async list(\n params: CategoryListParams,\n ): Promise<Record<number, KeepaCategory>> {\n if (params.ids.length === 0) return {};\n const data = await this._client._request<CategoryListResponseRaw>({\n path: '/category',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.ids,\n parents: params.withParents ? 1 : 0,\n },\n context: 'categories.list',\n });\n const out: Record<number, KeepaCategory> = {};\n for (const [idStr, cat] of Object.entries(data.categories ?? {})) {\n out[Number(idStr)] = cat;\n }\n return out;\n }\n\n /**\n * Free-text search against Keepa's category index. Hits the\n * `/search?type=category` endpoint — Keepa's `/search` is category-\n * specific despite the generic-sounding path, so it lives here as a\n * Categories method rather than a separate resource.\n *\n * Empty array on \"no matches\" — Keepa doesn't distinguish that from a\n * missing `categories` field in the response.\n */\n async search(params: CategorySearchParams): Promise<CategorySearchHit[]> {\n const data = await this._client._request<CategorySearchResponseRaw>({\n path: '/search',\n query: {\n domain: resolveDomainId(params.marketplace),\n type: 'category',\n term: params.term,\n },\n context: 'categories.search',\n });\n return Object.values(data.categories ?? {});\n }\n}\n","import { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n BestSellerRetrieveParams,\n KeepaBestSellerList,\n} from './bestseller.type.js';\n\ninterface BestSellersResponseRaw {\n bestSellersList?: {\n asinList?: string[];\n categoryId?: number;\n } | null;\n}\n\nexport class BestSellers extends APIResource {\n /**\n * Fetch the best-seller list for a category. Returns null when Keepa has\n * no list (typical for non-leaf nodes and sparse leaves) rather than\n * throwing — callers usually want to surface \"no data\" as a row state,\n * not an error.\n */\n async retrieve(\n params: BestSellerRetrieveParams,\n ): Promise<KeepaBestSellerList | null> {\n const data = await this._client._request<BestSellersResponseRaw>({\n path: '/bestsellers',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.categoryId,\n sublist: params.sublist === false ? 0 : 1,\n },\n context: 'bestsellers.retrieve',\n });\n if (!data.bestSellersList) return null;\n return {\n categoryId: data.bestSellersList.categoryId ?? params.categoryId,\n asinList: data.bestSellersList.asinList ?? [],\n };\n }\n}\n","import { request } from './core/request.js';\nimport type { RequestArgs, RequestConfig } from './core/request.js';\nimport type { RateLimitInfo } from './core/rate-limit.js';\nimport { Products } from './resources/products/products.js';\nimport { Categories } from './resources/categories/categories.js';\nimport { BestSellers } from './resources/bestsellers/bestsellers.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 readonly categories: Categories;\n readonly bestSellers: BestSellers;\n\n /** Latest rate-limit snapshot from Keepa, updated after every response.\n * Null until the first request completes. */\n rateLimit: RateLimitInfo | null = null;\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 this.categories = new Categories(this);\n this.bestSellers = new BestSellers(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 onRateLimit: (info) => {\n this.rateLimit = info;\n },\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":[]}
package/dist/index.d.cts CHANGED
@@ -170,6 +170,9 @@ interface KeepaProduct {
170
170
  };
171
171
  };
172
172
  stats: {
173
+ /** Current buy box price in the marketplace's major unit. Null when
174
+ * `stats: false` was used or there's no buy box (Keepa's `-1` sentinel). */
175
+ buyBoxPrice: number | null;
173
176
  /** Buy box strikethrough reference price in the marketplace's major unit.
174
177
  * Null when `stats: false` was used or Keepa has no saving-basis data. */
175
178
  buyBoxSavingBasis: number | null;
package/dist/index.d.ts CHANGED
@@ -170,6 +170,9 @@ interface KeepaProduct {
170
170
  };
171
171
  };
172
172
  stats: {
173
+ /** Current buy box price in the marketplace's major unit. Null when
174
+ * `stats: false` was used or there's no buy box (Keepa's `-1` sentinel). */
175
+ buyBoxPrice: number | null;
173
176
  /** Buy box strikethrough reference price in the marketplace's major unit.
174
177
  * Null when `stats: false` was used or Keepa has no saving-basis data. */
175
178
  buyBoxSavingBasis: number | null;
package/dist/index.js CHANGED
@@ -254,6 +254,7 @@ function toKeepaProduct(raw) {
254
254
  price: { amazon, new: new_, list }
255
255
  },
256
256
  stats: {
257
+ buyBoxPrice: parsePrice(raw.stats?.buyBoxPrice),
257
258
  buyBoxSavingBasis: parsePrice(raw.stats?.buyBoxSavingBasis),
258
259
  buyBoxSavingBasisType: parseSavingBasisType(raw.stats?.buyBoxSavingBasisType)
259
260
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/error.ts","../src/core/rate-limit.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/resources/categories/categories.ts","../src/resources/bestsellers/bestsellers.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["import type { RateLimitInfo } from './rate-limit.js';\n\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 from(\n status: number,\n context: string,\n body: string,\n rateLimit: RateLimitInfo | null = null,\n ): APIError {\n switch (status) {\n case 429:\n return new RateLimitError(context, body, rateLimit);\n case 401:\n return new AuthenticationError(context, body);\n default:\n return new APIError(status, context, body);\n }\n }\n}\n\nexport class RateLimitError extends APIError {\n /** Bucket snapshot at the moment the 429 was returned. Null if Keepa's body\n * didn't carry the usual rate-limit fields. */\n readonly rateLimit: RateLimitInfo | null;\n\n constructor(context: string, body: string, rateLimit: RateLimitInfo | null = null) {\n super(429, context, body, 'Keepa rate limit exceeded, please wait or upgrade plan');\n this.name = 'RateLimitError';\n this.rateLimit = rateLimit;\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","export interface RateLimitInfo {\n /** Tokens remaining in the bucket after this response. */\n tokensLeft: number;\n /** Milliseconds until the next token refills. */\n refillIn: number;\n /** Tokens added to the bucket per minute. */\n refillRate: number;\n /** Reduction applied to the refill rate during high system load. */\n tokenFlowReduction: number;\n /** Local time the snapshot was received. */\n receivedAt: Date;\n}\n\n// Keepa includes these fields on every response body — 200 and 429 alike — so a\n// single extractor works for both the success path and the rate-limit error path.\nexport function extractRateLimit(body: unknown): RateLimitInfo | null {\n if (!body || typeof body !== 'object') return null;\n const b = body as Record<string, unknown>;\n if (typeof b.tokensLeft !== 'number') return null;\n return {\n tokensLeft: b.tokensLeft,\n refillIn: typeof b.refillIn === 'number' ? b.refillIn : 0,\n refillRate: typeof b.refillRate === 'number' ? b.refillRate : 0,\n tokenFlowReduction: typeof b.tokenFlowReduction === 'number' ? b.tokenFlowReduction : 0,\n receivedAt: new Date(),\n };\n}\n","import { APIError, NetworkError } from './error.js';\nimport { extractRateLimit, type RateLimitInfo } from './rate-limit.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 /** Fired once per response (200 or 429) when Keepa's bucket fields are\n * present on the body. */\n onRateLimit?: (info: RateLimitInfo) => void;\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\n // Read the body once — Response bodies are single-use streams, and we need\n // the same payload for both rate-limit extraction and (on errors) the body\n // attached to the thrown error.\n const text = await res.text().catch(() => '');\n const body = safeJsonParse(text);\n\n const rl = extractRateLimit(body);\n if (rl) config.onRateLimit?.(rl);\n\n if (!res.ok) throw APIError.from(res.status, args.context, text, rl);\n return body as T;\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\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\nexport const SavingBasisType = {\n LIST_PRICE: 0,\n WAS_PRICE: 1,\n} as const;\nexport type SavingBasisType = (typeof SavingBasisType)[keyof typeof SavingBasisType];\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 SavingBasisType,\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 new_ = parsePriceHistory(raw.csv?.[CsvType.NEW]);\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 amazonPrice: amazon.at(-1)?.price ?? null,\n newPrice: new_.at(-1)?.price ?? null,\n listPrice: list.at(-1)?.price ?? null,\n monthlySold: parseMonthlySold(raw.monthlySold),\n referralFeePercent: parseReferralFeePercent(raw.referralFeePercent),\n history: {\n price: { amazon, new: new_, list },\n },\n stats: {\n buyBoxSavingBasis: parsePrice(raw.stats?.buyBoxSavingBasis),\n buyBoxSavingBasisType: parseSavingBasisType(raw.stats?.buyBoxSavingBasisType),\n },\n };\n}\n\n/** Keepa's smallest-currency unit (cents/pence/…) → marketplace's major unit.\n * Keepa scales JPY/INR/BRL by 100 too, so /100 produces the right unit across\n * every supported region. Returns null for missing values, non-finite numbers,\n * and the `-1` no-data sentinel. */\nexport function parsePrice(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value / 100;\n}\n\n/** Keepa returns `-1` when Amazon doesn't show the \"bought in past month\"\n * widget for the ASIN; surface that as null so callers can distinguish \"no\n * data\" from a genuine zero. */\nexport function parseMonthlySold(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\n/** Keepa returns `-1` when the referral fee percent is unknown; surface that as\n * null so callers can distinguish \"no data\" from a genuine zero. */\nexport function parseReferralFeePercent(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\nexport function parseSavingBasisType(value: unknown): SavingBasisType | null {\n return value === SavingBasisType.LIST_PRICE || value === SavingBasisType.WAS_PRICE\n ? value\n : null;\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\nexport function parsePriceHistory(series: number[] | undefined): PriceHistoryEntry[] {\n return pairKeepaSeries(series).flatMap(({ timestamp, value }) => {\n const price = parsePrice(value);\n return price === null ? [] : [{ timestamp: keepaMinutesToDate(timestamp), price }];\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 // Keepa's `stats` parameter is the number of days to compute stats over;\n // we pass `days` when the flag is on, 0 to disable.\n stats: params.stats ? (params.days ?? DEFAULT_DAYS) : 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 { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n CategoryListParams,\n CategorySearchHit,\n CategorySearchParams,\n KeepaCategory,\n} from './category.type.js';\n\ninterface CategoryListResponseRaw {\n categories?: Record<string, KeepaCategory>;\n}\n\ninterface CategorySearchResponseRaw {\n categories?: Record<string, CategorySearchHit>;\n}\n\nexport class Categories extends APIResource {\n /**\n * Resolve category metadata by browse-node id. Returns a map keyed by\n * `catId` for easy lookup; missing ids in the response (Keepa silently\n * omits unknown nodes) are simply absent from the result.\n */\n async list(\n params: CategoryListParams,\n ): Promise<Record<number, KeepaCategory>> {\n if (params.ids.length === 0) return {};\n const data = await this._client._request<CategoryListResponseRaw>({\n path: '/category',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.ids,\n parents: params.withParents ? 1 : 0,\n },\n context: 'categories.list',\n });\n const out: Record<number, KeepaCategory> = {};\n for (const [idStr, cat] of Object.entries(data.categories ?? {})) {\n out[Number(idStr)] = cat;\n }\n return out;\n }\n\n /**\n * Free-text search against Keepa's category index. Hits the\n * `/search?type=category` endpoint — Keepa's `/search` is category-\n * specific despite the generic-sounding path, so it lives here as a\n * Categories method rather than a separate resource.\n *\n * Empty array on \"no matches\" — Keepa doesn't distinguish that from a\n * missing `categories` field in the response.\n */\n async search(params: CategorySearchParams): Promise<CategorySearchHit[]> {\n const data = await this._client._request<CategorySearchResponseRaw>({\n path: '/search',\n query: {\n domain: resolveDomainId(params.marketplace),\n type: 'category',\n term: params.term,\n },\n context: 'categories.search',\n });\n return Object.values(data.categories ?? {});\n }\n}\n","import { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n BestSellerRetrieveParams,\n KeepaBestSellerList,\n} from './bestseller.type.js';\n\ninterface BestSellersResponseRaw {\n bestSellersList?: {\n asinList?: string[];\n categoryId?: number;\n } | null;\n}\n\nexport class BestSellers extends APIResource {\n /**\n * Fetch the best-seller list for a category. Returns null when Keepa has\n * no list (typical for non-leaf nodes and sparse leaves) rather than\n * throwing — callers usually want to surface \"no data\" as a row state,\n * not an error.\n */\n async retrieve(\n params: BestSellerRetrieveParams,\n ): Promise<KeepaBestSellerList | null> {\n const data = await this._client._request<BestSellersResponseRaw>({\n path: '/bestsellers',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.categoryId,\n sublist: params.sublist === false ? 0 : 1,\n },\n context: 'bestsellers.retrieve',\n });\n if (!data.bestSellersList) return null;\n return {\n categoryId: data.bestSellersList.categoryId ?? params.categoryId,\n asinList: data.bestSellersList.asinList ?? [],\n };\n }\n}\n","import { request } from './core/request.js';\nimport type { RequestArgs, RequestConfig } from './core/request.js';\nimport type { RateLimitInfo } from './core/rate-limit.js';\nimport { Products } from './resources/products/products.js';\nimport { Categories } from './resources/categories/categories.js';\nimport { BestSellers } from './resources/bestsellers/bestsellers.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 readonly categories: Categories;\n readonly bestSellers: BestSellers;\n\n /** Latest rate-limit snapshot from Keepa, updated after every response.\n * Null until the first request completes. */\n rateLimit: RateLimitInfo | null = null;\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 this.categories = new Categories(this);\n this.bestSellers = new BestSellers(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 onRateLimit: (info) => {\n this.rateLimit = info;\n },\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":[]}
1
+ {"version":3,"sources":["../src/core/error.ts","../src/core/rate-limit.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/resources/categories/categories.ts","../src/resources/bestsellers/bestsellers.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["import type { RateLimitInfo } from './rate-limit.js';\n\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 from(\n status: number,\n context: string,\n body: string,\n rateLimit: RateLimitInfo | null = null,\n ): APIError {\n switch (status) {\n case 429:\n return new RateLimitError(context, body, rateLimit);\n case 401:\n return new AuthenticationError(context, body);\n default:\n return new APIError(status, context, body);\n }\n }\n}\n\nexport class RateLimitError extends APIError {\n /** Bucket snapshot at the moment the 429 was returned. Null if Keepa's body\n * didn't carry the usual rate-limit fields. */\n readonly rateLimit: RateLimitInfo | null;\n\n constructor(context: string, body: string, rateLimit: RateLimitInfo | null = null) {\n super(429, context, body, 'Keepa rate limit exceeded, please wait or upgrade plan');\n this.name = 'RateLimitError';\n this.rateLimit = rateLimit;\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","export interface RateLimitInfo {\n /** Tokens remaining in the bucket after this response. */\n tokensLeft: number;\n /** Milliseconds until the next token refills. */\n refillIn: number;\n /** Tokens added to the bucket per minute. */\n refillRate: number;\n /** Reduction applied to the refill rate during high system load. */\n tokenFlowReduction: number;\n /** Local time the snapshot was received. */\n receivedAt: Date;\n}\n\n// Keepa includes these fields on every response body — 200 and 429 alike — so a\n// single extractor works for both the success path and the rate-limit error path.\nexport function extractRateLimit(body: unknown): RateLimitInfo | null {\n if (!body || typeof body !== 'object') return null;\n const b = body as Record<string, unknown>;\n if (typeof b.tokensLeft !== 'number') return null;\n return {\n tokensLeft: b.tokensLeft,\n refillIn: typeof b.refillIn === 'number' ? b.refillIn : 0,\n refillRate: typeof b.refillRate === 'number' ? b.refillRate : 0,\n tokenFlowReduction: typeof b.tokenFlowReduction === 'number' ? b.tokenFlowReduction : 0,\n receivedAt: new Date(),\n };\n}\n","import { APIError, NetworkError } from './error.js';\nimport { extractRateLimit, type RateLimitInfo } from './rate-limit.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 /** Fired once per response (200 or 429) when Keepa's bucket fields are\n * present on the body. */\n onRateLimit?: (info: RateLimitInfo) => void;\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\n // Read the body once — Response bodies are single-use streams, and we need\n // the same payload for both rate-limit extraction and (on errors) the body\n // attached to the thrown error.\n const text = await res.text().catch(() => '');\n const body = safeJsonParse(text);\n\n const rl = extractRateLimit(body);\n if (rl) config.onRateLimit?.(rl);\n\n if (!res.ok) throw APIError.from(res.status, args.context, text, rl);\n return body as T;\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return null;\n }\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\nexport const SavingBasisType = {\n LIST_PRICE: 0,\n WAS_PRICE: 1,\n} as const;\nexport type SavingBasisType = (typeof SavingBasisType)[keyof typeof SavingBasisType];\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 SavingBasisType,\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 new_ = parsePriceHistory(raw.csv?.[CsvType.NEW]);\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 amazonPrice: amazon.at(-1)?.price ?? null,\n newPrice: new_.at(-1)?.price ?? null,\n listPrice: list.at(-1)?.price ?? null,\n monthlySold: parseMonthlySold(raw.monthlySold),\n referralFeePercent: parseReferralFeePercent(raw.referralFeePercent),\n history: {\n price: { amazon, new: new_, list },\n },\n stats: {\n buyBoxPrice: parsePrice(raw.stats?.buyBoxPrice),\n buyBoxSavingBasis: parsePrice(raw.stats?.buyBoxSavingBasis),\n buyBoxSavingBasisType: parseSavingBasisType(raw.stats?.buyBoxSavingBasisType),\n },\n };\n}\n\n/** Keepa's smallest-currency unit (cents/pence/…) → marketplace's major unit.\n * Keepa scales JPY/INR/BRL by 100 too, so /100 produces the right unit across\n * every supported region. Returns null for missing values, non-finite numbers,\n * and the `-1` no-data sentinel. */\nexport function parsePrice(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value / 100;\n}\n\n/** Keepa returns `-1` when Amazon doesn't show the \"bought in past month\"\n * widget for the ASIN; surface that as null so callers can distinguish \"no\n * data\" from a genuine zero. */\nexport function parseMonthlySold(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\n/** Keepa returns `-1` when the referral fee percent is unknown; surface that as\n * null so callers can distinguish \"no data\" from a genuine zero. */\nexport function parseReferralFeePercent(value: unknown): number | null {\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n if (value === KEEPA_NO_DATA_SENTINEL) return null;\n return value;\n}\n\nexport function parseSavingBasisType(value: unknown): SavingBasisType | null {\n return value === SavingBasisType.LIST_PRICE || value === SavingBasisType.WAS_PRICE\n ? value\n : null;\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\nexport function parsePriceHistory(series: number[] | undefined): PriceHistoryEntry[] {\n return pairKeepaSeries(series).flatMap(({ timestamp, value }) => {\n const price = parsePrice(value);\n return price === null ? [] : [{ timestamp: keepaMinutesToDate(timestamp), price }];\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 // Keepa's `stats` parameter is the number of days to compute stats over;\n // we pass `days` when the flag is on, 0 to disable.\n stats: params.stats ? (params.days ?? DEFAULT_DAYS) : 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 { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n CategoryListParams,\n CategorySearchHit,\n CategorySearchParams,\n KeepaCategory,\n} from './category.type.js';\n\ninterface CategoryListResponseRaw {\n categories?: Record<string, KeepaCategory>;\n}\n\ninterface CategorySearchResponseRaw {\n categories?: Record<string, CategorySearchHit>;\n}\n\nexport class Categories extends APIResource {\n /**\n * Resolve category metadata by browse-node id. Returns a map keyed by\n * `catId` for easy lookup; missing ids in the response (Keepa silently\n * omits unknown nodes) are simply absent from the result.\n */\n async list(\n params: CategoryListParams,\n ): Promise<Record<number, KeepaCategory>> {\n if (params.ids.length === 0) return {};\n const data = await this._client._request<CategoryListResponseRaw>({\n path: '/category',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.ids,\n parents: params.withParents ? 1 : 0,\n },\n context: 'categories.list',\n });\n const out: Record<number, KeepaCategory> = {};\n for (const [idStr, cat] of Object.entries(data.categories ?? {})) {\n out[Number(idStr)] = cat;\n }\n return out;\n }\n\n /**\n * Free-text search against Keepa's category index. Hits the\n * `/search?type=category` endpoint — Keepa's `/search` is category-\n * specific despite the generic-sounding path, so it lives here as a\n * Categories method rather than a separate resource.\n *\n * Empty array on \"no matches\" — Keepa doesn't distinguish that from a\n * missing `categories` field in the response.\n */\n async search(params: CategorySearchParams): Promise<CategorySearchHit[]> {\n const data = await this._client._request<CategorySearchResponseRaw>({\n path: '/search',\n query: {\n domain: resolveDomainId(params.marketplace),\n type: 'category',\n term: params.term,\n },\n context: 'categories.search',\n });\n return Object.values(data.categories ?? {});\n }\n}\n","import { APIResource } from '../../core/resource.js';\nimport { resolveDomainId } from '../../lib/marketplace.js';\nimport type {\n BestSellerRetrieveParams,\n KeepaBestSellerList,\n} from './bestseller.type.js';\n\ninterface BestSellersResponseRaw {\n bestSellersList?: {\n asinList?: string[];\n categoryId?: number;\n } | null;\n}\n\nexport class BestSellers extends APIResource {\n /**\n * Fetch the best-seller list for a category. Returns null when Keepa has\n * no list (typical for non-leaf nodes and sparse leaves) rather than\n * throwing — callers usually want to surface \"no data\" as a row state,\n * not an error.\n */\n async retrieve(\n params: BestSellerRetrieveParams,\n ): Promise<KeepaBestSellerList | null> {\n const data = await this._client._request<BestSellersResponseRaw>({\n path: '/bestsellers',\n query: {\n domain: resolveDomainId(params.marketplace),\n category: params.categoryId,\n sublist: params.sublist === false ? 0 : 1,\n },\n context: 'bestsellers.retrieve',\n });\n if (!data.bestSellersList) return null;\n return {\n categoryId: data.bestSellersList.categoryId ?? params.categoryId,\n asinList: data.bestSellersList.asinList ?? [],\n };\n }\n}\n","import { request } from './core/request.js';\nimport type { RequestArgs, RequestConfig } from './core/request.js';\nimport type { RateLimitInfo } from './core/rate-limit.js';\nimport { Products } from './resources/products/products.js';\nimport { Categories } from './resources/categories/categories.js';\nimport { BestSellers } from './resources/bestsellers/bestsellers.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 readonly categories: Categories;\n readonly bestSellers: BestSellers;\n\n /** Latest rate-limit snapshot from Keepa, updated after every response.\n * Null until the first request completes. */\n rateLimit: RateLimitInfo | null = null;\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 this.categories = new Categories(this);\n this.bestSellers = new BestSellers(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 onRateLimit: (info) => {\n this.rateLimit = info;\n },\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keepa-api",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript SDK for the Keepa API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",