/** * Per-key token bucket for outbound marketplace calls (process-local, PM2 * instances: 1). Amazon's Listings Items usage plan is 5 req/s per * seller+app with burst 5 — the flush runs at 4 req/s per order source so a * concurrent interactive push never tips it into 429s. */ const buckets = new Map }>() export interface BucketOpts { ratePerSec: number; burst: number } const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)) export const acquire = async (key: string, opts: BucketOpts): Promise => { let b = buckets.get(key) if (!b) { b = { tokens: opts.burst, last: Date.now(), queue: Promise.resolve() }; buckets.set(key, b) } const bucket = b // serialize waiters per key so the arithmetic below is never interleaved const my = bucket.queue.then(async () => { for (;;) { const now = Date.now() bucket.tokens = Math.min(opts.burst, bucket.tokens + ((now - bucket.last) / 1000) * opts.ratePerSec) bucket.last = now if (bucket.tokens >= 1) { bucket.tokens -= 1; return } await sleep(Math.ceil(((1 - bucket.tokens) / opts.ratePerSec) * 1000)) } }) bucket.queue = my.catch(() => {}) await my } export const LISTINGS_WRITE_BUCKET: BucketOpts = { ratePerSec: 4, burst: 4 } export const LISTINGS_READ_BUCKET: BucketOpts = { ratePerSec: 4, burst: 4 }