import { type Reporter } from '@quenty/cli-output-helpers/reporting'; /** A wait the limiter is about to sleep through before sending a request. */ export interface RateLimitWait { route: string; waitMs: number; /** * `quota`: a prior response said this endpoint's quota is spent. * `retry`: this request failed (429, 5xx, transport) and is backing off. */ reason: 'quota' | 'retry'; /** HTTP status that triggered a `retry` wait, absent for transport errors. */ status?: number; } /** * Builds an `onWait` that shows the wait as the package's progress label, so * a spinner stuck on "Scheduling" reads as rate limiting rather than a hang. */ export declare function reportRateLimitWait(reporter: Reporter, packageName: string): (wait: RateLimitWait) => void; /** * Kept short enough that "◇ Scheduling (…)" fits the spinner's 32-column * status cell: a longer label wraps on an 80-column terminal, and the * spinner's cursor rewind doesn't count wrapped lines. The `--verbose` * warnings carry the status code and route. */ export declare function formatRateLimitWait(wait: RateLimitWait): string; /** * Minimal fetch shape the limiter drives. Defaults to the global `fetch` * (undici). Callers can inject an alternative — e.g. node-fetch, whose Node * `http` stack accepts a streaming request body against the Open Cloud upload * endpoint where undici throws "expected non-null body source". */ export type FetchLike = (url: string | URL | Request, init?: RequestInit) => Promise; export interface RateLimiterOptions { /** * Maximum number of concurrent in-flight requests. Defaults to 4. * Pick higher only after measuring — the default balances wallclock against * keeping a tight feedback loop on `x-ratelimit-remaining`. */ maxConcurrency?: number; /** * Total attempts per request before giving up (including the initial try). * Defaults to 6, sized to ride out the Luau-execution endpoint's burst * windows during a multi-place deploy. */ maxRetries?: number; } /** * Shared rate limiter for Roblox Open Cloud API requests. * * - Tracks `x-ratelimit-remaining` and `x-ratelimit-reset` (seconds until reset) * per endpoint, so a spent task-creation quota doesn't stall task polling * - Caps concurrent in-flight requests (default 4) via a semaphore so callers * benefit from parallelism (e.g. multi-place deploys) while still getting * header feedback before each wave widens past the quota * - Retries on 429 with exponential back-off plus 0-30% additive jitter so * concurrent callers don't all wake up on the same millisecond and re-collide */ export declare class RateLimiter { private _quotas; /** Queue of callers waiting to send a request. */ private _queue; private _inflight; private _maxConcurrency; private _maxRetries; constructor(options?: RateLimiterOptions); private _updateFromHeaders; private _waitIfNeededAsync; /** * Acquire a send slot. Up to `_maxConcurrency` callers can hold a slot at * once — beyond that they queue. The cap keeps the header-feedback loop * tight without artificially serializing unrelated work. */ private _acquireAsync; private _release; /** * Wraps a fetch call with rate-limit awareness: * - Caps concurrency at `_maxConcurrency` (default 4) so we keep getting * header feedback before each new wave fans out * - Delays if we know we're out of quota * - Retries on 429, retryable 5xx (502/503/504), and transport errors * with jittered back-off * - Tracks rate-limit headers from every response * - Tells `onWait` before every sleep, so callers can say why they stalled */ fetchAsync(url: string | URL | Request, init?: RequestInit | (() => RequestInit), options?: { fetchImpl?: FetchLike; onWait?: (wait: RateLimitWait) => void; }): Promise; } //# sourceMappingURL=rate-limiter.d.ts.map