{"version":3,"file":"rate-limit.cjs","names":["createPassthroughRateLimiter","createDefaultRateLimiter"],"sources":["../../src/utils/rate-limit.ts"],"sourcesContent":["import type { AdaptiveConfig, RateLimitContext, RateLimiter } from \"./limiter\";\nimport { createDefaultRateLimiter, createPassthroughRateLimiter } from \"./limiter\";\n\nconst DEFAULT_REQUESTS_PER_SECOND = 6;\nconst MAX_RATE_LIMIT = 1_000;\n/** The Management API enforces one quota per token, so one bucket covers it. */\nconst BUCKET = \"management-api\";\n\nexport interface RateLimitConfig {\n  /**\n   * Maximum number of MAPI requests to start per second.\n   * Defaults to 6. Capped at 1000.\n   */\n  requestsPerSecond?: number;\n  /**\n   * @deprecated Use `requestsPerSecond` instead.\n   * @todo(next-major): Remove this field.\n   */\n  maxConcurrency?: number;\n  /**\n   * Adapt the rate to what the API actually sustains: back off multiplicatively\n   * whenever a request is throttled, recover additively while none is.\n   *\n   * Rate limits are enforced per token, but each client instance paces itself\n   * alone, so parallel builds or workers sharing a token overrun the quota\n   * between them. Adaptation lets them settle at a rate the token sustains.\n   * The limit never rises above the one the client would have used anyway.\n   *\n   * Pass an object to tune it, or `false` to pin the rate to `requestsPerSecond`.\n   * @default true\n   */\n  adaptive?: boolean | AdaptiveConfig;\n  /**\n   * Replaces the in-memory limiter, which paces each instance independently.\n   *\n   * Supply one backed by shared storage (Redis, Upstash, …) to hold a fleet of\n   * instances to a single quota proactively rather than by reacting to 429s.\n   * `requestsPerSecond` and `adaptive` do not apply to a custom limiter; the\n   * rate the client would have used is passed to it as `context.limit`.\n   */\n  limiter?: RateLimiter;\n}\n\nexport interface ThrottleManager {\n  /**\n   * @deprecated Admission happens in `beforeRequest`, or in `wrapFetch` for a\n   * caller that only wraps `fetch`; this only runs `fn`.\n   * @todo(next-major): Remove this method.\n   */\n  execute: <T>(fn: () => Promise<T>) => Promise<T>;\n  /**\n   * Waits for the limiter to admit one request. Register it as a hook that runs\n   * before the HTTP layer starts its timeout, so a queue longer than the\n   * timeout delays requests instead of failing them.\n   */\n  beforeRequest: (request: Request) => Promise<void>;\n  /**\n   * Wraps `fetch` so the limiter observes and releases every HTTP request —\n   * retries included — and admits the ones `beforeRequest` did not.\n   */\n  wrapFetch: (fetchFn: typeof globalThis.fetch) => typeof globalThis.fetch;\n}\n\nfunction getRequestUrl(input: RequestInfo | URL): string | undefined {\n  if (typeof input === \"string\") {\n    return input;\n  }\n  if (input instanceof URL) {\n    return input.href;\n  }\n  return typeof input === \"object\" && input !== null && \"url\" in input ? input.url : undefined;\n}\n\n/** Query parameters that carry credentials and must not reach a limiter. */\nconst CREDENTIAL_PARAMS = new Set([\"token\"]);\n\n/**\n * Splits an outgoing request URL into the path and query a limiter is given.\n *\n * Derived from the URL rather than carried alongside the call so that it stays\n * correct when several requests are in flight at once.\n */\nfunction requestParts(url: string | undefined): { path: string; query: Record<string, unknown> } {\n  if (url === undefined) {\n    return { path: \"\", query: {} };\n  }\n\n  try {\n    const parsed = new URL(url);\n    const query: Record<string, unknown> = {};\n    for (const [key, value] of parsed.searchParams) {\n      if (!CREDENTIAL_PARAMS.has(key)) {\n        query[key] = value;\n      }\n    }\n    return { path: parsed.pathname, query };\n  } catch {\n    // A URL that will not parse must still not hand a token to a custom\n    // limiter, so the query is dropped rather than passed through unread.\n    return { path: url.split(\"?\")[0] ?? url, query: {} };\n  }\n}\n\n/**\n * Creates a `ThrottleManager` from the user-supplied `rateLimit` config.\n *\n * - `false`                       → no throttling (passthrough)\n * - `number`                      → N requests per second\n * - `{ requestsPerSecond: n }`    → N requests per second\n * - `{}` / `undefined` (default)  → DEFAULT_REQUESTS_PER_SECOND per second\n */\nexport function createThrottleManager(config: RateLimitConfig | number | false): ThrottleManager {\n  if (config === false) {\n    return createManager(createPassthroughRateLimiter(), Number.POSITIVE_INFINITY);\n  }\n\n  const resolvedConfig: RateLimitConfig =\n    typeof config === \"number\" ? { requestsPerSecond: config } : config;\n  const { requestsPerSecond, maxConcurrency, adaptive = true, limiter } = resolvedConfig;\n  const rps = requestsPerSecond ?? maxConcurrency ?? DEFAULT_REQUESTS_PER_SECOND;\n\n  return createManager(\n    limiter ?? createDefaultRateLimiter({ adaptive }),\n    Math.min(rps, MAX_RATE_LIMIT),\n  );\n}\n\nfunction createManager(limiter: RateLimiter, limit: number): ThrottleManager {\n  // Each attempt is admitted separately, because the HTTP layer retries inside\n  // one call: with the default `retry.limit` of 12, gating the call alone would\n  // let one admitted request put 13 on the wire. The request carries its\n  // context from admission to response so that a limiter pairing a release to\n  // its acquire by identity finds the one it admitted.\n  const admitted = new WeakMap<Request, RateLimitContext>();\n\n  const admit = async (url: string | undefined): Promise<RateLimitContext> => {\n    const context: RateLimitContext = { ...requestParts(url), bucket: BUCKET, limit };\n    await limiter.acquire(context);\n    return context;\n  };\n\n  return {\n    execute: (fn) => fn(),\n    beforeRequest: async (request) => {\n      admitted.set(request, await admit(request.url));\n    },\n    wrapFetch: (fetchFn) => async (input, init) => {\n      // Queueing here would count against the request timeout, so admission\n      // belongs in `beforeRequest`. Waiting for it here regardless keeps a\n      // caller that only wraps `fetch` paced rather than unthrottled.\n      const context =\n        (input instanceof Request ? admitted.get(input) : undefined) ??\n        (await admit(getRequestUrl(input)));\n\n      try {\n        const response = await fetchFn(input, init);\n        // The limiter gets a copy: reading the body of the response the caller\n        // is waiting for would consume it.\n        await report(() => limiter.recordResponse?.(context, response.clone()));\n        return response;\n      } finally {\n        await report(() => limiter.release?.(context));\n      }\n    },\n  };\n}\n\n/**\n * Swallows a reporting hook's failure. Shared storage can fail transiently, and\n * neither recording a response nor releasing a slot may turn a served request\n * into an error, or, inside the retry loop, into another request.\n */\nasync function report(hook: () => void | Promise<void>): Promise<void> {\n  try {\n    await hook();\n  } catch {\n    // Intentionally ignored.\n  }\n}\n"],"mappings":";;AAGA,MAAM,8BAA8B;AACpC,MAAM,iBAAiB;;AAEvB,MAAM,SAAS;AAyDf,SAAS,cAAc,OAA8C;CACnE,IAAI,OAAO,UAAU,UACnB,OAAO;CAET,IAAI,iBAAiB,KACnB,OAAO,MAAM;CAEf,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,SAAS,QAAQ,MAAM,MAAM,KAAA;AACrF;;AAGA,MAAM,oCAAoB,IAAI,IAAI,CAAC,OAAO,CAAC;;;;;;;AAQ3C,SAAS,aAAa,KAA2E;CAC/F,IAAI,QAAQ,KAAA,GACV,OAAO;EAAE,MAAM;EAAI,OAAO,CAAC;CAAE;CAG/B,IAAI;EACF,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,MAAM,QAAiC,CAAC;EACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,cAChC,IAAI,CAAC,kBAAkB,IAAI,GAAG,GAC5B,MAAM,OAAO;EAGjB,OAAO;GAAE,MAAM,OAAO;GAAU;EAAM;CACxC,QAAQ;EAGN,OAAO;GAAE,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC,MAAM;GAAK,OAAO,CAAC;EAAE;CACrD;AACF;;;;;;;;;AAUA,SAAgB,sBAAsB,QAA2D;CAC/F,IAAI,WAAW,OACb,OAAO,cAAcA,gBAAAA,6BAA6B,GAAG,OAAO,iBAAiB;CAK/E,MAAM,EAAE,mBAAmB,gBAAgB,WAAW,MAAM,YAD1D,OAAO,WAAW,WAAW,EAAE,mBAAmB,OAAO,IAAI;CAE/D,MAAM,MAAM,qBAAqB,kBAAkB;CAEnD,OAAO,cACL,WAAWC,gBAAAA,yBAAyB,EAAE,SAAS,CAAC,GAChD,KAAK,IAAI,KAAK,cAAc,CAC9B;AACF;AAEA,SAAS,cAAc,SAAsB,OAAgC;CAM3E,MAAM,2BAAW,IAAI,QAAmC;CAExD,MAAM,QAAQ,OAAO,QAAuD;EAC1E,MAAM,UAA4B;GAAE,GAAG,aAAa,GAAG;GAAG,QAAQ;GAAQ;EAAM;EAChF,MAAM,QAAQ,QAAQ,OAAO;EAC7B,OAAO;CACT;CAEA,OAAO;EACL,UAAU,OAAO,GAAG;EACpB,eAAe,OAAO,YAAY;GAChC,SAAS,IAAI,SAAS,MAAM,MAAM,QAAQ,GAAG,CAAC;EAChD;EACA,YAAY,YAAY,OAAO,OAAO,SAAS;GAI7C,MAAM,WACH,iBAAiB,UAAU,SAAS,IAAI,KAAK,IAAI,KAAA,MACjD,MAAM,MAAM,cAAc,KAAK,CAAC;GAEnC,IAAI;IACF,MAAM,WAAW,MAAM,QAAQ,OAAO,IAAI;IAG1C,MAAM,aAAa,QAAQ,iBAAiB,SAAS,SAAS,MAAM,CAAC,CAAC;IACtE,OAAO;GACT,UAAU;IACR,MAAM,aAAa,QAAQ,UAAU,OAAO,CAAC;GAC/C;EACF;CACF;AACF;;;;;;AAOA,eAAe,OAAO,MAAiD;CACrE,IAAI;EACF,MAAM,KAAK;CACb,QAAQ,CAER;AACF"}