{"version":3,"sources":["../src/utils/rate-limit.ts"],"sourcesContent":["export interface RateLimitOptions {\n  maxRequestsPerInterval: number; // tokens refilled every interval\n  intervalMs: number; // refill period\n  maxConcurrency: number; // simultaneous in-flight requests\n}\n\ntype Task<T> = {\n  fn: () => Promise<T>;\n  resolve: (value: T) => void;\n  reject: (reason?: any) => void;\n};\n\nclass RateLimiter {\n  private options: RateLimitOptions;\n  private queue: Task<any>[] = [];\n  private tokens: number;\n  private inFlight = 0;\n  private refillTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor(options: RateLimitOptions) {\n    this.options = options;\n    this.tokens = options.maxRequestsPerInterval;\n  }\n\n  private startRefill() {\n    if (this.refillTimer) return;\n    this.refillTimer = setInterval(() => {\n      this.tokens = this.options.maxRequestsPerInterval;\n      this.tryRun();\n    }, this.options.intervalMs);\n    // In some runtimes, timers keep process alive; make it best-effort\n    if (\n      this.refillTimer &&\n      typeof (this.refillTimer as any).unref === \"function\"\n    ) {\n      (this.refillTimer as any).unref();\n    }\n  }\n\n  private stopRefill() {\n    if (this.refillTimer) {\n      clearInterval(this.refillTimer);\n      this.refillTimer = null;\n    }\n  }\n\n  private ensureRefillStarted() {\n    if (!this.refillTimer) {\n      this.startRefill();\n    }\n  }\n\n  configure(next: Partial<RateLimitOptions>) {\n    this.options = { ...this.options, ...next };\n    // Clamp to sensible minimums\n    this.options.maxRequestsPerInterval = Math.max(\n      1,\n      this.options.maxRequestsPerInterval\n    );\n    this.options.intervalMs = Math.max(10, this.options.intervalMs);\n    this.options.maxConcurrency = Math.max(1, this.options.maxConcurrency);\n  }\n\n  schedule<T>(fn: () => Promise<T>): Promise<T> {\n    return new Promise<T>((resolve, reject) => {\n      // Start the timer lazily on first schedule/use\n      this.ensureRefillStarted();\n      this.queue.push({ fn, resolve, reject });\n      this.tryRun();\n    });\n  }\n\n  private tryRun() {\n    while (\n      this.queue.length > 0 &&\n      this.inFlight < this.options.maxConcurrency &&\n      this.tokens > 0\n    ) {\n      const task = this.queue.shift()!;\n      this.tokens -= 1;\n      this.inFlight += 1;\n\n      // Execute task and release slot when done\n      Promise.resolve()\n        .then(task.fn)\n        .then((result) => task.resolve(result))\n        .catch((err) => task.reject(err))\n        .finally(() => {\n          this.inFlight -= 1;\n          // Yield to event loop, then continue draining\n          setTimeout(() => this.tryRun(), 0);\n        });\n    }\n  }\n}\n\nlet enabled = false;\nconst defaultOptions: RateLimitOptions = {\n  maxRequestsPerInterval: 5, // 5 requests\n  intervalMs: 1000, // per second\n  maxConcurrency: 5, // up to 5 in parallel\n};\n\nconst limiter = new RateLimiter(defaultOptions);\nconst hostLimiters = new Map<string, RateLimiter>();\nconst classLimiters = new Map<string, RateLimiter>();\n\nexport type RateLimitedRequestInit = RequestInit & { rateLimitClass?: string };\n\nfunction getHost(input: RequestInfo | URL): string | undefined {\n  try {\n    if (typeof input === \"string\") {\n      return new URL(input).host;\n    }\n    if (input instanceof URL) {\n      return input.host;\n    }\n    // Request object or similar\n    const url = (input as any).url as string | undefined;\n    if (url) {\n      return new URL(url).host;\n    }\n  } catch {\n    // ignore parsing errors\n  }\n  return undefined;\n}\n\nfunction getHostLimiter(host?: string): RateLimiter | undefined {\n  if (!host) return undefined;\n  // Exact match first\n  const exact = hostLimiters.get(host);\n  if (exact) return exact;\n  // Wildcard suffix match: keys of the form '*.example.com'\n  for (const [key, lim] of hostLimiters.entries()) {\n    if (key.startsWith(\"*.\") && host.endsWith(key.slice(2))) {\n      return lim;\n    }\n  }\n  return undefined;\n}\n\nexport function configureRateLimit(\n  options: Partial<RateLimitOptions & { enabled: boolean }> & {\n    perHost?: Record<string, Partial<RateLimitOptions>>; // key: host (supports '*.example.com')\n    perClass?: Record<string, Partial<RateLimitOptions>>; // key: arbitrary class name\n  }\n) {\n  if (typeof options.enabled === \"boolean\") {\n    enabled = options.enabled;\n  }\n  const { perHost, perClass } = options;\n  const globalOpts: Partial<RateLimitOptions> = {};\n  if (typeof options.maxRequestsPerInterval === \"number\") {\n    globalOpts.maxRequestsPerInterval = options.maxRequestsPerInterval;\n  }\n  if (typeof options.intervalMs === \"number\") {\n    globalOpts.intervalMs = options.intervalMs;\n  }\n  if (typeof options.maxConcurrency === \"number\") {\n    globalOpts.maxConcurrency = options.maxConcurrency;\n  }\n  if (Object.keys(globalOpts).length) {\n    limiter.configure(globalOpts);\n  }\n  if (perHost) {\n    for (const host of Object.keys(perHost)) {\n      const opts = perHost[host]!;\n      const existing = hostLimiters.get(host);\n      if (existing) {\n        existing.configure(opts);\n      } else {\n        hostLimiters.set(\n          host,\n          new RateLimiter({\n            maxRequestsPerInterval:\n              opts.maxRequestsPerInterval ??\n              defaultOptions.maxRequestsPerInterval,\n            intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n            maxConcurrency:\n              opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n          })\n        );\n      }\n    }\n  }\n  if (perClass) {\n    for (const klass of Object.keys(perClass)) {\n      const opts = perClass[klass]!;\n      const existing = classLimiters.get(klass);\n      if (existing) {\n        existing.configure(opts);\n      } else {\n        classLimiters.set(\n          klass,\n          new RateLimiter({\n            maxRequestsPerInterval:\n              opts.maxRequestsPerInterval ??\n              defaultOptions.maxRequestsPerInterval,\n            intervalMs: opts.intervalMs ?? defaultOptions.intervalMs,\n            maxConcurrency:\n              opts.maxConcurrency ?? defaultOptions.maxConcurrency,\n          })\n        );\n      }\n    }\n  }\n}\n\nexport async function rateLimitedFetch(\n  input: RequestInfo | URL,\n  init?: RateLimitedRequestInit\n): Promise<Response> {\n  if (!enabled) {\n    return fetch(input as any, init);\n  }\n  const klass = init?.rateLimitClass;\n  const byClass = klass ? classLimiters.get(klass) : undefined;\n  const byHost = getHostLimiter(getHost(input));\n  const eff = byClass ?? byHost ?? limiter;\n  return eff.schedule(() => fetch(input as any, init));\n}\n\nexport function getRateLimitStatus() {\n  return {\n    enabled,\n    options: { ...defaultOptions },\n  };\n}\n"],"mappings":"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","names":[]}