@ar-agents/mercadopago 0.14.0 → 0.15.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/CHANGELOG.md +51 -0
- package/README.md +11 -0
- package/README.skeleton.md +200 -0
- package/dist/index.cjs +59 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +59 -1
- package/dist/index.js.map +1 -1
- package/dist/vercel-kv.cjs +15 -3
- package/dist/vercel-kv.cjs.map +1 -1
- package/dist/vercel-kv.d.cts +39 -8
- package/dist/vercel-kv.d.ts +39 -8
- package/dist/vercel-kv.js +15 -3
- package/dist/vercel-kv.js.map +1 -1
- package/package.json +1 -1
- package/tools.manifest.json +545 -297
package/dist/vercel-kv.cjs
CHANGED
|
@@ -112,11 +112,17 @@ var VercelKVRateLimiter = class {
|
|
|
112
112
|
/**
|
|
113
113
|
* Acquire a token. Resolves immediately if the distributed bucket has
|
|
114
114
|
* one available; otherwise waits until refilled. Throws if the wait
|
|
115
|
-
* exceeds `acquireTimeoutMs
|
|
115
|
+
* exceeds `acquireTimeoutMs` or if the retry cap is reached.
|
|
116
|
+
*
|
|
117
|
+
* Caps retries at 8 iterations so a misconfigured bucket (capacity too
|
|
118
|
+
* low for traffic) fails fast for the agent layer to surface, instead
|
|
119
|
+
* of silently burning serverless compute time.
|
|
116
120
|
*/
|
|
117
121
|
async acquire() {
|
|
118
122
|
const start = Date.now();
|
|
119
|
-
|
|
123
|
+
const MAX_RETRIES = 8;
|
|
124
|
+
let attempt = 0;
|
|
125
|
+
while (attempt < MAX_RETRIES) {
|
|
120
126
|
const now = Date.now();
|
|
121
127
|
const state = this.refill(await this.readState(), now);
|
|
122
128
|
if (state.tokens >= 1) {
|
|
@@ -125,7 +131,9 @@ var VercelKVRateLimiter = class {
|
|
|
125
131
|
return;
|
|
126
132
|
}
|
|
127
133
|
const tokensNeeded = 1 - state.tokens;
|
|
128
|
-
const
|
|
134
|
+
const baseWaitMs = Math.ceil(tokensNeeded / this.refillPerSecond * 1e3);
|
|
135
|
+
const jitterFactor = 0.7 + Math.random() * 0.6;
|
|
136
|
+
const waitMs = Math.ceil(baseWaitMs * jitterFactor);
|
|
129
137
|
const elapsed = now - start;
|
|
130
138
|
if (elapsed + waitMs > this.acquireTimeoutMs) {
|
|
131
139
|
throw new Error(
|
|
@@ -133,7 +141,11 @@ var VercelKVRateLimiter = class {
|
|
|
133
141
|
);
|
|
134
142
|
}
|
|
135
143
|
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
144
|
+
attempt += 1;
|
|
136
145
|
}
|
|
146
|
+
throw new Error(
|
|
147
|
+
`VercelKVRateLimiter exhausted ${MAX_RETRIES} retries (key=${this.key}). Bucket likely undersized for traffic \u2014 increase capacity or refillPerSecond.`
|
|
148
|
+
);
|
|
137
149
|
}
|
|
138
150
|
/** Best-effort acquire — returns true if a token was available, false otherwise. */
|
|
139
151
|
async tryAcquire() {
|
package/dist/vercel-kv.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vercel-kv.ts"],"names":["defaultKv"],"mappings":"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","file":"vercel-kv.cjs","sourcesContent":["/**\n * Vercel KV adapters — drop-in `SubscriptionStateAdapter`,\n * `OAuthTokenStore`, and `IdempotencyCache` implementations backed by\n * [Vercel KV](https://vercel.com/docs/storage/vercel-kv) (Upstash Redis).\n *\n * # Why a separate subpath?\n *\n * `@vercel/kv` is a peer dependency — only consumers who actually use Vercel\n * KV install it. Importing from `@ar-agents/mercadopago/vercel-kv` is\n * lazy: the main `@ar-agents/mercadopago` bundle stays tiny for callers who\n * use the in-memory adapters or a different store.\n *\n * # Setup\n *\n * 1. Create a KV store at https://vercel.com/dashboard/stores\n * 2. Connect it to your project — Vercel auto-injects `KV_*` env vars\n * 3. `pnpm add @vercel/kv`\n * 4. Wire the adapters:\n *\n * ```ts\n * import { mercadoPagoTools, MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import {\n * VercelKVSubscriptionStateAdapter,\n * VercelKVOAuthTokenStore,\n * } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * const tools = mercadoPagoTools(client, {\n * state: new VercelKVSubscriptionStateAdapter(),\n * backUrl: \"https://mysite.com/done\",\n * // ... oauth, webhookSecret, etc.\n * });\n *\n * // For marketplace flows, also wire the OAuth token store:\n * const oauthStore = new VercelKVOAuthTokenStore();\n * await oauthStore.set(token.user_id, {\n * user_id: token.user_id,\n * access_token: token.access_token,\n * refresh_token: token.refresh_token!,\n * expires_at: Date.now() + (token.expires_in ?? 21600) * 1000,\n * });\n * ```\n *\n * # Edge Runtime\n *\n * `@vercel/kv` works in Vercel Edge Runtime, Node.js, and any environment\n * with `fetch` (it's a thin REST client over Upstash). All adapters here\n * are async and Edge-safe.\n *\n * # Key namespacing\n *\n * Each adapter uses its own prefix so multiple adapters can share the same\n * KV store without collisions:\n * - Subscriptions: `mp:sub:{id}`\n * - OAuth tokens: `mp:oauth:{userId}`\n * - Idempotency: `mp:idem:{key}`\n *\n * Pass a custom prefix via the constructor if you need to share the store\n * with other apps.\n */\n\nimport { kv as defaultKv } from \"@vercel/kv\";\nimport type { VercelKV } from \"@vercel/kv\";\nimport type { AuditEntry, AuditLogAdapter, AuditOperation } from \"./audit\";\nimport type {\n IdempotencyCache,\n OAuthTokenRecord,\n OAuthTokenStore,\n SubscriptionStateAdapter,\n SubscriptionStateRecord,\n} from \"./state\";\n\nconst DEFAULT_SUBSCRIPTION_PREFIX = \"mp:sub:\";\nconst DEFAULT_OAUTH_PREFIX = \"mp:oauth:\";\nconst DEFAULT_IDEMPOTENCY_PREFIX = \"mp:idem:\";\nconst DEFAULT_AUDIT_PREFIX = \"mp:audit:\";\n\ninterface VercelKVAdapterOptions {\n /**\n * Custom KV client. If omitted, uses the default `kv` export from\n * `@vercel/kv` (which reads `KV_REST_API_URL` + `KV_REST_API_TOKEN` from\n * env — auto-injected when you connect a KV store to your Vercel project).\n */\n kv?: VercelKV;\n /** Override the key prefix. */\n prefix?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SubscriptionStateAdapter\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVSubscriptionStateAdapter\n implements SubscriptionStateAdapter\n{\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_SUBSCRIPTION_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(id: string): string {\n return `${this.prefix}${id}`;\n }\n\n async set(\n id: string,\n state: Partial<SubscriptionStateRecord>,\n ): Promise<void> {\n const existing = (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? {};\n await this.kv.set(this.key(id), { ...existing, ...state });\n await this.kv.sadd(this.indexKey, id);\n }\n\n async get(id: string): Promise<SubscriptionStateRecord | null> {\n return (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? null;\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n\n /** Forget a subscription record. NOT part of the adapter interface. */\n async delete(id: string): Promise<void> {\n await this.kv.del(this.key(id));\n await this.kv.srem(this.indexKey, id);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuthTokenStore (per-seller marketplace token persistence)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVOAuthTokenStore implements OAuthTokenStore {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_OAUTH_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(userId: string): string {\n return `${this.prefix}${userId}`;\n }\n\n async set(userId: string, token: OAuthTokenRecord): Promise<void> {\n await this.kv.set(this.key(userId), token);\n await this.kv.sadd(this.indexKey, userId);\n }\n\n async get(userId: string): Promise<OAuthTokenRecord | null> {\n return (await this.kv.get<OAuthTokenRecord>(this.key(userId))) ?? null;\n }\n\n async delete(userId: string): Promise<void> {\n await this.kv.del(this.key(userId));\n await this.kv.srem(this.indexKey, userId);\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Distributed Token Bucket Rate Limiter (KV-backed)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst DEFAULT_RATELIMIT_PREFIX = \"mp:rl:\";\n\n/**\n * Distributed token bucket rate limiter backed by Vercel KV.\n *\n * # Why distributed\n *\n * The default in-memory `TokenBucketRateLimiter` is per-process. In\n * serverless (Vercel Functions, Lambda, Cloudflare Workers), each cold\n * start gets its own bucket — meaning N concurrent instances effectively\n * have N×capacity. For multi-region deployments or marketplace setups\n * with shared MP rate budget, that's a footgun.\n *\n * This adapter uses a single Vercel KV (Upstash Redis) bucket per `key`,\n * shared across all instances. Two instances acquiring at the same time\n * decrement the same counter atomically — the rate limit holds globally.\n *\n * # Algorithm\n *\n * Standard token bucket with lazy refill: every `acquire()` call:\n * 1. Reads `{ tokens, lastRefill }` from KV\n * 2. Computes refill since `lastRefill`\n * 3. If tokens >= 1: decrements and writes back\n * 4. Otherwise: computes wait time, sleeps, retries\n *\n * The read-modify-write isn't atomic per-call, so under heavy contention\n * a small over-spend window is possible (worst case: ~N concurrent\n * acquires can succeed when only 1 token was available). Acceptable for\n * MP rate limiting — the \"actual\" budget is much higher than what we\n * provision.\n *\n * # Usage\n *\n * ```ts\n * import { MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import { VercelKVRateLimiter } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * // ONE rate limit shared across all serverless instances of this app:\n * const limiter = new VercelKVRateLimiter({\n * key: \"mp-account-prod\",\n * capacity: 50,\n * refillPerSecond: 25,\n * });\n *\n * const client = new MercadoPagoClient({\n * accessToken: process.env.MP_ACCESS_TOKEN!,\n * rateLimiter: limiter, // (See client.ts — wired the same as in-memory)\n * });\n * ```\n *\n * # Marketplace setups (per-seller rate limit)\n *\n * Use the seller's MP user_id as part of the `key`:\n *\n * ```ts\n * function makeLimiter(sellerUserId: string) {\n * return new VercelKVRateLimiter({\n * key: `mp-seller-${sellerUserId}`,\n * capacity: 10,\n * refillPerSecond: 5,\n * });\n * }\n * ```\n *\n * Each seller now has their own globally-distributed bucket.\n */\nexport interface VercelKVRateLimiterOptions extends VercelKVAdapterOptions {\n /**\n * Unique key for this bucket. Use distinct keys per logical \"rate-limit\n * scope\" (per-environment, per-seller, per-region, etc.). Required.\n */\n key: string;\n /** Bucket capacity (max burst). Default 50. */\n capacity?: number;\n /** Refill rate in tokens per second. Default 25. */\n refillPerSecond?: number;\n /**\n * Hard cap on how long `acquire()` will wait. If the bucket can't\n * refill in this time, `acquire()` throws. Default 30s.\n */\n acquireTimeoutMs?: number;\n /**\n * If true, `learnFromHeaders` syncs the bucket with MP's stated\n * `x-rate-limit-remaining`. Default true.\n */\n adaptive?: boolean;\n}\n\ninterface BucketState {\n tokens: number;\n lastRefillMs: number;\n}\n\nexport class VercelKVRateLimiter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly key: string;\n private readonly capacity: number;\n private readonly refillPerSecond: number;\n private readonly acquireTimeoutMs: number;\n private readonly adaptive: boolean;\n\n constructor(options: VercelKVRateLimiterOptions) {\n if (!options.key) {\n throw new Error(\n \"VercelKVRateLimiter requires a `key` (use distinct keys per rate-limit scope, e.g., per-environment or per-seller).\",\n );\n }\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_RATELIMIT_PREFIX;\n this.key = options.key;\n this.capacity = options.capacity ?? 50;\n this.refillPerSecond = options.refillPerSecond ?? 25;\n this.acquireTimeoutMs = options.acquireTimeoutMs ?? 30_000;\n this.adaptive = options.adaptive ?? true;\n }\n\n private fullKey(): string {\n return `${this.prefix}${this.key}`;\n }\n\n private async readState(): Promise<BucketState> {\n const stored = await this.kv.get<BucketState>(this.fullKey());\n if (stored && typeof stored.tokens === \"number\" && typeof stored.lastRefillMs === \"number\") {\n return stored;\n }\n return { tokens: this.capacity, lastRefillMs: Date.now() };\n }\n\n private refill(state: BucketState, nowMs: number): BucketState {\n const elapsedMs = Math.max(0, nowMs - state.lastRefillMs);\n const refilled = Math.min(\n this.capacity,\n state.tokens + (elapsedMs / 1000) * this.refillPerSecond,\n );\n return { tokens: refilled, lastRefillMs: nowMs };\n }\n\n private async writeState(state: BucketState): Promise<void> {\n // TTL = 1h. Long-idle buckets get garbage-collected, capacity rebuilds\n // from initial state on next acquire (which is fine — at the right rate).\n await this.kv.set(this.fullKey(), state, { ex: 3600 });\n }\n\n /**\n * Acquire a token. Resolves immediately if the distributed bucket has\n * one available; otherwise waits until refilled. Throws if the wait\n * exceeds `acquireTimeoutMs`.\n */\n async acquire(): Promise<void> {\n const start = Date.now();\n while (true) {\n const now = Date.now();\n const state = this.refill(await this.readState(), now);\n\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return;\n }\n\n // Compute wait time until next token. Cap at remaining timeout budget.\n const tokensNeeded = 1 - state.tokens;\n const waitMs = Math.ceil((tokensNeeded / this.refillPerSecond) * 1000);\n const elapsed = now - start;\n if (elapsed + waitMs > this.acquireTimeoutMs) {\n throw new Error(\n `VercelKVRateLimiter acquire timed out after ${elapsed + waitMs}ms (key=${this.key}).`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n }\n\n /** Best-effort acquire — returns true if a token was available, false otherwise. */\n async tryAcquire(): Promise<boolean> {\n const state = this.refill(await this.readState(), Date.now());\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return true;\n }\n return false;\n }\n\n /**\n * Adaptive learning — call after each MP API response. If MP's stated\n * `x-rate-limit-remaining` is lower than our local count, trust MP and\n * drop the bucket to match (prevents over-spending).\n */\n async learnFromHeaders(headers: {\n remaining: number | null;\n resetSeconds: number | null;\n }): Promise<void> {\n if (!this.adaptive) return;\n if (headers.remaining === null) return;\n const state = this.refill(await this.readState(), Date.now());\n if (headers.remaining < state.tokens) {\n state.tokens = Math.max(0, headers.remaining);\n await this.writeState(state);\n }\n }\n\n /** Inspect bucket state. */\n async getStats(): Promise<{ tokens: number; capacity: number; refillPerSecond: number }> {\n const state = this.refill(await this.readState(), Date.now());\n return {\n tokens: state.tokens,\n capacity: this.capacity,\n refillPerSecond: this.refillPerSecond,\n };\n }\n\n /** Reset the bucket to full. Use sparingly (e.g., after a known-clean window). */\n async reset(): Promise<void> {\n await this.writeState({ tokens: this.capacity, lastRefillMs: Date.now() });\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// IdempotencyCache (KV-backed dedup of agent retries)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVIdempotencyCache implements IdempotencyCache {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_IDEMPOTENCY_PREFIX;\n }\n\n private key(k: string): string {\n return `${this.prefix}${k}`;\n }\n\n async get<T>(key: string): Promise<T | null> {\n return (await this.kv.get<T>(this.key(key))) ?? null;\n }\n\n async set<T>(key: string, value: T, ttlSeconds = 86_400): Promise<void> {\n // Vercel KV's `set` supports a TTL in seconds via the `ex` option.\n await this.kv.set(this.key(key), value, { ex: ttlSeconds });\n }\n\n async delete(key: string): Promise<void> {\n await this.kv.del(this.key(key));\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AuditLogAdapter — production audit trail with daily-bucket indexing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vercel KV–backed audit log adapter. Stores each entry under\n * `mp:audit:entry:{id}` AND adds the id to a daily index sorted set\n * `mp:audit:day:{YYYY-MM-DD}` (score = timestamp ms). This gives O(log N)\n * time-range queries (\"all entries from May 1 to May 5\") without scanning\n * the entire log.\n *\n * # Storage layout\n *\n * - `mp:audit:entry:{id}` → the full entry JSON\n * - `mp:audit:day:{YYYY-MM-DD}` → ZSET of entry ids by timestamp (ms)\n * - `mp:audit:actor:{actor}` → ZSET of entry ids by timestamp (for \"all\n * entries by actor X\")\n * - `mp:audit:tenant:{tenantId}` → same, by tenant\n *\n * # Cost considerations\n *\n * Each `append()` does 1-3 KV writes (entry + 1-2 indexes). For high-traffic\n * deployments (>10/s sustained), batch via your own queue (e.g., Vercel\n * Queues with daily flush) and provide a custom adapter that batches.\n */\nexport class VercelKVAuditLog implements AuditLogAdapter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_AUDIT_PREFIX;\n }\n\n async append(entry: AuditEntry): Promise<void> {\n const ts = new Date(entry.timestamp).getTime();\n const day = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n await Promise.all([\n this.kv.set(`${this.prefix}entry:${entry.id}`, entry),\n this.kv.zadd(`${this.prefix}day:${day}`, { score: ts, member: entry.id }),\n this.kv.zadd(`${this.prefix}actor:${entry.actor}`, { score: ts, member: entry.id }),\n ...(entry.tenantId\n ? [\n this.kv.zadd(`${this.prefix}tenant:${entry.tenantId}`, {\n score: ts,\n member: entry.id,\n }),\n ]\n : []),\n ]);\n }\n\n async query(filter: {\n actor?: string;\n operation?: AuditOperation;\n tenantId?: string;\n from?: string;\n to?: string;\n limit?: number;\n }): Promise<AuditEntry[]> {\n const limit = filter.limit ?? 100;\n let ids: string[];\n\n // Pick the most selective index available\n if (filter.actor) {\n ids = await this.zrangeByScore(\n `${this.prefix}actor:${filter.actor}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.tenantId) {\n ids = await this.zrangeByScore(\n `${this.prefix}tenant:${filter.tenantId}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.from || filter.to) {\n // Walk daily buckets for the date range\n const fromDate = filter.from?.slice(0, 10) ?? \"0000-00-00\";\n const toDate = filter.to?.slice(0, 10) ?? \"9999-99-99\";\n ids = [];\n // Cap walk to ~1 year max to avoid runaway\n const fromTs = new Date(fromDate).getTime();\n const toTs = new Date(toDate).getTime();\n for (let d = fromTs; d <= toTs && ids.length < limit; d += 86_400_000) {\n const day = new Date(d).toISOString().slice(0, 10);\n const dayIds = await this.zrangeByScore(\n `${this.prefix}day:${day}`,\n filter.from,\n filter.to,\n limit - ids.length,\n );\n ids.push(...dayIds);\n }\n } else {\n // No filter — bail (full scan would be unbounded)\n return [];\n }\n\n // Load entries\n const entries: AuditEntry[] = [];\n for (const id of ids) {\n const entry = await this.kv.get<AuditEntry>(`${this.prefix}entry:${id}`);\n if (!entry) continue;\n if (filter.operation && entry.operation !== filter.operation) continue;\n entries.push(entry);\n }\n return entries;\n }\n\n private async zrangeByScore(\n key: string,\n from?: string,\n to?: string,\n limit?: number,\n ): Promise<string[]> {\n const min = from ? new Date(from).getTime() : 0;\n const max = to ? new Date(to).getTime() : Number.MAX_SAFE_INTEGER;\n const opts = {\n byScore: true as const,\n offset: 0,\n ...(limit !== undefined ? { count: limit } : { count: 100 }),\n };\n const ids = await this.kv.zrange(key, min, max, opts);\n return ids.map(String);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/vercel-kv.ts"],"names":["defaultKv"],"mappings":"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","file":"vercel-kv.cjs","sourcesContent":["/**\n * Vercel KV adapters — drop-in `SubscriptionStateAdapter`,\n * `OAuthTokenStore`, and `IdempotencyCache` implementations backed by\n * [Vercel KV](https://vercel.com/docs/storage/vercel-kv) (Upstash Redis).\n *\n * # Why a separate subpath?\n *\n * `@vercel/kv` is a peer dependency — only consumers who actually use Vercel\n * KV install it. Importing from `@ar-agents/mercadopago/vercel-kv` is\n * lazy: the main `@ar-agents/mercadopago` bundle stays tiny for callers who\n * use the in-memory adapters or a different store.\n *\n * # Setup\n *\n * 1. Create a KV store at https://vercel.com/dashboard/stores\n * 2. Connect it to your project — Vercel auto-injects `KV_*` env vars\n * 3. `pnpm add @vercel/kv`\n * 4. Wire the adapters:\n *\n * ```ts\n * import { mercadoPagoTools, MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import {\n * VercelKVSubscriptionStateAdapter,\n * VercelKVOAuthTokenStore,\n * } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * const tools = mercadoPagoTools(client, {\n * state: new VercelKVSubscriptionStateAdapter(),\n * backUrl: \"https://mysite.com/done\",\n * // ... oauth, webhookSecret, etc.\n * });\n *\n * // For marketplace flows, also wire the OAuth token store:\n * const oauthStore = new VercelKVOAuthTokenStore();\n * await oauthStore.set(token.user_id, {\n * user_id: token.user_id,\n * access_token: token.access_token,\n * refresh_token: token.refresh_token!,\n * expires_at: Date.now() + (token.expires_in ?? 21600) * 1000,\n * });\n * ```\n *\n * # Edge Runtime\n *\n * `@vercel/kv` works in Vercel Edge Runtime, Node.js, and any environment\n * with `fetch` (it's a thin REST client over Upstash). All adapters here\n * are async and Edge-safe.\n *\n * # Key namespacing\n *\n * Each adapter uses its own prefix so multiple adapters can share the same\n * KV store without collisions:\n * - Subscriptions: `mp:sub:{id}`\n * - OAuth tokens: `mp:oauth:{userId}`\n * - Idempotency: `mp:idem:{key}`\n *\n * Pass a custom prefix via the constructor if you need to share the store\n * with other apps.\n */\n\nimport { kv as defaultKv } from \"@vercel/kv\";\nimport type { VercelKV } from \"@vercel/kv\";\nimport type { AuditEntry, AuditLogAdapter, AuditOperation } from \"./audit\";\nimport type {\n IdempotencyCache,\n OAuthTokenRecord,\n OAuthTokenStore,\n SubscriptionStateAdapter,\n SubscriptionStateRecord,\n} from \"./state\";\n\nconst DEFAULT_SUBSCRIPTION_PREFIX = \"mp:sub:\";\nconst DEFAULT_OAUTH_PREFIX = \"mp:oauth:\";\nconst DEFAULT_IDEMPOTENCY_PREFIX = \"mp:idem:\";\nconst DEFAULT_AUDIT_PREFIX = \"mp:audit:\";\n\ninterface VercelKVAdapterOptions {\n /**\n * Custom KV client. If omitted, uses the default `kv` export from\n * `@vercel/kv` (which reads `KV_REST_API_URL` + `KV_REST_API_TOKEN` from\n * env — auto-injected when you connect a KV store to your Vercel project).\n */\n kv?: VercelKV;\n /** Override the key prefix. */\n prefix?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SubscriptionStateAdapter\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVSubscriptionStateAdapter\n implements SubscriptionStateAdapter\n{\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_SUBSCRIPTION_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(id: string): string {\n return `${this.prefix}${id}`;\n }\n\n async set(\n id: string,\n state: Partial<SubscriptionStateRecord>,\n ): Promise<void> {\n const existing = (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? {};\n await this.kv.set(this.key(id), { ...existing, ...state });\n await this.kv.sadd(this.indexKey, id);\n }\n\n async get(id: string): Promise<SubscriptionStateRecord | null> {\n return (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? null;\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n\n /** Forget a subscription record. NOT part of the adapter interface. */\n async delete(id: string): Promise<void> {\n await this.kv.del(this.key(id));\n await this.kv.srem(this.indexKey, id);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuthTokenStore (per-seller marketplace token persistence)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVOAuthTokenStore implements OAuthTokenStore {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_OAUTH_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(userId: string): string {\n return `${this.prefix}${userId}`;\n }\n\n async set(userId: string, token: OAuthTokenRecord): Promise<void> {\n await this.kv.set(this.key(userId), token);\n await this.kv.sadd(this.indexKey, userId);\n }\n\n async get(userId: string): Promise<OAuthTokenRecord | null> {\n return (await this.kv.get<OAuthTokenRecord>(this.key(userId))) ?? null;\n }\n\n async delete(userId: string): Promise<void> {\n await this.kv.del(this.key(userId));\n await this.kv.srem(this.indexKey, userId);\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Distributed Token Bucket Rate Limiter (KV-backed)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst DEFAULT_RATELIMIT_PREFIX = \"mp:rl:\";\n\n/**\n * Distributed token bucket rate limiter backed by Vercel KV.\n *\n * # Why distributed\n *\n * The default in-memory `TokenBucketRateLimiter` is per-process. In\n * serverless (Vercel Functions, Lambda, Cloudflare Workers), each cold\n * start gets its own bucket — meaning N concurrent instances effectively\n * have N×capacity. For multi-region deployments or marketplace setups\n * with shared MP rate budget, that's a footgun.\n *\n * This adapter uses a single Vercel KV (Upstash Redis) bucket per `key`,\n * shared across all instances. Two instances acquiring at the same time\n * decrement the same counter atomically — the rate limit holds globally.\n *\n * # Algorithm\n *\n * Standard token bucket with lazy refill: every `acquire()` call:\n * 1. Reads `{ tokens, lastRefill }` from KV\n * 2. Computes refill since `lastRefill`\n * 3. If tokens >= 1: decrements and writes back\n * 4. Otherwise: computes wait time, sleeps, retries\n *\n * The read-modify-write isn't atomic per-call, so under heavy contention\n * a small over-spend window is possible (worst case: ~N concurrent\n * acquires can succeed when only 1 token was available). Acceptable for\n * MP rate limiting — the \"actual\" budget is much higher than what we\n * provision.\n *\n * # Usage — wire via `withRateLimit` middleware\n *\n * `MercadoPagoClient` does not accept a rate limiter directly. Apply the\n * limiter at the tool layer using `withRateLimit` from the middleware\n * module, which works for both the in-memory `TokenBucketRateLimiter` and\n * this distributed variant.\n *\n * ```ts\n * import {\n * MercadoPagoClient,\n * mercadoPagoTools,\n * InMemoryStateAdapter,\n * applyToAllTools,\n * withRateLimit,\n * } from \"@ar-agents/mercadopago\";\n * import { VercelKVRateLimiter } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * // ONE distributed bucket shared across all serverless instances of this app:\n * const limiter = new VercelKVRateLimiter({\n * key: \"mp-account-prod\",\n * capacity: 50,\n * refillPerSecond: 25,\n * });\n *\n * const client = new MercadoPagoClient({ accessToken: process.env.MP_ACCESS_TOKEN! });\n * const tools = applyToAllTools(\n * mercadoPagoTools(client, { state: new InMemoryStateAdapter(), backUrl: \"...\" }),\n * withRateLimit(limiter),\n * );\n * ```\n *\n * # Concurrency caveats\n *\n * Read-modify-write is NOT strictly atomic per `acquire()`. Under heavy\n * contention a small over-spend window is possible, acceptable for **API\n * call** rate limiting where the actual MP budget exceeds what we\n * provision. **Do NOT repurpose this limiter as a money/spend cap** —\n * the over-spend window means you could exceed a money budget by the\n * concurrent-instance count × per-call cost. For money budgets, use\n * Upstash's `EVAL`-based atomic Lua script (or a stricter primitive).\n *\n * The `acquire()` retry loop applies randomized jitter (±30%) to spread\n * concurrent acquirers across multiple refill windows, mitigating the\n * thundering-herd that would otherwise hit Upstash with N reads + N\n * writes the instant a bucket refills.\n *\n * # Marketplace setups (per-seller rate limit)\n *\n * Use the seller's MP user_id as part of the `key`:\n *\n * ```ts\n * function makeLimiter(sellerUserId: string) {\n * return new VercelKVRateLimiter({\n * key: `mp-seller-${sellerUserId}`,\n * capacity: 10,\n * refillPerSecond: 5,\n * });\n * }\n * ```\n *\n * Each seller now has their own globally-distributed bucket.\n */\nexport interface VercelKVRateLimiterOptions extends VercelKVAdapterOptions {\n /**\n * Unique key for this bucket. Use distinct keys per logical \"rate-limit\n * scope\" (per-environment, per-seller, per-region, etc.). Required.\n */\n key: string;\n /** Bucket capacity (max burst). Default 50. */\n capacity?: number;\n /** Refill rate in tokens per second. Default 25. */\n refillPerSecond?: number;\n /**\n * Hard cap on how long `acquire()` will wait. If the bucket can't\n * refill in this time, `acquire()` throws. Default 30s.\n */\n acquireTimeoutMs?: number;\n /**\n * If true, `learnFromHeaders` syncs the bucket with MP's stated\n * `x-rate-limit-remaining`. Default true.\n */\n adaptive?: boolean;\n}\n\ninterface BucketState {\n tokens: number;\n lastRefillMs: number;\n}\n\nexport class VercelKVRateLimiter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly key: string;\n private readonly capacity: number;\n private readonly refillPerSecond: number;\n private readonly acquireTimeoutMs: number;\n private readonly adaptive: boolean;\n\n constructor(options: VercelKVRateLimiterOptions) {\n if (!options.key) {\n throw new Error(\n \"VercelKVRateLimiter requires a `key` (use distinct keys per rate-limit scope, e.g., per-environment or per-seller).\",\n );\n }\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_RATELIMIT_PREFIX;\n this.key = options.key;\n this.capacity = options.capacity ?? 50;\n this.refillPerSecond = options.refillPerSecond ?? 25;\n this.acquireTimeoutMs = options.acquireTimeoutMs ?? 30_000;\n this.adaptive = options.adaptive ?? true;\n }\n\n private fullKey(): string {\n return `${this.prefix}${this.key}`;\n }\n\n private async readState(): Promise<BucketState> {\n const stored = await this.kv.get<BucketState>(this.fullKey());\n if (stored && typeof stored.tokens === \"number\" && typeof stored.lastRefillMs === \"number\") {\n return stored;\n }\n return { tokens: this.capacity, lastRefillMs: Date.now() };\n }\n\n private refill(state: BucketState, nowMs: number): BucketState {\n const elapsedMs = Math.max(0, nowMs - state.lastRefillMs);\n const refilled = Math.min(\n this.capacity,\n state.tokens + (elapsedMs / 1000) * this.refillPerSecond,\n );\n return { tokens: refilled, lastRefillMs: nowMs };\n }\n\n private async writeState(state: BucketState): Promise<void> {\n // TTL = 1h. Long-idle buckets get garbage-collected, capacity rebuilds\n // from initial state on next acquire (which is fine — at the right rate).\n await this.kv.set(this.fullKey(), state, { ex: 3600 });\n }\n\n /**\n * Acquire a token. Resolves immediately if the distributed bucket has\n * one available; otherwise waits until refilled. Throws if the wait\n * exceeds `acquireTimeoutMs` or if the retry cap is reached.\n *\n * Caps retries at 8 iterations so a misconfigured bucket (capacity too\n * low for traffic) fails fast for the agent layer to surface, instead\n * of silently burning serverless compute time.\n */\n async acquire(): Promise<void> {\n const start = Date.now();\n const MAX_RETRIES = 8;\n let attempt = 0;\n while (attempt < MAX_RETRIES) {\n const now = Date.now();\n const state = this.refill(await this.readState(), now);\n\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return;\n }\n\n // Compute wait time until next token. Cap at remaining timeout budget.\n const tokensNeeded = 1 - state.tokens;\n const baseWaitMs = Math.ceil((tokensNeeded / this.refillPerSecond) * 1000);\n // Randomized jitter (±30%) prevents thundering herd: without it, all\n // concurrent acquirers compute identical waitMs, sleep identical\n // duration, and wake at the same wall-clock instant — then all hit\n // KV simultaneously. Jitter spreads them across the refill window.\n const jitterFactor = 0.7 + Math.random() * 0.6; // 0.7–1.3\n const waitMs = Math.ceil(baseWaitMs * jitterFactor);\n const elapsed = now - start;\n if (elapsed + waitMs > this.acquireTimeoutMs) {\n throw new Error(\n `VercelKVRateLimiter acquire timed out after ${elapsed + waitMs}ms (key=${this.key}).`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n attempt += 1;\n }\n throw new Error(\n `VercelKVRateLimiter exhausted ${MAX_RETRIES} retries (key=${this.key}). ` +\n `Bucket likely undersized for traffic — increase capacity or refillPerSecond.`,\n );\n }\n\n /** Best-effort acquire — returns true if a token was available, false otherwise. */\n async tryAcquire(): Promise<boolean> {\n const state = this.refill(await this.readState(), Date.now());\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return true;\n }\n return false;\n }\n\n /**\n * Adaptive learning — call after each MP API response. If MP's stated\n * `x-rate-limit-remaining` is lower than our local count, trust MP and\n * drop the bucket to match (prevents over-spending).\n */\n async learnFromHeaders(headers: {\n remaining: number | null;\n resetSeconds: number | null;\n }): Promise<void> {\n if (!this.adaptive) return;\n if (headers.remaining === null) return;\n const state = this.refill(await this.readState(), Date.now());\n if (headers.remaining < state.tokens) {\n state.tokens = Math.max(0, headers.remaining);\n await this.writeState(state);\n }\n }\n\n /** Inspect bucket state. */\n async getStats(): Promise<{ tokens: number; capacity: number; refillPerSecond: number }> {\n const state = this.refill(await this.readState(), Date.now());\n return {\n tokens: state.tokens,\n capacity: this.capacity,\n refillPerSecond: this.refillPerSecond,\n };\n }\n\n /** Reset the bucket to full. Use sparingly (e.g., after a known-clean window). */\n async reset(): Promise<void> {\n await this.writeState({ tokens: this.capacity, lastRefillMs: Date.now() });\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// IdempotencyCache (KV-backed dedup of agent retries)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVIdempotencyCache implements IdempotencyCache {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_IDEMPOTENCY_PREFIX;\n }\n\n private key(k: string): string {\n return `${this.prefix}${k}`;\n }\n\n async get<T>(key: string): Promise<T | null> {\n return (await this.kv.get<T>(this.key(key))) ?? null;\n }\n\n async set<T>(key: string, value: T, ttlSeconds = 86_400): Promise<void> {\n // Vercel KV's `set` supports a TTL in seconds via the `ex` option.\n await this.kv.set(this.key(key), value, { ex: ttlSeconds });\n }\n\n async delete(key: string): Promise<void> {\n await this.kv.del(this.key(key));\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AuditLogAdapter — production audit trail with daily-bucket indexing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vercel KV–backed audit log adapter. Stores each entry under\n * `mp:audit:entry:{id}` AND adds the id to a daily index sorted set\n * `mp:audit:day:{YYYY-MM-DD}` (score = timestamp ms). This gives O(log N)\n * time-range queries (\"all entries from May 1 to May 5\") without scanning\n * the entire log.\n *\n * # Storage layout\n *\n * - `mp:audit:entry:{id}` → the full entry JSON\n * - `mp:audit:day:{YYYY-MM-DD}` → ZSET of entry ids by timestamp (ms)\n * - `mp:audit:actor:{actor}` → ZSET of entry ids by timestamp (for \"all\n * entries by actor X\")\n * - `mp:audit:tenant:{tenantId}` → same, by tenant\n *\n * # Cost considerations\n *\n * Each `append()` does 1-3 KV writes (entry + 1-2 indexes). For high-traffic\n * deployments (>10/s sustained), batch via your own queue (e.g., Vercel\n * Queues with daily flush) and provide a custom adapter that batches.\n */\nexport class VercelKVAuditLog implements AuditLogAdapter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_AUDIT_PREFIX;\n }\n\n async append(entry: AuditEntry): Promise<void> {\n const ts = new Date(entry.timestamp).getTime();\n const day = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n await Promise.all([\n this.kv.set(`${this.prefix}entry:${entry.id}`, entry),\n this.kv.zadd(`${this.prefix}day:${day}`, { score: ts, member: entry.id }),\n this.kv.zadd(`${this.prefix}actor:${entry.actor}`, { score: ts, member: entry.id }),\n ...(entry.tenantId\n ? [\n this.kv.zadd(`${this.prefix}tenant:${entry.tenantId}`, {\n score: ts,\n member: entry.id,\n }),\n ]\n : []),\n ]);\n }\n\n async query(filter: {\n actor?: string;\n operation?: AuditOperation;\n tenantId?: string;\n from?: string;\n to?: string;\n limit?: number;\n }): Promise<AuditEntry[]> {\n const limit = filter.limit ?? 100;\n let ids: string[];\n\n // Pick the most selective index available\n if (filter.actor) {\n ids = await this.zrangeByScore(\n `${this.prefix}actor:${filter.actor}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.tenantId) {\n ids = await this.zrangeByScore(\n `${this.prefix}tenant:${filter.tenantId}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.from || filter.to) {\n // Walk daily buckets for the date range\n const fromDate = filter.from?.slice(0, 10) ?? \"0000-00-00\";\n const toDate = filter.to?.slice(0, 10) ?? \"9999-99-99\";\n ids = [];\n // Cap walk to ~1 year max to avoid runaway\n const fromTs = new Date(fromDate).getTime();\n const toTs = new Date(toDate).getTime();\n for (let d = fromTs; d <= toTs && ids.length < limit; d += 86_400_000) {\n const day = new Date(d).toISOString().slice(0, 10);\n const dayIds = await this.zrangeByScore(\n `${this.prefix}day:${day}`,\n filter.from,\n filter.to,\n limit - ids.length,\n );\n ids.push(...dayIds);\n }\n } else {\n // No filter — bail (full scan would be unbounded)\n return [];\n }\n\n // Load entries\n const entries: AuditEntry[] = [];\n for (const id of ids) {\n const entry = await this.kv.get<AuditEntry>(`${this.prefix}entry:${id}`);\n if (!entry) continue;\n if (filter.operation && entry.operation !== filter.operation) continue;\n entries.push(entry);\n }\n return entries;\n }\n\n private async zrangeByScore(\n key: string,\n from?: string,\n to?: string,\n limit?: number,\n ): Promise<string[]> {\n const min = from ? new Date(from).getTime() : 0;\n const max = to ? new Date(to).getTime() : Number.MAX_SAFE_INTEGER;\n const opts = {\n byScore: true as const,\n offset: 0,\n ...(limit !== undefined ? { count: limit } : { count: 100 }),\n };\n const ids = await this.kv.zrange(key, min, max, opts);\n return ids.map(String);\n }\n}\n"]}
|
package/dist/vercel-kv.d.cts
CHANGED
|
@@ -123,25 +123,52 @@ declare class VercelKVOAuthTokenStore implements OAuthTokenStore {
|
|
|
123
123
|
* MP rate limiting — the "actual" budget is much higher than what we
|
|
124
124
|
* provision.
|
|
125
125
|
*
|
|
126
|
-
* # Usage
|
|
126
|
+
* # Usage — wire via `withRateLimit` middleware
|
|
127
|
+
*
|
|
128
|
+
* `MercadoPagoClient` does not accept a rate limiter directly. Apply the
|
|
129
|
+
* limiter at the tool layer using `withRateLimit` from the middleware
|
|
130
|
+
* module, which works for both the in-memory `TokenBucketRateLimiter` and
|
|
131
|
+
* this distributed variant.
|
|
127
132
|
*
|
|
128
133
|
* ```ts
|
|
129
|
-
* import {
|
|
134
|
+
* import {
|
|
135
|
+
* MercadoPagoClient,
|
|
136
|
+
* mercadoPagoTools,
|
|
137
|
+
* InMemoryStateAdapter,
|
|
138
|
+
* applyToAllTools,
|
|
139
|
+
* withRateLimit,
|
|
140
|
+
* } from "@ar-agents/mercadopago";
|
|
130
141
|
* import { VercelKVRateLimiter } from "@ar-agents/mercadopago/vercel-kv";
|
|
131
142
|
*
|
|
132
|
-
* // ONE
|
|
143
|
+
* // ONE distributed bucket shared across all serverless instances of this app:
|
|
133
144
|
* const limiter = new VercelKVRateLimiter({
|
|
134
145
|
* key: "mp-account-prod",
|
|
135
146
|
* capacity: 50,
|
|
136
147
|
* refillPerSecond: 25,
|
|
137
148
|
* });
|
|
138
149
|
*
|
|
139
|
-
* const client = new MercadoPagoClient({
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
150
|
+
* const client = new MercadoPagoClient({ accessToken: process.env.MP_ACCESS_TOKEN! });
|
|
151
|
+
* const tools = applyToAllTools(
|
|
152
|
+
* mercadoPagoTools(client, { state: new InMemoryStateAdapter(), backUrl: "..." }),
|
|
153
|
+
* withRateLimit(limiter),
|
|
154
|
+
* );
|
|
143
155
|
* ```
|
|
144
156
|
*
|
|
157
|
+
* # Concurrency caveats
|
|
158
|
+
*
|
|
159
|
+
* Read-modify-write is NOT strictly atomic per `acquire()`. Under heavy
|
|
160
|
+
* contention a small over-spend window is possible, acceptable for **API
|
|
161
|
+
* call** rate limiting where the actual MP budget exceeds what we
|
|
162
|
+
* provision. **Do NOT repurpose this limiter as a money/spend cap** —
|
|
163
|
+
* the over-spend window means you could exceed a money budget by the
|
|
164
|
+
* concurrent-instance count × per-call cost. For money budgets, use
|
|
165
|
+
* Upstash's `EVAL`-based atomic Lua script (or a stricter primitive).
|
|
166
|
+
*
|
|
167
|
+
* The `acquire()` retry loop applies randomized jitter (±30%) to spread
|
|
168
|
+
* concurrent acquirers across multiple refill windows, mitigating the
|
|
169
|
+
* thundering-herd that would otherwise hit Upstash with N reads + N
|
|
170
|
+
* writes the instant a bucket refills.
|
|
171
|
+
*
|
|
145
172
|
* # Marketplace setups (per-seller rate limit)
|
|
146
173
|
*
|
|
147
174
|
* Use the seller's MP user_id as part of the `key`:
|
|
@@ -195,7 +222,11 @@ declare class VercelKVRateLimiter {
|
|
|
195
222
|
/**
|
|
196
223
|
* Acquire a token. Resolves immediately if the distributed bucket has
|
|
197
224
|
* one available; otherwise waits until refilled. Throws if the wait
|
|
198
|
-
* exceeds `acquireTimeoutMs
|
|
225
|
+
* exceeds `acquireTimeoutMs` or if the retry cap is reached.
|
|
226
|
+
*
|
|
227
|
+
* Caps retries at 8 iterations so a misconfigured bucket (capacity too
|
|
228
|
+
* low for traffic) fails fast for the agent layer to surface, instead
|
|
229
|
+
* of silently burning serverless compute time.
|
|
199
230
|
*/
|
|
200
231
|
acquire(): Promise<void>;
|
|
201
232
|
/** Best-effort acquire — returns true if a token was available, false otherwise. */
|
package/dist/vercel-kv.d.ts
CHANGED
|
@@ -123,25 +123,52 @@ declare class VercelKVOAuthTokenStore implements OAuthTokenStore {
|
|
|
123
123
|
* MP rate limiting — the "actual" budget is much higher than what we
|
|
124
124
|
* provision.
|
|
125
125
|
*
|
|
126
|
-
* # Usage
|
|
126
|
+
* # Usage — wire via `withRateLimit` middleware
|
|
127
|
+
*
|
|
128
|
+
* `MercadoPagoClient` does not accept a rate limiter directly. Apply the
|
|
129
|
+
* limiter at the tool layer using `withRateLimit` from the middleware
|
|
130
|
+
* module, which works for both the in-memory `TokenBucketRateLimiter` and
|
|
131
|
+
* this distributed variant.
|
|
127
132
|
*
|
|
128
133
|
* ```ts
|
|
129
|
-
* import {
|
|
134
|
+
* import {
|
|
135
|
+
* MercadoPagoClient,
|
|
136
|
+
* mercadoPagoTools,
|
|
137
|
+
* InMemoryStateAdapter,
|
|
138
|
+
* applyToAllTools,
|
|
139
|
+
* withRateLimit,
|
|
140
|
+
* } from "@ar-agents/mercadopago";
|
|
130
141
|
* import { VercelKVRateLimiter } from "@ar-agents/mercadopago/vercel-kv";
|
|
131
142
|
*
|
|
132
|
-
* // ONE
|
|
143
|
+
* // ONE distributed bucket shared across all serverless instances of this app:
|
|
133
144
|
* const limiter = new VercelKVRateLimiter({
|
|
134
145
|
* key: "mp-account-prod",
|
|
135
146
|
* capacity: 50,
|
|
136
147
|
* refillPerSecond: 25,
|
|
137
148
|
* });
|
|
138
149
|
*
|
|
139
|
-
* const client = new MercadoPagoClient({
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
150
|
+
* const client = new MercadoPagoClient({ accessToken: process.env.MP_ACCESS_TOKEN! });
|
|
151
|
+
* const tools = applyToAllTools(
|
|
152
|
+
* mercadoPagoTools(client, { state: new InMemoryStateAdapter(), backUrl: "..." }),
|
|
153
|
+
* withRateLimit(limiter),
|
|
154
|
+
* );
|
|
143
155
|
* ```
|
|
144
156
|
*
|
|
157
|
+
* # Concurrency caveats
|
|
158
|
+
*
|
|
159
|
+
* Read-modify-write is NOT strictly atomic per `acquire()`. Under heavy
|
|
160
|
+
* contention a small over-spend window is possible, acceptable for **API
|
|
161
|
+
* call** rate limiting where the actual MP budget exceeds what we
|
|
162
|
+
* provision. **Do NOT repurpose this limiter as a money/spend cap** —
|
|
163
|
+
* the over-spend window means you could exceed a money budget by the
|
|
164
|
+
* concurrent-instance count × per-call cost. For money budgets, use
|
|
165
|
+
* Upstash's `EVAL`-based atomic Lua script (or a stricter primitive).
|
|
166
|
+
*
|
|
167
|
+
* The `acquire()` retry loop applies randomized jitter (±30%) to spread
|
|
168
|
+
* concurrent acquirers across multiple refill windows, mitigating the
|
|
169
|
+
* thundering-herd that would otherwise hit Upstash with N reads + N
|
|
170
|
+
* writes the instant a bucket refills.
|
|
171
|
+
*
|
|
145
172
|
* # Marketplace setups (per-seller rate limit)
|
|
146
173
|
*
|
|
147
174
|
* Use the seller's MP user_id as part of the `key`:
|
|
@@ -195,7 +222,11 @@ declare class VercelKVRateLimiter {
|
|
|
195
222
|
/**
|
|
196
223
|
* Acquire a token. Resolves immediately if the distributed bucket has
|
|
197
224
|
* one available; otherwise waits until refilled. Throws if the wait
|
|
198
|
-
* exceeds `acquireTimeoutMs
|
|
225
|
+
* exceeds `acquireTimeoutMs` or if the retry cap is reached.
|
|
226
|
+
*
|
|
227
|
+
* Caps retries at 8 iterations so a misconfigured bucket (capacity too
|
|
228
|
+
* low for traffic) fails fast for the agent layer to surface, instead
|
|
229
|
+
* of silently burning serverless compute time.
|
|
199
230
|
*/
|
|
200
231
|
acquire(): Promise<void>;
|
|
201
232
|
/** Best-effort acquire — returns true if a token was available, false otherwise. */
|
package/dist/vercel-kv.js
CHANGED
|
@@ -110,11 +110,17 @@ var VercelKVRateLimiter = class {
|
|
|
110
110
|
/**
|
|
111
111
|
* Acquire a token. Resolves immediately if the distributed bucket has
|
|
112
112
|
* one available; otherwise waits until refilled. Throws if the wait
|
|
113
|
-
* exceeds `acquireTimeoutMs
|
|
113
|
+
* exceeds `acquireTimeoutMs` or if the retry cap is reached.
|
|
114
|
+
*
|
|
115
|
+
* Caps retries at 8 iterations so a misconfigured bucket (capacity too
|
|
116
|
+
* low for traffic) fails fast for the agent layer to surface, instead
|
|
117
|
+
* of silently burning serverless compute time.
|
|
114
118
|
*/
|
|
115
119
|
async acquire() {
|
|
116
120
|
const start = Date.now();
|
|
117
|
-
|
|
121
|
+
const MAX_RETRIES = 8;
|
|
122
|
+
let attempt = 0;
|
|
123
|
+
while (attempt < MAX_RETRIES) {
|
|
118
124
|
const now = Date.now();
|
|
119
125
|
const state = this.refill(await this.readState(), now);
|
|
120
126
|
if (state.tokens >= 1) {
|
|
@@ -123,7 +129,9 @@ var VercelKVRateLimiter = class {
|
|
|
123
129
|
return;
|
|
124
130
|
}
|
|
125
131
|
const tokensNeeded = 1 - state.tokens;
|
|
126
|
-
const
|
|
132
|
+
const baseWaitMs = Math.ceil(tokensNeeded / this.refillPerSecond * 1e3);
|
|
133
|
+
const jitterFactor = 0.7 + Math.random() * 0.6;
|
|
134
|
+
const waitMs = Math.ceil(baseWaitMs * jitterFactor);
|
|
127
135
|
const elapsed = now - start;
|
|
128
136
|
if (elapsed + waitMs > this.acquireTimeoutMs) {
|
|
129
137
|
throw new Error(
|
|
@@ -131,7 +139,11 @@ var VercelKVRateLimiter = class {
|
|
|
131
139
|
);
|
|
132
140
|
}
|
|
133
141
|
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
142
|
+
attempt += 1;
|
|
134
143
|
}
|
|
144
|
+
throw new Error(
|
|
145
|
+
`VercelKVRateLimiter exhausted ${MAX_RETRIES} retries (key=${this.key}). Bucket likely undersized for traffic \u2014 increase capacity or refillPerSecond.`
|
|
146
|
+
);
|
|
135
147
|
}
|
|
136
148
|
/** Best-effort acquire — returns true if a token was available, false otherwise. */
|
|
137
149
|
async tryAcquire() {
|
package/dist/vercel-kv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vercel-kv.ts"],"names":["defaultKv"],"mappings":"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","file":"vercel-kv.js","sourcesContent":["/**\n * Vercel KV adapters — drop-in `SubscriptionStateAdapter`,\n * `OAuthTokenStore`, and `IdempotencyCache` implementations backed by\n * [Vercel KV](https://vercel.com/docs/storage/vercel-kv) (Upstash Redis).\n *\n * # Why a separate subpath?\n *\n * `@vercel/kv` is a peer dependency — only consumers who actually use Vercel\n * KV install it. Importing from `@ar-agents/mercadopago/vercel-kv` is\n * lazy: the main `@ar-agents/mercadopago` bundle stays tiny for callers who\n * use the in-memory adapters or a different store.\n *\n * # Setup\n *\n * 1. Create a KV store at https://vercel.com/dashboard/stores\n * 2. Connect it to your project — Vercel auto-injects `KV_*` env vars\n * 3. `pnpm add @vercel/kv`\n * 4. Wire the adapters:\n *\n * ```ts\n * import { mercadoPagoTools, MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import {\n * VercelKVSubscriptionStateAdapter,\n * VercelKVOAuthTokenStore,\n * } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * const tools = mercadoPagoTools(client, {\n * state: new VercelKVSubscriptionStateAdapter(),\n * backUrl: \"https://mysite.com/done\",\n * // ... oauth, webhookSecret, etc.\n * });\n *\n * // For marketplace flows, also wire the OAuth token store:\n * const oauthStore = new VercelKVOAuthTokenStore();\n * await oauthStore.set(token.user_id, {\n * user_id: token.user_id,\n * access_token: token.access_token,\n * refresh_token: token.refresh_token!,\n * expires_at: Date.now() + (token.expires_in ?? 21600) * 1000,\n * });\n * ```\n *\n * # Edge Runtime\n *\n * `@vercel/kv` works in Vercel Edge Runtime, Node.js, and any environment\n * with `fetch` (it's a thin REST client over Upstash). All adapters here\n * are async and Edge-safe.\n *\n * # Key namespacing\n *\n * Each adapter uses its own prefix so multiple adapters can share the same\n * KV store without collisions:\n * - Subscriptions: `mp:sub:{id}`\n * - OAuth tokens: `mp:oauth:{userId}`\n * - Idempotency: `mp:idem:{key}`\n *\n * Pass a custom prefix via the constructor if you need to share the store\n * with other apps.\n */\n\nimport { kv as defaultKv } from \"@vercel/kv\";\nimport type { VercelKV } from \"@vercel/kv\";\nimport type { AuditEntry, AuditLogAdapter, AuditOperation } from \"./audit\";\nimport type {\n IdempotencyCache,\n OAuthTokenRecord,\n OAuthTokenStore,\n SubscriptionStateAdapter,\n SubscriptionStateRecord,\n} from \"./state\";\n\nconst DEFAULT_SUBSCRIPTION_PREFIX = \"mp:sub:\";\nconst DEFAULT_OAUTH_PREFIX = \"mp:oauth:\";\nconst DEFAULT_IDEMPOTENCY_PREFIX = \"mp:idem:\";\nconst DEFAULT_AUDIT_PREFIX = \"mp:audit:\";\n\ninterface VercelKVAdapterOptions {\n /**\n * Custom KV client. If omitted, uses the default `kv` export from\n * `@vercel/kv` (which reads `KV_REST_API_URL` + `KV_REST_API_TOKEN` from\n * env — auto-injected when you connect a KV store to your Vercel project).\n */\n kv?: VercelKV;\n /** Override the key prefix. */\n prefix?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SubscriptionStateAdapter\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVSubscriptionStateAdapter\n implements SubscriptionStateAdapter\n{\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_SUBSCRIPTION_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(id: string): string {\n return `${this.prefix}${id}`;\n }\n\n async set(\n id: string,\n state: Partial<SubscriptionStateRecord>,\n ): Promise<void> {\n const existing = (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? {};\n await this.kv.set(this.key(id), { ...existing, ...state });\n await this.kv.sadd(this.indexKey, id);\n }\n\n async get(id: string): Promise<SubscriptionStateRecord | null> {\n return (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? null;\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n\n /** Forget a subscription record. NOT part of the adapter interface. */\n async delete(id: string): Promise<void> {\n await this.kv.del(this.key(id));\n await this.kv.srem(this.indexKey, id);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuthTokenStore (per-seller marketplace token persistence)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVOAuthTokenStore implements OAuthTokenStore {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_OAUTH_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(userId: string): string {\n return `${this.prefix}${userId}`;\n }\n\n async set(userId: string, token: OAuthTokenRecord): Promise<void> {\n await this.kv.set(this.key(userId), token);\n await this.kv.sadd(this.indexKey, userId);\n }\n\n async get(userId: string): Promise<OAuthTokenRecord | null> {\n return (await this.kv.get<OAuthTokenRecord>(this.key(userId))) ?? null;\n }\n\n async delete(userId: string): Promise<void> {\n await this.kv.del(this.key(userId));\n await this.kv.srem(this.indexKey, userId);\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Distributed Token Bucket Rate Limiter (KV-backed)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst DEFAULT_RATELIMIT_PREFIX = \"mp:rl:\";\n\n/**\n * Distributed token bucket rate limiter backed by Vercel KV.\n *\n * # Why distributed\n *\n * The default in-memory `TokenBucketRateLimiter` is per-process. In\n * serverless (Vercel Functions, Lambda, Cloudflare Workers), each cold\n * start gets its own bucket — meaning N concurrent instances effectively\n * have N×capacity. For multi-region deployments or marketplace setups\n * with shared MP rate budget, that's a footgun.\n *\n * This adapter uses a single Vercel KV (Upstash Redis) bucket per `key`,\n * shared across all instances. Two instances acquiring at the same time\n * decrement the same counter atomically — the rate limit holds globally.\n *\n * # Algorithm\n *\n * Standard token bucket with lazy refill: every `acquire()` call:\n * 1. Reads `{ tokens, lastRefill }` from KV\n * 2. Computes refill since `lastRefill`\n * 3. If tokens >= 1: decrements and writes back\n * 4. Otherwise: computes wait time, sleeps, retries\n *\n * The read-modify-write isn't atomic per-call, so under heavy contention\n * a small over-spend window is possible (worst case: ~N concurrent\n * acquires can succeed when only 1 token was available). Acceptable for\n * MP rate limiting — the \"actual\" budget is much higher than what we\n * provision.\n *\n * # Usage\n *\n * ```ts\n * import { MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import { VercelKVRateLimiter } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * // ONE rate limit shared across all serverless instances of this app:\n * const limiter = new VercelKVRateLimiter({\n * key: \"mp-account-prod\",\n * capacity: 50,\n * refillPerSecond: 25,\n * });\n *\n * const client = new MercadoPagoClient({\n * accessToken: process.env.MP_ACCESS_TOKEN!,\n * rateLimiter: limiter, // (See client.ts — wired the same as in-memory)\n * });\n * ```\n *\n * # Marketplace setups (per-seller rate limit)\n *\n * Use the seller's MP user_id as part of the `key`:\n *\n * ```ts\n * function makeLimiter(sellerUserId: string) {\n * return new VercelKVRateLimiter({\n * key: `mp-seller-${sellerUserId}`,\n * capacity: 10,\n * refillPerSecond: 5,\n * });\n * }\n * ```\n *\n * Each seller now has their own globally-distributed bucket.\n */\nexport interface VercelKVRateLimiterOptions extends VercelKVAdapterOptions {\n /**\n * Unique key for this bucket. Use distinct keys per logical \"rate-limit\n * scope\" (per-environment, per-seller, per-region, etc.). Required.\n */\n key: string;\n /** Bucket capacity (max burst). Default 50. */\n capacity?: number;\n /** Refill rate in tokens per second. Default 25. */\n refillPerSecond?: number;\n /**\n * Hard cap on how long `acquire()` will wait. If the bucket can't\n * refill in this time, `acquire()` throws. Default 30s.\n */\n acquireTimeoutMs?: number;\n /**\n * If true, `learnFromHeaders` syncs the bucket with MP's stated\n * `x-rate-limit-remaining`. Default true.\n */\n adaptive?: boolean;\n}\n\ninterface BucketState {\n tokens: number;\n lastRefillMs: number;\n}\n\nexport class VercelKVRateLimiter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly key: string;\n private readonly capacity: number;\n private readonly refillPerSecond: number;\n private readonly acquireTimeoutMs: number;\n private readonly adaptive: boolean;\n\n constructor(options: VercelKVRateLimiterOptions) {\n if (!options.key) {\n throw new Error(\n \"VercelKVRateLimiter requires a `key` (use distinct keys per rate-limit scope, e.g., per-environment or per-seller).\",\n );\n }\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_RATELIMIT_PREFIX;\n this.key = options.key;\n this.capacity = options.capacity ?? 50;\n this.refillPerSecond = options.refillPerSecond ?? 25;\n this.acquireTimeoutMs = options.acquireTimeoutMs ?? 30_000;\n this.adaptive = options.adaptive ?? true;\n }\n\n private fullKey(): string {\n return `${this.prefix}${this.key}`;\n }\n\n private async readState(): Promise<BucketState> {\n const stored = await this.kv.get<BucketState>(this.fullKey());\n if (stored && typeof stored.tokens === \"number\" && typeof stored.lastRefillMs === \"number\") {\n return stored;\n }\n return { tokens: this.capacity, lastRefillMs: Date.now() };\n }\n\n private refill(state: BucketState, nowMs: number): BucketState {\n const elapsedMs = Math.max(0, nowMs - state.lastRefillMs);\n const refilled = Math.min(\n this.capacity,\n state.tokens + (elapsedMs / 1000) * this.refillPerSecond,\n );\n return { tokens: refilled, lastRefillMs: nowMs };\n }\n\n private async writeState(state: BucketState): Promise<void> {\n // TTL = 1h. Long-idle buckets get garbage-collected, capacity rebuilds\n // from initial state on next acquire (which is fine — at the right rate).\n await this.kv.set(this.fullKey(), state, { ex: 3600 });\n }\n\n /**\n * Acquire a token. Resolves immediately if the distributed bucket has\n * one available; otherwise waits until refilled. Throws if the wait\n * exceeds `acquireTimeoutMs`.\n */\n async acquire(): Promise<void> {\n const start = Date.now();\n while (true) {\n const now = Date.now();\n const state = this.refill(await this.readState(), now);\n\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return;\n }\n\n // Compute wait time until next token. Cap at remaining timeout budget.\n const tokensNeeded = 1 - state.tokens;\n const waitMs = Math.ceil((tokensNeeded / this.refillPerSecond) * 1000);\n const elapsed = now - start;\n if (elapsed + waitMs > this.acquireTimeoutMs) {\n throw new Error(\n `VercelKVRateLimiter acquire timed out after ${elapsed + waitMs}ms (key=${this.key}).`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n }\n\n /** Best-effort acquire — returns true if a token was available, false otherwise. */\n async tryAcquire(): Promise<boolean> {\n const state = this.refill(await this.readState(), Date.now());\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return true;\n }\n return false;\n }\n\n /**\n * Adaptive learning — call after each MP API response. If MP's stated\n * `x-rate-limit-remaining` is lower than our local count, trust MP and\n * drop the bucket to match (prevents over-spending).\n */\n async learnFromHeaders(headers: {\n remaining: number | null;\n resetSeconds: number | null;\n }): Promise<void> {\n if (!this.adaptive) return;\n if (headers.remaining === null) return;\n const state = this.refill(await this.readState(), Date.now());\n if (headers.remaining < state.tokens) {\n state.tokens = Math.max(0, headers.remaining);\n await this.writeState(state);\n }\n }\n\n /** Inspect bucket state. */\n async getStats(): Promise<{ tokens: number; capacity: number; refillPerSecond: number }> {\n const state = this.refill(await this.readState(), Date.now());\n return {\n tokens: state.tokens,\n capacity: this.capacity,\n refillPerSecond: this.refillPerSecond,\n };\n }\n\n /** Reset the bucket to full. Use sparingly (e.g., after a known-clean window). */\n async reset(): Promise<void> {\n await this.writeState({ tokens: this.capacity, lastRefillMs: Date.now() });\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// IdempotencyCache (KV-backed dedup of agent retries)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVIdempotencyCache implements IdempotencyCache {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_IDEMPOTENCY_PREFIX;\n }\n\n private key(k: string): string {\n return `${this.prefix}${k}`;\n }\n\n async get<T>(key: string): Promise<T | null> {\n return (await this.kv.get<T>(this.key(key))) ?? null;\n }\n\n async set<T>(key: string, value: T, ttlSeconds = 86_400): Promise<void> {\n // Vercel KV's `set` supports a TTL in seconds via the `ex` option.\n await this.kv.set(this.key(key), value, { ex: ttlSeconds });\n }\n\n async delete(key: string): Promise<void> {\n await this.kv.del(this.key(key));\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AuditLogAdapter — production audit trail with daily-bucket indexing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vercel KV–backed audit log adapter. Stores each entry under\n * `mp:audit:entry:{id}` AND adds the id to a daily index sorted set\n * `mp:audit:day:{YYYY-MM-DD}` (score = timestamp ms). This gives O(log N)\n * time-range queries (\"all entries from May 1 to May 5\") without scanning\n * the entire log.\n *\n * # Storage layout\n *\n * - `mp:audit:entry:{id}` → the full entry JSON\n * - `mp:audit:day:{YYYY-MM-DD}` → ZSET of entry ids by timestamp (ms)\n * - `mp:audit:actor:{actor}` → ZSET of entry ids by timestamp (for \"all\n * entries by actor X\")\n * - `mp:audit:tenant:{tenantId}` → same, by tenant\n *\n * # Cost considerations\n *\n * Each `append()` does 1-3 KV writes (entry + 1-2 indexes). For high-traffic\n * deployments (>10/s sustained), batch via your own queue (e.g., Vercel\n * Queues with daily flush) and provide a custom adapter that batches.\n */\nexport class VercelKVAuditLog implements AuditLogAdapter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_AUDIT_PREFIX;\n }\n\n async append(entry: AuditEntry): Promise<void> {\n const ts = new Date(entry.timestamp).getTime();\n const day = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n await Promise.all([\n this.kv.set(`${this.prefix}entry:${entry.id}`, entry),\n this.kv.zadd(`${this.prefix}day:${day}`, { score: ts, member: entry.id }),\n this.kv.zadd(`${this.prefix}actor:${entry.actor}`, { score: ts, member: entry.id }),\n ...(entry.tenantId\n ? [\n this.kv.zadd(`${this.prefix}tenant:${entry.tenantId}`, {\n score: ts,\n member: entry.id,\n }),\n ]\n : []),\n ]);\n }\n\n async query(filter: {\n actor?: string;\n operation?: AuditOperation;\n tenantId?: string;\n from?: string;\n to?: string;\n limit?: number;\n }): Promise<AuditEntry[]> {\n const limit = filter.limit ?? 100;\n let ids: string[];\n\n // Pick the most selective index available\n if (filter.actor) {\n ids = await this.zrangeByScore(\n `${this.prefix}actor:${filter.actor}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.tenantId) {\n ids = await this.zrangeByScore(\n `${this.prefix}tenant:${filter.tenantId}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.from || filter.to) {\n // Walk daily buckets for the date range\n const fromDate = filter.from?.slice(0, 10) ?? \"0000-00-00\";\n const toDate = filter.to?.slice(0, 10) ?? \"9999-99-99\";\n ids = [];\n // Cap walk to ~1 year max to avoid runaway\n const fromTs = new Date(fromDate).getTime();\n const toTs = new Date(toDate).getTime();\n for (let d = fromTs; d <= toTs && ids.length < limit; d += 86_400_000) {\n const day = new Date(d).toISOString().slice(0, 10);\n const dayIds = await this.zrangeByScore(\n `${this.prefix}day:${day}`,\n filter.from,\n filter.to,\n limit - ids.length,\n );\n ids.push(...dayIds);\n }\n } else {\n // No filter — bail (full scan would be unbounded)\n return [];\n }\n\n // Load entries\n const entries: AuditEntry[] = [];\n for (const id of ids) {\n const entry = await this.kv.get<AuditEntry>(`${this.prefix}entry:${id}`);\n if (!entry) continue;\n if (filter.operation && entry.operation !== filter.operation) continue;\n entries.push(entry);\n }\n return entries;\n }\n\n private async zrangeByScore(\n key: string,\n from?: string,\n to?: string,\n limit?: number,\n ): Promise<string[]> {\n const min = from ? new Date(from).getTime() : 0;\n const max = to ? new Date(to).getTime() : Number.MAX_SAFE_INTEGER;\n const opts = {\n byScore: true as const,\n offset: 0,\n ...(limit !== undefined ? { count: limit } : { count: 100 }),\n };\n const ids = await this.kv.zrange(key, min, max, opts);\n return ids.map(String);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/vercel-kv.ts"],"names":["defaultKv"],"mappings":"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","file":"vercel-kv.js","sourcesContent":["/**\n * Vercel KV adapters — drop-in `SubscriptionStateAdapter`,\n * `OAuthTokenStore`, and `IdempotencyCache` implementations backed by\n * [Vercel KV](https://vercel.com/docs/storage/vercel-kv) (Upstash Redis).\n *\n * # Why a separate subpath?\n *\n * `@vercel/kv` is a peer dependency — only consumers who actually use Vercel\n * KV install it. Importing from `@ar-agents/mercadopago/vercel-kv` is\n * lazy: the main `@ar-agents/mercadopago` bundle stays tiny for callers who\n * use the in-memory adapters or a different store.\n *\n * # Setup\n *\n * 1. Create a KV store at https://vercel.com/dashboard/stores\n * 2. Connect it to your project — Vercel auto-injects `KV_*` env vars\n * 3. `pnpm add @vercel/kv`\n * 4. Wire the adapters:\n *\n * ```ts\n * import { mercadoPagoTools, MercadoPagoClient } from \"@ar-agents/mercadopago\";\n * import {\n * VercelKVSubscriptionStateAdapter,\n * VercelKVOAuthTokenStore,\n * } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * const tools = mercadoPagoTools(client, {\n * state: new VercelKVSubscriptionStateAdapter(),\n * backUrl: \"https://mysite.com/done\",\n * // ... oauth, webhookSecret, etc.\n * });\n *\n * // For marketplace flows, also wire the OAuth token store:\n * const oauthStore = new VercelKVOAuthTokenStore();\n * await oauthStore.set(token.user_id, {\n * user_id: token.user_id,\n * access_token: token.access_token,\n * refresh_token: token.refresh_token!,\n * expires_at: Date.now() + (token.expires_in ?? 21600) * 1000,\n * });\n * ```\n *\n * # Edge Runtime\n *\n * `@vercel/kv` works in Vercel Edge Runtime, Node.js, and any environment\n * with `fetch` (it's a thin REST client over Upstash). All adapters here\n * are async and Edge-safe.\n *\n * # Key namespacing\n *\n * Each adapter uses its own prefix so multiple adapters can share the same\n * KV store without collisions:\n * - Subscriptions: `mp:sub:{id}`\n * - OAuth tokens: `mp:oauth:{userId}`\n * - Idempotency: `mp:idem:{key}`\n *\n * Pass a custom prefix via the constructor if you need to share the store\n * with other apps.\n */\n\nimport { kv as defaultKv } from \"@vercel/kv\";\nimport type { VercelKV } from \"@vercel/kv\";\nimport type { AuditEntry, AuditLogAdapter, AuditOperation } from \"./audit\";\nimport type {\n IdempotencyCache,\n OAuthTokenRecord,\n OAuthTokenStore,\n SubscriptionStateAdapter,\n SubscriptionStateRecord,\n} from \"./state\";\n\nconst DEFAULT_SUBSCRIPTION_PREFIX = \"mp:sub:\";\nconst DEFAULT_OAUTH_PREFIX = \"mp:oauth:\";\nconst DEFAULT_IDEMPOTENCY_PREFIX = \"mp:idem:\";\nconst DEFAULT_AUDIT_PREFIX = \"mp:audit:\";\n\ninterface VercelKVAdapterOptions {\n /**\n * Custom KV client. If omitted, uses the default `kv` export from\n * `@vercel/kv` (which reads `KV_REST_API_URL` + `KV_REST_API_TOKEN` from\n * env — auto-injected when you connect a KV store to your Vercel project).\n */\n kv?: VercelKV;\n /** Override the key prefix. */\n prefix?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SubscriptionStateAdapter\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVSubscriptionStateAdapter\n implements SubscriptionStateAdapter\n{\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_SUBSCRIPTION_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(id: string): string {\n return `${this.prefix}${id}`;\n }\n\n async set(\n id: string,\n state: Partial<SubscriptionStateRecord>,\n ): Promise<void> {\n const existing = (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? {};\n await this.kv.set(this.key(id), { ...existing, ...state });\n await this.kv.sadd(this.indexKey, id);\n }\n\n async get(id: string): Promise<SubscriptionStateRecord | null> {\n return (await this.kv.get<SubscriptionStateRecord>(this.key(id))) ?? null;\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n\n /** Forget a subscription record. NOT part of the adapter interface. */\n async delete(id: string): Promise<void> {\n await this.kv.del(this.key(id));\n await this.kv.srem(this.indexKey, id);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuthTokenStore (per-seller marketplace token persistence)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVOAuthTokenStore implements OAuthTokenStore {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly indexKey: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_OAUTH_PREFIX;\n this.indexKey = `${this.prefix}__index`;\n }\n\n private key(userId: string): string {\n return `${this.prefix}${userId}`;\n }\n\n async set(userId: string, token: OAuthTokenRecord): Promise<void> {\n await this.kv.set(this.key(userId), token);\n await this.kv.sadd(this.indexKey, userId);\n }\n\n async get(userId: string): Promise<OAuthTokenRecord | null> {\n return (await this.kv.get<OAuthTokenRecord>(this.key(userId))) ?? null;\n }\n\n async delete(userId: string): Promise<void> {\n await this.kv.del(this.key(userId));\n await this.kv.srem(this.indexKey, userId);\n }\n\n async list(): Promise<string[]> {\n const ids = await this.kv.smembers(this.indexKey);\n return ids.map(String);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Distributed Token Bucket Rate Limiter (KV-backed)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst DEFAULT_RATELIMIT_PREFIX = \"mp:rl:\";\n\n/**\n * Distributed token bucket rate limiter backed by Vercel KV.\n *\n * # Why distributed\n *\n * The default in-memory `TokenBucketRateLimiter` is per-process. In\n * serverless (Vercel Functions, Lambda, Cloudflare Workers), each cold\n * start gets its own bucket — meaning N concurrent instances effectively\n * have N×capacity. For multi-region deployments or marketplace setups\n * with shared MP rate budget, that's a footgun.\n *\n * This adapter uses a single Vercel KV (Upstash Redis) bucket per `key`,\n * shared across all instances. Two instances acquiring at the same time\n * decrement the same counter atomically — the rate limit holds globally.\n *\n * # Algorithm\n *\n * Standard token bucket with lazy refill: every `acquire()` call:\n * 1. Reads `{ tokens, lastRefill }` from KV\n * 2. Computes refill since `lastRefill`\n * 3. If tokens >= 1: decrements and writes back\n * 4. Otherwise: computes wait time, sleeps, retries\n *\n * The read-modify-write isn't atomic per-call, so under heavy contention\n * a small over-spend window is possible (worst case: ~N concurrent\n * acquires can succeed when only 1 token was available). Acceptable for\n * MP rate limiting — the \"actual\" budget is much higher than what we\n * provision.\n *\n * # Usage — wire via `withRateLimit` middleware\n *\n * `MercadoPagoClient` does not accept a rate limiter directly. Apply the\n * limiter at the tool layer using `withRateLimit` from the middleware\n * module, which works for both the in-memory `TokenBucketRateLimiter` and\n * this distributed variant.\n *\n * ```ts\n * import {\n * MercadoPagoClient,\n * mercadoPagoTools,\n * InMemoryStateAdapter,\n * applyToAllTools,\n * withRateLimit,\n * } from \"@ar-agents/mercadopago\";\n * import { VercelKVRateLimiter } from \"@ar-agents/mercadopago/vercel-kv\";\n *\n * // ONE distributed bucket shared across all serverless instances of this app:\n * const limiter = new VercelKVRateLimiter({\n * key: \"mp-account-prod\",\n * capacity: 50,\n * refillPerSecond: 25,\n * });\n *\n * const client = new MercadoPagoClient({ accessToken: process.env.MP_ACCESS_TOKEN! });\n * const tools = applyToAllTools(\n * mercadoPagoTools(client, { state: new InMemoryStateAdapter(), backUrl: \"...\" }),\n * withRateLimit(limiter),\n * );\n * ```\n *\n * # Concurrency caveats\n *\n * Read-modify-write is NOT strictly atomic per `acquire()`. Under heavy\n * contention a small over-spend window is possible, acceptable for **API\n * call** rate limiting where the actual MP budget exceeds what we\n * provision. **Do NOT repurpose this limiter as a money/spend cap** —\n * the over-spend window means you could exceed a money budget by the\n * concurrent-instance count × per-call cost. For money budgets, use\n * Upstash's `EVAL`-based atomic Lua script (or a stricter primitive).\n *\n * The `acquire()` retry loop applies randomized jitter (±30%) to spread\n * concurrent acquirers across multiple refill windows, mitigating the\n * thundering-herd that would otherwise hit Upstash with N reads + N\n * writes the instant a bucket refills.\n *\n * # Marketplace setups (per-seller rate limit)\n *\n * Use the seller's MP user_id as part of the `key`:\n *\n * ```ts\n * function makeLimiter(sellerUserId: string) {\n * return new VercelKVRateLimiter({\n * key: `mp-seller-${sellerUserId}`,\n * capacity: 10,\n * refillPerSecond: 5,\n * });\n * }\n * ```\n *\n * Each seller now has their own globally-distributed bucket.\n */\nexport interface VercelKVRateLimiterOptions extends VercelKVAdapterOptions {\n /**\n * Unique key for this bucket. Use distinct keys per logical \"rate-limit\n * scope\" (per-environment, per-seller, per-region, etc.). Required.\n */\n key: string;\n /** Bucket capacity (max burst). Default 50. */\n capacity?: number;\n /** Refill rate in tokens per second. Default 25. */\n refillPerSecond?: number;\n /**\n * Hard cap on how long `acquire()` will wait. If the bucket can't\n * refill in this time, `acquire()` throws. Default 30s.\n */\n acquireTimeoutMs?: number;\n /**\n * If true, `learnFromHeaders` syncs the bucket with MP's stated\n * `x-rate-limit-remaining`. Default true.\n */\n adaptive?: boolean;\n}\n\ninterface BucketState {\n tokens: number;\n lastRefillMs: number;\n}\n\nexport class VercelKVRateLimiter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n private readonly key: string;\n private readonly capacity: number;\n private readonly refillPerSecond: number;\n private readonly acquireTimeoutMs: number;\n private readonly adaptive: boolean;\n\n constructor(options: VercelKVRateLimiterOptions) {\n if (!options.key) {\n throw new Error(\n \"VercelKVRateLimiter requires a `key` (use distinct keys per rate-limit scope, e.g., per-environment or per-seller).\",\n );\n }\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_RATELIMIT_PREFIX;\n this.key = options.key;\n this.capacity = options.capacity ?? 50;\n this.refillPerSecond = options.refillPerSecond ?? 25;\n this.acquireTimeoutMs = options.acquireTimeoutMs ?? 30_000;\n this.adaptive = options.adaptive ?? true;\n }\n\n private fullKey(): string {\n return `${this.prefix}${this.key}`;\n }\n\n private async readState(): Promise<BucketState> {\n const stored = await this.kv.get<BucketState>(this.fullKey());\n if (stored && typeof stored.tokens === \"number\" && typeof stored.lastRefillMs === \"number\") {\n return stored;\n }\n return { tokens: this.capacity, lastRefillMs: Date.now() };\n }\n\n private refill(state: BucketState, nowMs: number): BucketState {\n const elapsedMs = Math.max(0, nowMs - state.lastRefillMs);\n const refilled = Math.min(\n this.capacity,\n state.tokens + (elapsedMs / 1000) * this.refillPerSecond,\n );\n return { tokens: refilled, lastRefillMs: nowMs };\n }\n\n private async writeState(state: BucketState): Promise<void> {\n // TTL = 1h. Long-idle buckets get garbage-collected, capacity rebuilds\n // from initial state on next acquire (which is fine — at the right rate).\n await this.kv.set(this.fullKey(), state, { ex: 3600 });\n }\n\n /**\n * Acquire a token. Resolves immediately if the distributed bucket has\n * one available; otherwise waits until refilled. Throws if the wait\n * exceeds `acquireTimeoutMs` or if the retry cap is reached.\n *\n * Caps retries at 8 iterations so a misconfigured bucket (capacity too\n * low for traffic) fails fast for the agent layer to surface, instead\n * of silently burning serverless compute time.\n */\n async acquire(): Promise<void> {\n const start = Date.now();\n const MAX_RETRIES = 8;\n let attempt = 0;\n while (attempt < MAX_RETRIES) {\n const now = Date.now();\n const state = this.refill(await this.readState(), now);\n\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return;\n }\n\n // Compute wait time until next token. Cap at remaining timeout budget.\n const tokensNeeded = 1 - state.tokens;\n const baseWaitMs = Math.ceil((tokensNeeded / this.refillPerSecond) * 1000);\n // Randomized jitter (±30%) prevents thundering herd: without it, all\n // concurrent acquirers compute identical waitMs, sleep identical\n // duration, and wake at the same wall-clock instant — then all hit\n // KV simultaneously. Jitter spreads them across the refill window.\n const jitterFactor = 0.7 + Math.random() * 0.6; // 0.7–1.3\n const waitMs = Math.ceil(baseWaitMs * jitterFactor);\n const elapsed = now - start;\n if (elapsed + waitMs > this.acquireTimeoutMs) {\n throw new Error(\n `VercelKVRateLimiter acquire timed out after ${elapsed + waitMs}ms (key=${this.key}).`,\n );\n }\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n attempt += 1;\n }\n throw new Error(\n `VercelKVRateLimiter exhausted ${MAX_RETRIES} retries (key=${this.key}). ` +\n `Bucket likely undersized for traffic — increase capacity or refillPerSecond.`,\n );\n }\n\n /** Best-effort acquire — returns true if a token was available, false otherwise. */\n async tryAcquire(): Promise<boolean> {\n const state = this.refill(await this.readState(), Date.now());\n if (state.tokens >= 1) {\n state.tokens -= 1;\n await this.writeState(state);\n return true;\n }\n return false;\n }\n\n /**\n * Adaptive learning — call after each MP API response. If MP's stated\n * `x-rate-limit-remaining` is lower than our local count, trust MP and\n * drop the bucket to match (prevents over-spending).\n */\n async learnFromHeaders(headers: {\n remaining: number | null;\n resetSeconds: number | null;\n }): Promise<void> {\n if (!this.adaptive) return;\n if (headers.remaining === null) return;\n const state = this.refill(await this.readState(), Date.now());\n if (headers.remaining < state.tokens) {\n state.tokens = Math.max(0, headers.remaining);\n await this.writeState(state);\n }\n }\n\n /** Inspect bucket state. */\n async getStats(): Promise<{ tokens: number; capacity: number; refillPerSecond: number }> {\n const state = this.refill(await this.readState(), Date.now());\n return {\n tokens: state.tokens,\n capacity: this.capacity,\n refillPerSecond: this.refillPerSecond,\n };\n }\n\n /** Reset the bucket to full. Use sparingly (e.g., after a known-clean window). */\n async reset(): Promise<void> {\n await this.writeState({ tokens: this.capacity, lastRefillMs: Date.now() });\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// IdempotencyCache (KV-backed dedup of agent retries)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class VercelKVIdempotencyCache implements IdempotencyCache {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_IDEMPOTENCY_PREFIX;\n }\n\n private key(k: string): string {\n return `${this.prefix}${k}`;\n }\n\n async get<T>(key: string): Promise<T | null> {\n return (await this.kv.get<T>(this.key(key))) ?? null;\n }\n\n async set<T>(key: string, value: T, ttlSeconds = 86_400): Promise<void> {\n // Vercel KV's `set` supports a TTL in seconds via the `ex` option.\n await this.kv.set(this.key(key), value, { ex: ttlSeconds });\n }\n\n async delete(key: string): Promise<void> {\n await this.kv.del(this.key(key));\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// AuditLogAdapter — production audit trail with daily-bucket indexing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vercel KV–backed audit log adapter. Stores each entry under\n * `mp:audit:entry:{id}` AND adds the id to a daily index sorted set\n * `mp:audit:day:{YYYY-MM-DD}` (score = timestamp ms). This gives O(log N)\n * time-range queries (\"all entries from May 1 to May 5\") without scanning\n * the entire log.\n *\n * # Storage layout\n *\n * - `mp:audit:entry:{id}` → the full entry JSON\n * - `mp:audit:day:{YYYY-MM-DD}` → ZSET of entry ids by timestamp (ms)\n * - `mp:audit:actor:{actor}` → ZSET of entry ids by timestamp (for \"all\n * entries by actor X\")\n * - `mp:audit:tenant:{tenantId}` → same, by tenant\n *\n * # Cost considerations\n *\n * Each `append()` does 1-3 KV writes (entry + 1-2 indexes). For high-traffic\n * deployments (>10/s sustained), batch via your own queue (e.g., Vercel\n * Queues with daily flush) and provide a custom adapter that batches.\n */\nexport class VercelKVAuditLog implements AuditLogAdapter {\n private readonly kv: VercelKV;\n private readonly prefix: string;\n\n constructor(options: VercelKVAdapterOptions = {}) {\n this.kv = options.kv ?? defaultKv;\n this.prefix = options.prefix ?? DEFAULT_AUDIT_PREFIX;\n }\n\n async append(entry: AuditEntry): Promise<void> {\n const ts = new Date(entry.timestamp).getTime();\n const day = entry.timestamp.slice(0, 10); // YYYY-MM-DD\n await Promise.all([\n this.kv.set(`${this.prefix}entry:${entry.id}`, entry),\n this.kv.zadd(`${this.prefix}day:${day}`, { score: ts, member: entry.id }),\n this.kv.zadd(`${this.prefix}actor:${entry.actor}`, { score: ts, member: entry.id }),\n ...(entry.tenantId\n ? [\n this.kv.zadd(`${this.prefix}tenant:${entry.tenantId}`, {\n score: ts,\n member: entry.id,\n }),\n ]\n : []),\n ]);\n }\n\n async query(filter: {\n actor?: string;\n operation?: AuditOperation;\n tenantId?: string;\n from?: string;\n to?: string;\n limit?: number;\n }): Promise<AuditEntry[]> {\n const limit = filter.limit ?? 100;\n let ids: string[];\n\n // Pick the most selective index available\n if (filter.actor) {\n ids = await this.zrangeByScore(\n `${this.prefix}actor:${filter.actor}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.tenantId) {\n ids = await this.zrangeByScore(\n `${this.prefix}tenant:${filter.tenantId}`,\n filter.from,\n filter.to,\n limit,\n );\n } else if (filter.from || filter.to) {\n // Walk daily buckets for the date range\n const fromDate = filter.from?.slice(0, 10) ?? \"0000-00-00\";\n const toDate = filter.to?.slice(0, 10) ?? \"9999-99-99\";\n ids = [];\n // Cap walk to ~1 year max to avoid runaway\n const fromTs = new Date(fromDate).getTime();\n const toTs = new Date(toDate).getTime();\n for (let d = fromTs; d <= toTs && ids.length < limit; d += 86_400_000) {\n const day = new Date(d).toISOString().slice(0, 10);\n const dayIds = await this.zrangeByScore(\n `${this.prefix}day:${day}`,\n filter.from,\n filter.to,\n limit - ids.length,\n );\n ids.push(...dayIds);\n }\n } else {\n // No filter — bail (full scan would be unbounded)\n return [];\n }\n\n // Load entries\n const entries: AuditEntry[] = [];\n for (const id of ids) {\n const entry = await this.kv.get<AuditEntry>(`${this.prefix}entry:${id}`);\n if (!entry) continue;\n if (filter.operation && entry.operation !== filter.operation) continue;\n entries.push(entry);\n }\n return entries;\n }\n\n private async zrangeByScore(\n key: string,\n from?: string,\n to?: string,\n limit?: number,\n ): Promise<string[]> {\n const min = from ? new Date(from).getTime() : 0;\n const max = to ? new Date(to).getTime() : Number.MAX_SAFE_INTEGER;\n const opts = {\n byScore: true as const,\n offset: 0,\n ...(limit !== undefined ? { count: limit } : { count: 100 }),\n };\n const ids = await this.kv.zrange(key, min, max, opts);\n return ids.map(String);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ar-agents/mercadopago",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "The most complete Mercado Pago agent toolkit for Vercel AI SDK 6+. 82 tools, Edge Runtime, Vercel KV adapters, circuit breaker + deadline propagation + W3C trace context, HMAC webhook verify with replay protection, OAuth Marketplace, Order Management, Point Devices, marketplace splits, status_detail explainer in Spanish, AR-specific knowledge baked in. Property-tested + integration-tested vs MP sandbox + benchmarked.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mercadopago",
|