lunibee 0.3.0 → 0.3.2
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/dist/builders/attachment.d.ts +1 -0
- package/dist/builders/clone.d.ts +10 -0
- package/dist/builders/commands.d.ts +1 -1
- package/dist/builders/components.d.ts +24 -5
- package/dist/builders/embed.d.ts +1 -0
- package/dist/builders/index.js +68 -17
- package/dist/builders/index.js.map +10 -9
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +297 -19
- package/dist/collection/index.js.map +5 -4
- package/dist/collection/redis.d.ts +98 -0
- package/dist/core/collector.d.ts +8 -0
- package/dist/core/events.d.ts +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +6252 -4704
- package/dist/core/index.js.map +47 -40
- package/dist/core/permissions.d.ts +3 -402
- package/dist/core/redis-cache.d.ts +85 -0
- package/dist/formatters/index.d.ts +5 -1
- package/dist/formatters/index.js +7 -3
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js +11 -2
- package/dist/handlers/index.js.map +3 -3
- package/dist/index.js +10481 -6148
- package/dist/index.js.map +70 -56
- package/dist/managers/application.d.ts +10 -6
- package/dist/managers/base.d.ts +4 -1
- package/dist/managers/guild.d.ts +3 -1
- package/dist/managers/index.d.ts +6 -1
- package/dist/managers/index.js +365 -103
- package/dist/managers/index.js.map +19 -22
- package/dist/managers/member.d.ts +3 -1
- package/dist/managers/message.d.ts +13 -4
- package/dist/managers/thread.d.ts +3 -3
- package/dist/rest/decoder.d.ts +2 -0
- package/dist/rest/errors.d.ts +13 -2
- package/dist/rest/headers.d.ts +31 -0
- package/dist/rest/identity.d.ts +31 -0
- package/dist/rest/index.d.ts +99 -6
- package/dist/rest/index.js +1412 -231
- package/dist/rest/index.js.map +16 -13
- package/dist/rest/limiter.d.ts +75 -10
- package/dist/rest/redis.d.ts +103 -22
- package/dist/rest/scheduler.d.ts +13 -0
- package/dist/rest/store.d.ts +135 -12
- package/dist/rest/telemetry.d.ts +151 -0
- package/dist/rest/transport.d.ts +9 -0
- package/dist/rest/webhook.d.ts +18 -6
- package/dist/sharding/bus.d.ts +12 -2
- package/dist/sharding/cluster.d.ts +225 -15
- package/dist/sharding/context.d.ts +41 -0
- package/dist/sharding/gateway-bot.d.ts +20 -0
- package/dist/sharding/health.d.ts +94 -0
- package/dist/sharding/host.d.ts +76 -0
- package/dist/sharding/identify.d.ts +64 -0
- package/dist/sharding/index.d.ts +62 -12
- package/dist/sharding/index.js +2459 -202
- package/dist/sharding/index.js.map +21 -13
- package/dist/sharding/protocol.d.ts +47 -0
- package/dist/sharding/remote-protocol.d.ts +100 -0
- package/dist/sharding/remote.d.ts +134 -0
- package/dist/sharding/requests.d.ts +54 -0
- package/dist/sharding/supervisor.d.ts +1 -1
- package/dist/sharding/transport.d.ts +6 -5
- package/dist/sharding/worker.d.ts +55 -0
- package/dist/structures/base.d.ts +16 -5
- package/dist/structures/index.d.ts +1 -1
- package/dist/structures/index.js +57 -91
- package/dist/structures/index.js.map +10 -18
- package/dist/structures/options.d.ts +3 -2
- package/dist/structures/permissions.d.ts +409 -0
- package/dist/structures/resources.d.ts +4 -1
- package/dist/types/gateway.d.ts +7 -0
- package/dist/types/index.d.ts +14 -4
- package/dist/types/index.js.map +3 -3
- package/dist/utils/args.d.ts +3 -1
- package/dist/utils/index.d.ts +6 -1
- package/dist/utils/index.js +17 -10
- package/dist/utils/index.js.map +4 -4
- package/dist/voice/index.d.ts +7 -1
- package/dist/voice/index.js +33 -10
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/index.d.ts +3 -1
- package/dist/ws/index.js +33 -5
- package/dist/ws/index.js.map +6 -6
- package/dist/ws/send-budget.d.ts +1 -1
- package/package.json +22 -17
package/dist/rest/index.js.map
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../packages/rest/src/store.ts", "../../packages/rest/src/errors.ts", "../../packages/rest/src/route.ts", "../../packages/rest/src/scheduler.ts", "../../packages/rest/src/limiter.ts", "../../packages/rest/src/transport.ts", "../../packages/rest/src/decoder.ts", "../../packages/rest/src/routes.ts", "../../packages/rest/src/error-codes.ts", "../../packages/rest/src/redis.ts", "../../packages/rest/src/webhook.ts", "../../packages/rest/src/index.ts"],
|
|
3
|
+
"sources": ["../../packages/rest/src/store.ts", "../../packages/rest/src/errors.ts", "../../packages/rest/src/route.ts", "../../packages/rest/src/scheduler.ts", "../../packages/rest/src/headers.ts", "../../packages/rest/src/telemetry.ts", "../../packages/rest/src/limiter.ts", "../../packages/rest/src/identity.ts", "../../packages/rest/src/transport.ts", "../../packages/rest/src/decoder.ts", "../../packages/rest/src/routes.ts", "../../packages/rest/src/error-codes.ts", "../../packages/rest/src/redis.ts", "../../packages/rest/src/webhook.ts", "../../packages/rest/src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export interface BucketState {\n remaining: number;\n resetAt: number;\n}\n\n/**\n * Outcome of an atomic reservation.\n *\n * `granted` means one unit of the bucket's allowance now belongs to the caller\n * and to nobody else — the distinction that a read-then-write cannot make when\n * several workers share a store.\n */\nexport type Reservation =\n { granted: true } | { granted: false; retryAfterMs: number };\n\n/** Interface for distributed or local rate limit synchronization. */\nexport interface RateLimitStore {\n /** Gets the server bucket hash for a normalized route. */\n getBucketHash(\n route: string,\n ): Promise<string | undefined> | string | undefined;\n /** Associates a route with a server bucket hash. */\n setBucketHash(route: string, hash: string): Promise<void> | void;\n\n /** Gets the current state for a bucket key. */\n getBucket(\n key: string,\n ): Promise<BucketState | undefined> | BucketState | undefined;\n /** Updates the state for a bucket key. */\n updateBucket(key: string, state: BucketState): Promise<void> | void;\n\n /** Gets the global reset timestamp. */\n getGlobalReset(): Promise<number> | number;\n /** Sets the global reset timestamp. */\n setGlobalReset(resetAt: number): Promise<void> | void;\n\n /**\n * Atomically consumes one unit of a bucket's allowance.\n *\n * Optional: a store that cannot do this atomically must omit it, and the\n * limiter falls back to waiting on observed state. Implementations must\n * grant when the bucket is unknown or its window has elapsed — an unknown\n * bucket is discovered by sending, and refusing would deadlock the route.\n */\n reserve?(key: string): Promise<Reservation> | Reservation;\n\n /**\n * Evicts expired bucket entries. Optional: stores with native key\n * expiry (e.g. Redis) can omit it. `maxAge` (ms) additionally drops\n * buckets whose reset is older than that window; defaults to 0 (only\n * already-reset buckets are removed).\n */\n prune?(maxAge?: number): Promise<void> | void;\n}\n\n/** Local memory implementation of RateLimitStore. */\nexport class MemoryRateLimitStore implements RateLimitStore {\n readonly #buckets = new Map<string, BucketState>();\n readonly #routeBuckets = new Map<string, string>();\n #globalResetAt = 0;\n /** Minimum interval between automatic prunes, in ms. */\n static readonly #PRUNE_INTERVAL = 60_000;\n #lastPruneAt = 0;\n\n public getBucketHash(route: string): string | undefined {\n return this.#routeBuckets.get(route);\n }\n public setBucketHash(route: string, hash: string): void {\n this.#routeBuckets.set(route, hash);\n }\n public getBucket(key: string): BucketState | undefined {\n return this.#buckets.get(key);\n }\n /**\n * Consumes one unit of the bucket's allowance.\n *\n * Single-process reservation is trivially atomic: JavaScript will not\n * interleave this method with another caller's copy of it.\n */\n public reserve(key: string): Reservation {\n const state = this.#buckets.get(key);\n const now = Date.now();\n // Unknown bucket, or a window that has already elapsed: the next\n // response re-establishes the real allowance.\n if (!state || state.resetAt <= now) return { granted: true };\n if (state.remaining > 0) {\n state.remaining -= 1;\n return { granted: true };\n }\n return { granted: false, retryAfterMs: state.resetAt - now };\n }\n public updateBucket(key: string, state: BucketState): void {\n this.#buckets.set(key, state);\n // Amortised eviction: sweep expired buckets at most once per interval\n // so the map cannot grow unbounded across long-lived processes.\n const now = Date.now();\n if (now - this.#lastPruneAt >= MemoryRateLimitStore.#PRUNE_INTERVAL) {\n this.#lastPruneAt = now;\n this.prune();\n }\n }\n /** Removes buckets whose reset is in the past (or older than `maxAge` ms). */\n public prune(maxAge = 0): void {\n const cutoff = Date.now() - Math.max(0, maxAge);\n for (const [key, state] of this.#buckets) {\n if (state.resetAt <= cutoff) this.#buckets.delete(key);\n }\n }\n public getGlobalReset(): number {\n return this.#globalResetAt;\n }\n public setGlobalReset(resetAt: number): void {\n this.#globalResetAt = resetAt;\n }\n}\n",
|
|
6
|
-
"/** Why a REST request failed, for handling without matching on status codes. */\nexport type RESTErrorKind =\n /** Cancelled through an AbortSignal. */\n | \"aborted\"\n /** The attempt exceeded the transport timeout. */\n | \"timeout\"\n /** No response: DNS, TLS, connection reset. */\n | \"network\"\n /** 429 that outlived the retry budget. */\n | \"rateLimited\"\n /** 401: invalid or revoked token. */\n | \"authentication\"\n /** 403: missing access or permissions. */\n | \"permission\"\n /** 404: unknown resource. */\n | \"notFound\"\n /** 400 / 422: the request body or parameters were rejected. */\n | \"validation\"\n /** 5xx: Discord failed to handle the request. */\n | \"server\"\n /** Any other 4xx. */\n | \"client\";\n\nfunction kindFromStatus(status: number): RESTErrorKind {\n if (status === 429) return \"rateLimited\";\n if (status === 401) return \"authentication\";\n if (status === 403) return \"permission\";\n if (status === 404) return \"notFound\";\n if (status === 400 || status === 422) return \"validation\";\n if (status >= 500) return \"server\";\n if (status === 0) return \"network\";\n return \"client\";\n}\n\n/**\n * Replaces the token segment of webhook and interaction paths with `:token`,\n * so request paths can appear in errors, hooks and logs without leaking a\n * credential.\n */\nexport function redactPath(path: string): string {\n return (\n path\n // The ID segment is matched loosely: a malformed ID must not stop the\n // token that follows it from being redacted.\n .replace(/^(\\/webhooks\\/[^/?]+\\/)[^/?]+/i, \"$1:token\")\n .replace(/^(\\/interactions\\/[^/?]+\\/)[^/?]+/i, \"$1:token\")\n );\n}\n\n/** What to check when Discord refuses an action the bot attempted. */\nconst HINTS: Readonly<Record<number, string>> = {\n 50001: \"The bot cannot see this resource: check that it is in the guild and has View Channel there.\",\n 50013: \"The bot lacks a permission for this action, or the target role or member is at or above the bot's highest role.\",\n};\n\n/** Error thrown when Discord rejects a REST request. */\nexport class RESTError extends Error {\n /** Failure category. */\n public readonly kind: RESTErrorKind;\n /** HTTP status returned by Discord. */\n public readonly status: number;\n /** Discord API error code, when provided. */\n public readonly code?: number;\n /** Raw Discord validation/error payload. */\n public readonly errors?: unknown;\n /** HTTP method used for the failed request. */\n public readonly method?: string;\n /** API path used for the failed request. */\n public readonly path?: string;\n /** What to check next, for errors with a common cause (missing access or permissions). */\n public readonly hint?: string;\n /** Creates a REST error with request context. @param message Error message. @param status HTTP status. @param code Discord error code. @param errors Raw error payload. @param options Request context and cause. */\n public constructor(\n message: string,\n status: number,\n code?: number,\n errors?: unknown,\n options: {\n method?: string;\n path?: string;\n cause?: unknown;\n kind?: RESTErrorKind;\n } = {},\n ) {\n super(\n message,\n options.cause === undefined ? undefined : { cause: options.cause },\n );\n this.name = \"RESTError\";\n this.status = status;\n this.kind = options.kind ?? kindFromStatus(status);\n this.code = code;\n this.hint = code === undefined ? undefined : HINTS[code];\n this.errors = errors;\n this.method = options.method;\n this.path =\n options.path === undefined ? undefined : redactPath(options.path);\n }\n\n /** Whether sending the same request again later could succeed. Validation,\n * permission, authentication and not-found failures never will. */\n public get retryable(): boolean {\n return (\n this.kind === \"rateLimited\" ||\n this.kind === \"server\" ||\n this.kind === \"network\" ||\n this.kind === \"timeout\"\n );\n }\n}\n\n/** Creates the cancellation error shared by every stage of the request pipeline. */\nexport function abortError(path: string, reason: unknown): RESTError {\n return new RESTError(\n reason instanceof Error\n ? reason.message\n : \"Discord REST request was aborted\",\n 0,\n undefined,\n undefined,\n { path, cause: reason, kind: \"aborted\" },\n );\n}\n\n/** Waits for a delay, rejecting early if the signal aborts. */\nexport async function sleep(\n delay: number,\n signal: AbortSignal | undefined,\n path: string,\n): Promise<void> {\n if (delay <= 0) return;\n await new Promise<void>((resolve, reject) => {\n
|
|
7
|
-
"import { createHash } from \"node:crypto\";\nimport { redactPath } from \"./errors.js\";\n\n/** Query-string value accepted by a request. */\nexport type RESTQuery =\n | URLSearchParams\n | Record<string, string | number | boolean | null | undefined>\n | string;\n\n/**\n * Identity of a request as Discord's rate limiter sees it.\n *\n * `route` is the shape of the endpoint (ids replaced by `:id`), which is what\n * Discord returns a bucket hash for. `major` is the resource that gives an\n * endpoint its own independent limit. Both are needed: the hash alone would\n * make two channels share one counter.\n */\nexport interface RouteKey {\n /** Uppercased HTTP method. */\n method: string;\n /** Raw request path, without query string. */\n path: string;\n /** Normalized `METHOD:/path/:id` route used for bucket-hash lookup. */\n route: string;\n /** Major parameter scoping the limit (channel, guild, webhook id[:token]). */\n major: string;\n}\n\n/** Normalizes Discord routes for stable bucket discovery. */\nexport function normalizeRoutePath(path: string): string {\n const queryIndex = path.indexOf(\"?\");\n const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);\n // Tokens never enter a route key: keys are stored (Redis keeps them for days).\n return redactPath(routePath).replace(/\\/\\d+(?=\\/|$)/g, \"/:id\");\n}\n\n/**\n * Discord's *major parameters* — the resource ids that give an endpoint its own\n * independent rate limit. Per the API documentation these are `channel_id`,\n * `guild_id` and `webhook_id`; webhook routes are additionally scoped by\n * their token. Any other id in a path shares its limit with sibling resources.\n */\nexport function majorParameter(path: string): string {\n const match = /^\\/(channels|guilds|webhooks)\\/(\\d+)(?:\\/([^/?]+))?/i.exec(\n path,\n );\n if (!match) return \"@none\";\n // A webhook's limit is keyed by its id and its token. The token is hashed\n // so the key (stored, possibly shared through Redis) never holds it.\n if (match[1]!.toLowerCase() === \"webhooks\" && match[3] !== undefined)\n return `${match[2]}:${tokenDigest(match[3])}`;\n return match[2]!;\n}\n\n/** A short, stable digest of a token, identical in every process. */\nfunction tokenDigest(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\").slice(0, 16);\n}\n\n/** Combines a bucket hash (or route) with a major parameter into a bucket key. */\nexport function scopeBucket(hashOrRoute: string, major: string): string {\n return `${hashOrRoute}|${major}`;\n}\n\n/** Builds the routing identity for one request. */\nexport function createRouteKey(method: string, path: string): RouteKey {\n const normalizedMethod = method.toUpperCase();\n return {\n method: normalizedMethod,\n path,\n route: `${normalizedMethod}:${normalizeRoutePath(path)}`,\n major: majorParameter(path),\n };\n}\n\n/** Serialises a query into a `?...` suffix (empty string when nothing to add). */\nexport function serializeQuery(query: RESTQuery | undefined): string {\n if (query === undefined) return \"\";\n if (typeof query === \"string\")\n return query.length === 0 || query.startsWith(\"?\")\n ? query\n : `?${query}`;\n const params =\n query instanceof URLSearchParams\n ? query\n : new URLSearchParams(\n Object.entries(query).flatMap(([key, value]) =>\n value === undefined || value === null\n ? []\n : [[key, String(value)] as [string, string]],\n ),\n );\n const serialized = params.toString();\n return serialized.length === 0 ? \"\" : `?${serialized}`;\n}\n",
|
|
8
|
-
"import { abortable } from \"./errors.js\";\n\n/**\n * Serialises work per rate-limit bucket.\n *\n * One request per bucket key is in flight at a time, so each request sees the\n * limit state its predecessor's response wrote. The queue slot is released on\n * every exit path — including an abort raised while still waiting — because a\n * slot that is never released wedges its bucket for the process lifetime.\n */\nexport class RequestScheduler {\n /** Maximum times a request may be moved to a different bucket queue. */\n public static readonly MAX_REMAPS = 2;\n readonly #queues = new Map<string, Promise<void>>();\n\n /** Number of bucket keys currently holding a queue slot. */\n public get size(): number {\n return this.#queues.size;\n }\n\n /**\n * Runs `task` under the bucket key that `resolveKey` reports *at the moment\n * the slot is acquired*, not at enqueue time.\n *\n * Discord can map several routes onto one bucket hash, and a route only\n * learns its hash from a response. A request that queued under its\n * route-derived key while another request discovered the shared hash would\n * otherwise run concurrently with that bucket's other traffic. Re-resolving\n * after the wait moves it onto the right queue instead.\n *\n * @param resolveKey Returns the current bucket key for this request.\n * @param signal Cancellation signal honoured while queued.\n * @param path Request path, used for cancellation error context.\n * @param task Work to run while holding the slot, given the final key and\n * a callback that frees the slot early.\n */\n public async runResolved<T>(\n resolveKey: () => Promise<string>,\n signal: AbortSignal | undefined,\n path: string,\n task: (key: string, release: () => void) => Promise<T>,\n ): Promise<T> {\n let key = await resolveKey();\n // A remap can only chain as far as route key -> shared hash, so a small\n // bound is enough; the final hop runs whatever key it holds.\n for (let hop = 0; hop < RequestScheduler.MAX_REMAPS; hop++) {\n const outcome = await this.run<\n { done: true; value: T } | { done: false; key: string }\n >(key, signal, path, async (release) => {\n const current = await resolveKey();\n if (current !== key) return { done: false, key: current };\n return { done: true, value: await task(key, release) };\n });\n if (outcome.done) return outcome.value;\n key = outcome.key;\n }\n return this.run(key, signal, path, (release) => task(key, release));\n }\n\n /**\n * Runs `task` once every earlier task for `key` has settled, or has freed\n * its slot early through the `release` callback it was given.\n * @param key Bucket key to serialise on.\n * @param signal Cancellation signal honoured while queued.\n * @param path Request path, used for cancellation error context.\n * @param task Work to run while holding the slot, given an idempotent\n * callback that frees the slot before the task settles.\n */\n public async run<T>(\n key: string,\n signal: AbortSignal | undefined,\n path: string,\n task: (release: () => void) => Promise<T>,\n ): Promise<T> {\n const previous = this.#queues.get(key)
|
|
9
|
-
"
|
|
10
|
-
"import { RESTError, abortError } from \"./errors.js\";\n\n/** Minimal fetch shape the transport depends on, so a stub needs no extras. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\n/** One HTTP attempt, fully resolved: no routing, retry or rate-limit concerns. */\nexport interface TransportRequest {\n /** Uppercased HTTP method. */\n method: string;\n /** Path with query string, relative to the transport's base URL. */\n path: string;\n /** Request headers, already including authorization. */\n headers: Record<string, string>;\n /** Encoded body, or undefined for a bodyless request. */\n body?: BodyInit;\n /** Caller cancellation signal. */\n signal?: AbortSignal;\n}\n\n/** Thrown by {@link HttpTransport} when the attempt never produced a response. */\nexport class TransportError extends Error {\n /** Whether the attempt was cut short by the transport timeout or an abort. */\n public readonly timedOut: boolean;\n public constructor(message: string, timedOut: boolean, cause?: unknown) {\n super(message, cause === undefined ? undefined : { cause });\n this.name = \"TransportError\";\n this.timedOut = timedOut;\n }\n}\n\n/**\n * Sends one HTTP attempt and returns the raw {@link Response}.\n *\n * Everything above it — routing, rate limiting, retries, decoding — is somebody\n * else's job, which is what makes the layer substitutable in tests.\n */\nexport class HttpTransport {\n readonly #baseURL: string;\n readonly #timeout: number;\n readonly #fetch: FetchLike;\n\n public constructor(\n options: {\n baseURL?: string;\n timeout?: number;\n fetch?: FetchLike;\n } = {},\n ) {\n this.#baseURL = (\n options.baseURL ?? \"https://discord.com/api/v10\"\n ).replace(/\\/$/, \"\");\n this.#timeout = Math.max(1, options.timeout ?? 15_000);\n this.#fetch =\n options.fetch ?? ((input, init) => globalThis.fetch(input, init));\n }\n\n /** Base URL every request path is resolved against. */\n public get baseURL(): string {\n return this.#baseURL;\n }\n /** Per-attempt timeout in milliseconds. */\n public get timeout(): number {\n return this.#timeout;\n }\n\n /**\n * Performs the attempt.\n * @throws {RESTError} If the caller's signal was already aborted.\n * @throws {TransportError} If the attempt times out or the transport fails.\n */\n public async send(request: TransportRequest): Promise<Response> {\n if (request.signal?.aborted)\n throw abortError(request.path, request.signal.reason);\n\n const controller = new AbortController();\n const onAbort = (): void => controller.abort(request.signal?.reason);\n request.signal?.addEventListener(\"abort\", onAbort, { once: true });\n const timer = setTimeout(\n () =>\n controller.abort(\n new DOMException(\"REST request timeout\", \"TimeoutError\"),\n ),\n this.#timeout,\n );\n try {\n return await this.#fetch(`${this.#baseURL}${request.path}`, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: controller.signal,\n });\n } catch (error) {\n if (error instanceof RESTError) throw error;\n const timedOut =\n error instanceof DOMException &&\n (error.name === \"TimeoutError\" || error.name === \"AbortError\");\n throw new TransportError(\n timedOut\n ? `Discord REST request timed out after ${this.#timeout}ms`\n : \"Discord REST request failed\",\n timedOut,\n error,\n );\n } finally {\n clearTimeout(timer);\n request.signal?.removeEventListener(\"abort\", onAbort);\n }\n }\n}\n",
|
|
11
|
-
"/** Normalised shape of a Discord error payload. */\nexport interface DecodedError {\n message?: string;\n code?: number;\n retry_after?: number;\n global?: boolean;\n}\n\n/** Turns a raw {@link Response} into a payload and the error metadata retries need. */\nexport class ResponseDecoder {\n /** Reads a Discord response payload as JSON, text, or nothing for 204. */\n public async read(response: Response): Promise<unknown> {\n if (response.status === 204) return undefined;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\"))\n return response.json().catch(() => undefined);\n return response.text().catch(() => undefined);\n }\n\n /** Extracts Discord's error metadata from a decoded payload. */\n public errorData(payload: unknown): DecodedError {\n if (!payload || typeof payload !== \"object\") return {};\n const data = payload as Record<string, unknown>;\n return {\n message:\n typeof data.message === \"string\" ? data.message : undefined,\n code: typeof data.code === \"number\" ? data.code : undefined,\n retry_after:\n typeof data.retry_after === \"number\"\n ? data.retry_after\n : undefined,\n global: data.global === true,\n };\n }\n\n /**\n * Resolves Retry-After (seconds) from the payload, falling back to the\n * header and finally to one second.\n *\n * An absent header is explicitly *not* a zero delay: `Number(null)` is 0,\n * which is finite, so reading it directly would answer a 429 that carries\n * no Retry-After by retrying immediately — exactly when backing off matters.\n */\n public retryAfter(response: Response, data: DecodedError): number {\n const raw = response.headers.get(\"Retry-After\");\n const header = raw === null ? Number.NaN : Number(raw);\n const retryAfter =\n data.retry_after ?? (Number.isFinite(header) ? header : 1);\n return Math.max(0, retryAfter);\n }\n}\n",
|
|
5
|
+
"/**\n * A rate-limit bucket as stored: units `remaining` in the window and when it\n * resets (`resetAt`, ms since the epoch, in the store's clock).\n */\nexport interface BucketState {\n remaining: number;\n resetAt: number;\n /** The bucket's limit per window, once a response has told us. */\n limit?: number;\n /** `X-RateLimit-Reset` of the window this state describes, in epoch ms. */\n window?: number;\n /**\n * `resetAt` is an estimate, not a server value: the window was opened by a\n * reservation after the last one elapsed. A server reset replaces it.\n */\n provisional?: boolean;\n /**\n * Placeholder for a bucket nobody knows yet: one request (the probe) is\n * on its way to discover the limit, and everyone else waits for it.\n */\n probe?: boolean;\n}\n\n/**\n * Outcome of an atomic reservation.\n *\n * `granted` means one unit of the bucket's allowance now belongs to the caller\n * and to nobody else — the distinction that a read-then-write cannot make when\n * several workers share a store. `known` says whether the bucket's limit is\n * known (so more requests may follow concurrently) or this request is the\n * probe that discovers it; `resetAt` identifies the window the unit came from.\n * A refusal says how long to wait and why.\n */\nexport type Reservation =\n | { granted: true; known?: boolean; resetAt?: number }\n | {\n granted: false;\n retryAfterMs: number;\n reason?: \"bucket\" | \"global\" | \"probe\";\n };\n\n/** Options for {@link RateLimitStore.reserve}. */\nexport interface ReserveOptions {\n /** Global-limit scope to check in the same atomic step, if any. */\n global?: string;\n /** How long a probe may hold an unknown bucket before others may try (ms). */\n probeMs?: number;\n /**\n * Most requests per second the `global` scope may send, counted in the\n * same atomic step (Discord's global limit, 50 for most bots). Omitted or\n * 0: only a recorded global 429 is enforced.\n */\n globalLimit?: number;\n}\n\n/**\n * Span of the proactive global limit's sliding log (ms): at most the limit is\n * sent in any span this long. A second plus a margin, so latency jitter\n * cannot squeeze more than the limit into one second as Discord counts it.\n */\nexport const GLOBAL_WINDOW_MS = 1050;\n\n/**\n * Takes one slot from a sliding log of send times, or says how long until\n * one frees. `log` holds the send times still inside the window, oldest first.\n */\nexport function takeGlobalSlot(\n log: number[],\n now: number,\n limit: number,\n window = GLOBAL_WINDOW_MS,\n): number {\n while (log.length > 0 && log[0]! <= now - window) log.shift();\n if (log.length >= limit) return Math.max(1, log[0]! + window - now + 1);\n log.push(now);\n return 0;\n}\n\n/** What a response said about a bucket, for {@link RateLimitStore.reconcile}. */\nexport interface BucketUpdate {\n /** `X-RateLimit-Remaining`. */\n remaining?: number;\n /** `X-RateLimit-Reset-After`, in ms from now. */\n resetAfterMs?: number;\n /** `X-RateLimit-Limit`. */\n limit?: number;\n /** `X-RateLimit-Reset`, epoch ms: identifies the window. */\n window?: number;\n /** A bucket 429's wait, in ms: the bucket is spent until then. */\n retryAfterMs?: number;\n}\n\n/** Interface for distributed or local rate limit synchronization. */\nexport interface RateLimitStore {\n /** Gets the server bucket hash for a normalized route. */\n getBucketHash(\n route: string,\n ): Promise<string | undefined> | string | undefined;\n /** Associates a route with a server bucket hash. */\n setBucketHash(route: string, hash: string): Promise<void> | void;\n\n /** Gets the current state for a bucket key. */\n getBucket(\n key: string,\n ): Promise<BucketState | undefined> | BucketState | undefined;\n /** Replaces the state for a bucket key. */\n updateBucket(key: string, state: BucketState): Promise<void> | void;\n\n /** Gets the global reset timestamp (of `scope`, when the store keeps several). */\n getGlobalReset(scope?: string): Promise<number> | number;\n /** Sets the global reset timestamp (of `scope`, when the store keeps several). */\n setGlobalReset(resetAt: number, scope?: string): Promise<void> | void;\n\n /**\n * Atomically consumes one unit of a bucket's allowance.\n *\n * Optional: a store that cannot do this atomically must omit it, and the\n * limiter falls back to waiting on observed state. Implementations must\n * never refuse forever: an unknown bucket is discovered by sending, so\n * one caller (the probe) must be granted.\n */\n reserve?(\n key: string,\n options?: ReserveOptions,\n ): Promise<Reservation> | Reservation;\n\n /**\n * Whether {@link reserve} also checks `options.global` atomically. When\n * it does not, the limiter reads the global reset itself first.\n */\n readonly reservesGlobal?: boolean;\n\n /**\n * Atomically merges what a response said into a bucket, so a late or\n * out-of-order response cannot hand back allowance already reserved.\n * Optional: without it the limiter reads, merges and writes.\n */\n reconcile?(key: string, update: BucketUpdate): Promise<void> | void;\n\n /**\n * Atomically extends a global limit to at least `waitMs` from now, never\n * shortening a longer one. Optional: without it the limiter reads and\n * writes, which a concurrent writer can shorten.\n */\n extendGlobalReset?(scope: string, waitMs: number): Promise<void> | void;\n\n /**\n * Gives back what a granted reservation held, when that is certain to be\n * safe: a probe placeholder is cleared (so the next request may probe),\n * and with `refund` — the request was never sent — the unit is returned\n * to the window it came from, if that window is still current.\n */\n release?(\n key: string,\n grant: { resetAt?: number },\n refund: boolean,\n ): Promise<void> | void;\n\n /**\n * Evicts expired bucket entries. Optional: stores with native key\n * expiry (e.g. Redis) can omit it. `maxAge` (ms) additionally drops\n * buckets whose reset is older than that window; defaults to 0 (only\n * already-reset buckets are removed).\n */\n prune?(maxAge?: number): Promise<void> | void;\n}\n\n/** Longest a refused waiter sleeps on a placeholder before asking again (ms). */\nexport const PROBE_POLL_MS = 50;\n/** Default time a probe may hold an unknown bucket (ms). */\nexport const DEFAULT_PROBE_MS = 16_000;\n/** How long a bucket's limit is remembered after its window resets (ms). */\nexport const BUCKET_LINGER_MS = 60_000;\n/** Reset values within this many ms of each other name the same window. */\nconst WINDOW_TOLERANCE_MS = 50;\n\n/**\n * The reservation decision, shared by every built-in store (the Redis store\n * runs a Lua port of it). Returns the new state to write, if any.\n */\nexport function reserveState(\n state: BucketState | undefined,\n now: number,\n probeMs: number,\n): { reservation: Reservation; next?: BucketState } {\n if (state && state.resetAt > now) {\n if (state.remaining > 0)\n return {\n reservation: {\n granted: true,\n known: true,\n resetAt: state.resetAt,\n },\n next: { ...state, remaining: state.remaining - 1 },\n };\n const wait = state.resetAt - now;\n return {\n reservation: {\n granted: false,\n // An estimate is replaced as soon as a response lands, so ask\n // again soon rather than sleeping to the estimate.\n retryAfterMs: state.provisional\n ? Math.min(wait, PROBE_POLL_MS)\n : wait,\n reason: state.probe ? \"probe\" : \"bucket\",\n },\n };\n }\n const resetAt = now + probeMs;\n // The window elapsed and its limit is known: open the next one here, so\n // a fleet waking at the reset cannot send more than the limit at once.\n if (state?.limit !== undefined && state.limit > 0)\n return {\n reservation: { granted: true, known: true, resetAt },\n next: {\n remaining: state.limit - 1,\n resetAt,\n limit: state.limit,\n provisional: true,\n },\n };\n // Unknown: this caller probes, everyone else waits for its response.\n return {\n reservation: { granted: true, known: false, resetAt },\n next: { remaining: 0, resetAt, provisional: true, probe: true },\n };\n}\n\n/**\n * The merge of a response into a bucket, shared by every built-in store.\n *\n * Within a window `remaining` only ever goes down: the stored value already\n * counts reservations still in flight, the header counts what the server\n * has seen, and the lower of the two is the one that cannot over-send. A\n * response from an older window than the one stored is ignored. An estimated\n * reset is replaced by the server's.\n *\n * Returns the state to write, `null` to delete, or `undefined` for no change.\n */\nexport function reconcileState(\n state: BucketState | undefined,\n update: BucketUpdate,\n now: number,\n): BucketState | null | undefined {\n const incoming =\n update.resetAfterMs === undefined\n ? undefined\n : Math.ceil(now + update.resetAfterMs);\n const active = !!state && state.resetAt > now && !state.probe;\n let next: BucketState;\n if (active) {\n next = { ...state! };\n const stale =\n update.window !== undefined &&\n next.window !== undefined &&\n update.window < next.window - WINDOW_TOLERANCE_MS;\n if (!stale) {\n if (update.remaining !== undefined)\n next.remaining = Math.min(next.remaining, update.remaining);\n if (incoming !== undefined) {\n const newer =\n update.window !== undefined &&\n next.window !== undefined &&\n update.window > next.window + WINDOW_TOLERANCE_MS;\n next.resetAt =\n next.provisional || newer\n ? incoming\n : Math.max(next.resetAt, incoming);\n delete next.provisional;\n }\n if (update.window !== undefined)\n next.window = Math.max(next.window ?? 0, update.window);\n }\n } else {\n if (\n update.remaining === undefined &&\n update.retryAfterMs === undefined\n ) {\n // Nothing learned. A probe placeholder must not outlive its\n // request, or everyone else waits out its whole lifetime.\n return state?.probe ? null : undefined;\n }\n next = {\n remaining: update.remaining ?? 0,\n resetAt: incoming ?? now,\n };\n if (state?.limit !== undefined) next.limit = state.limit;\n if (update.window !== undefined) next.window = update.window;\n }\n if (update.limit !== undefined && update.limit > 0)\n next.limit = update.limit;\n if (update.retryAfterMs !== undefined) {\n next.remaining = 0;\n next.resetAt = Math.max(\n next.resetAt,\n Math.ceil(now + update.retryAfterMs),\n );\n delete next.provisional;\n }\n return next;\n}\n\n/** Local memory implementation of RateLimitStore. */\nexport class MemoryRateLimitStore implements RateLimitStore {\n readonly #buckets = new Map<string, BucketState>();\n readonly #routeBuckets = new Map<string, string>();\n readonly #globals = new Map<string, number>();\n readonly #globalLogs = new Map<string, number[]>();\n /** Minimum interval between automatic prunes, in ms. */\n static readonly #PRUNE_INTERVAL = 60_000;\n #lastPruneAt = 0;\n /** Most entries either map keeps; the oldest are dropped first. */\n static readonly #MAX_ENTRIES = 10_000;\n public readonly reservesGlobal = true;\n\n /** Creates an empty instance. */\n public constructor() {}\n\n static #evict(map: Map<string, unknown>): void {\n while (map.size > MemoryRateLimitStore.#MAX_ENTRIES)\n map.delete(map.keys().next().value as string);\n }\n\n public getBucketHash(route: string): string | undefined {\n return this.#routeBuckets.get(route);\n }\n public setBucketHash(route: string, hash: string): void {\n this.#routeBuckets.delete(route);\n this.#routeBuckets.set(route, hash);\n MemoryRateLimitStore.#evict(this.#routeBuckets);\n }\n public getBucket(key: string): BucketState | undefined {\n return this.#buckets.get(key);\n }\n /**\n * Consumes one unit of the bucket's allowance.\n *\n * Single-process reservation is trivially atomic: JavaScript will not\n * interleave this method with another caller's copy of it.\n */\n public reserve(key: string, options: ReserveOptions = {}): Reservation {\n const now = Date.now();\n if (options.global !== undefined) {\n const globalWait = this.getGlobalReset(options.global) - now;\n if (globalWait > 0)\n return {\n granted: false,\n retryAfterMs: globalWait,\n reason: \"global\",\n };\n }\n const limit =\n options.global === undefined ? 0 : (options.globalLimit ?? 0);\n let log: number[] | undefined;\n if (limit > 0) {\n log = this.#globalLogs.get(options.global!);\n if (!log) {\n this.#globalLogs.set(options.global!, (log = []));\n MemoryRateLimitStore.#evict(this.#globalLogs);\n }\n // Check without taking: the slot is taken only with the bucket's.\n const wait = takeGlobalSlot(log, now, limit);\n if (wait > 0)\n return { granted: false, retryAfterMs: wait, reason: \"global\" };\n log.pop();\n }\n const { reservation, next } = reserveState(\n this.#buckets.get(key),\n now,\n options.probeMs ?? DEFAULT_PROBE_MS,\n );\n // Record the send first: the write below may prune, and a log still\n // empty then would be dropped while this call holds it.\n if (log && reservation.granted) log.push(now);\n if (next) this.#write(key, next);\n return reservation;\n }\n public reconcile(key: string, update: BucketUpdate): void {\n const next = reconcileState(this.#buckets.get(key), update, Date.now());\n if (next === null) this.#buckets.delete(key);\n else if (next) this.#write(key, next);\n }\n public release(\n key: string,\n grant: { resetAt?: number },\n refund: boolean,\n ): void {\n const state = this.#buckets.get(key);\n if (!state || state.resetAt !== grant.resetAt) return;\n if (state.probe) this.#buckets.delete(key);\n else if (refund)\n state.remaining = Math.min(\n state.remaining + 1,\n state.limit ?? Number.POSITIVE_INFINITY,\n );\n }\n public updateBucket(key: string, state: BucketState): void {\n this.#write(key, state);\n }\n #write(key: string, state: BucketState): void {\n this.#buckets.delete(key);\n this.#buckets.set(key, state);\n MemoryRateLimitStore.#evict(this.#buckets);\n // Amortised eviction: sweep expired buckets at most once per interval\n // so the map cannot grow unbounded across long-lived processes. A\n // bucket's limit is kept a while past its reset, so the next window\n // opens at the known limit instead of probing again.\n const now = Date.now();\n if (now - this.#lastPruneAt >= MemoryRateLimitStore.#PRUNE_INTERVAL) {\n this.#lastPruneAt = now;\n this.prune(BUCKET_LINGER_MS);\n }\n }\n /** Removes buckets whose reset is in the past (or older than `maxAge` ms). */\n public prune(maxAge = 0): void {\n const cutoff = Date.now() - Math.max(0, maxAge);\n for (const [key, state] of this.#buckets) {\n if (state.resetAt <= cutoff) this.#buckets.delete(key);\n }\n for (const [scope, resetAt] of this.#globals)\n if (resetAt <= Date.now()) this.#globals.delete(scope);\n for (const [scope, log] of this.#globalLogs)\n if ((log.at(-1) ?? 0) <= Date.now() - GLOBAL_WINDOW_MS)\n this.#globalLogs.delete(scope);\n }\n public getGlobalReset(scope = \"\"): number {\n return this.#globals.get(scope) ?? 0;\n }\n public setGlobalReset(resetAt: number, scope = \"\"): void {\n this.#globals.set(scope, resetAt);\n MemoryRateLimitStore.#evict(this.#globals);\n }\n public extendGlobalReset(scope: string, waitMs: number): void {\n const resetAt = Date.now() + Math.max(0, waitMs);\n if (resetAt > this.getGlobalReset(scope))\n this.setGlobalReset(resetAt, scope);\n }\n /** Number of buckets held (for diagnostics and leak checks). */\n public get size(): number {\n return this.#buckets.size;\n }\n}\n",
|
|
6
|
+
"/** Why a REST request failed, for handling without matching on status codes. */\nexport type RESTErrorKind =\n /** Cancelled through an AbortSignal. */\n | \"aborted\"\n /** The attempt exceeded the transport timeout. */\n | \"timeout\"\n /** No response: DNS, TLS, connection reset. */\n | \"network\"\n /** 429 that outlived the retry budget. */\n | \"rateLimited\"\n /** 401: invalid or revoked token. */\n | \"authentication\"\n /** 403: missing access or permissions. */\n | \"permission\"\n /** 404: unknown resource. */\n | \"notFound\"\n /** 400 / 422: the request body or parameters were rejected. */\n | \"validation\"\n /** 5xx: Discord failed to handle the request. */\n | \"server\"\n /** Any other 4xx. */\n | \"client\"\n /** The rate-limit store failed or refused to coordinate; the request was not sent. */\n | \"coordination\"\n /** A local queue limit was reached; the request was not sent. */\n | \"overloaded\";\n\nfunction kindFromStatus(status: number): RESTErrorKind {\n if (status === 429) return \"rateLimited\";\n if (status === 401) return \"authentication\";\n if (status === 403) return \"permission\";\n if (status === 404) return \"notFound\";\n if (status === 400 || status === 422) return \"validation\";\n if (status >= 500) return \"server\";\n if (status === 0) return \"network\";\n return \"client\";\n}\n\n/**\n * Replaces the token segment of webhook and interaction paths with `:token`,\n * so request paths can appear in errors, hooks and logs without leaking a\n * credential.\n */\nexport function redactPath(path: string): string {\n return (\n path\n // The ID segment is matched loosely: a malformed ID must not stop the\n // token that follows it from being redacted.\n .replace(/^(\\/webhooks\\/[^/?]+\\/)[^/?]+/i, \"$1:token\")\n .replace(/^(\\/interactions\\/[^/?]+\\/)[^/?]+/i, \"$1:token\")\n );\n}\n\n/** Webhook and interaction tokens inside any text, such as the URL in a network error's message. */\nconst TOKEN_IN_TEXT =\n /(\\/(?:webhooks|interactions)\\/[^/\\s?\"'`]+\\/)[^/\\s?\"'`]+/gi;\n\n/** Hides webhook and interaction tokens in free text. */\nexport function redactText(text: string): string {\n return text.replace(TOKEN_IN_TEXT, \"$1:token\");\n}\n\n/**\n * A copy of a lower-level error that is safe to attach as a `cause`: the same\n * name and code, with tokens removed from the message and stack. Runtimes put\n * the request URL in network error messages, and the URL holds webhook tokens.\n */\nexport function sanitizeCause(error: unknown): unknown {\n if (!(error instanceof Error)) return undefined;\n const copy = new Error(redactText(error.message));\n copy.name = error.name;\n if (error.stack) copy.stack = redactText(error.stack);\n for (const key of [\"code\", \"errno\", \"syscall\"] as const) {\n const value = (error as unknown as Record<string, unknown>)[key];\n if (typeof value === \"string\" || typeof value === \"number\")\n (copy as unknown as Record<string, unknown>)[key] = value;\n }\n return copy;\n}\n\n/** What to check when Discord refuses an action the bot attempted. */\nconst HINTS: Readonly<Record<number, string>> = {\n 50001: \"The bot cannot see this resource: check that it is in the guild and has View Channel there.\",\n 50013: \"The bot lacks a permission for this action, or the target role or member is at or above the bot's highest role.\",\n};\n\n/** Error thrown when Discord rejects a REST request. */\nexport class RESTError extends Error {\n /** Failure category. */\n public readonly kind: RESTErrorKind;\n /** HTTP status returned by Discord. */\n public readonly status: number;\n /** Discord API error code, when provided. */\n public readonly code?: number;\n /** Raw Discord validation/error payload. */\n public readonly errors?: unknown;\n /** HTTP method used for the failed request. */\n public readonly method?: string;\n /** API path used for the failed request. */\n public readonly path?: string;\n /** What to check next, for errors with a common cause (missing access or permissions). */\n public readonly hint?: string;\n /** Creates a REST error with request context. @param message Error message. @param status HTTP status. @param code Discord error code. @param errors Raw error payload. @param options Request context and cause. */\n public constructor(\n message: string,\n status: number,\n code?: number,\n errors?: unknown,\n options: {\n method?: string;\n path?: string;\n cause?: unknown;\n kind?: RESTErrorKind;\n } = {},\n ) {\n super(\n message,\n options.cause === undefined ? undefined : { cause: options.cause },\n );\n this.name = \"RESTError\";\n this.status = status;\n this.kind = options.kind ?? kindFromStatus(status);\n this.code = code;\n this.hint = code === undefined ? undefined : HINTS[code];\n this.errors = errors;\n this.method = options.method;\n this.path =\n options.path === undefined ? undefined : redactPath(options.path);\n }\n\n /** Whether sending the same request again later could succeed. Validation,\n * permission, authentication and not-found failures never will. */\n public get retryable(): boolean {\n return (\n this.kind === \"rateLimited\" ||\n this.kind === \"server\" ||\n this.kind === \"network\" ||\n this.kind === \"timeout\" ||\n this.kind === \"coordination\" ||\n this.kind === \"overloaded\"\n );\n }\n}\n\n/** Creates the cancellation error shared by every stage of the request pipeline. */\nexport function abortError(path: string, reason: unknown): RESTError {\n return new RESTError(\n reason instanceof Error\n ? reason.message\n : \"Discord REST request was aborted\",\n 0,\n undefined,\n undefined,\n { path, cause: reason, kind: \"aborted\" },\n );\n}\n\n/** Waits for a delay, rejecting early if the signal aborts. */\n/** The longest delay `setTimeout` honours. */\nconst MAX_TIMER_MS = 2_147_483_647;\n\nexport async function sleep(\n delay: number,\n signal: AbortSignal | undefined,\n path: string,\n): Promise<void> {\n if (delay <= 0) return;\n // setTimeout runs a delay beyond 2^31 - 1 ms at once; that would turn a\n // long wait into none.\n delay = Math.min(delay, MAX_TIMER_MS);\n await new Promise<void>((resolve, reject) => {\n // Every way out clears the timer and removes the listener, so a signal\n // shared by many requests does not collect one listener per wait.\n const finish = (error?: Error): void => {\n clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onAbort);\n if (error) reject(error);\n else resolve();\n };\n const onAbort = (): void => finish(abortError(path, signal?.reason));\n const timer = setTimeout(finish, delay);\n if (signal?.aborted) return onAbort();\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/** Awaits a promise, rejecting early if the signal aborts. */\nexport async function abortable(\n promise: Promise<void>,\n signal: AbortSignal | undefined,\n path: string,\n): Promise<void> {\n if (!signal) return promise;\n if (signal.aborted) throw abortError(path, signal.reason);\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => reject(abortError(path, signal.reason));\n signal.addEventListener(\"abort\", onAbort, { once: true });\n // Whichever settles first, the listener goes.\n promise.then(\n () => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n },\n (error: unknown) => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(error);\n },\n );\n });\n}\n",
|
|
7
|
+
"import { createHash } from \"node:crypto\";\nimport { redactPath } from \"./errors.js\";\n\n/** Query-string value accepted by a request. */\nexport type RESTQuery =\n | URLSearchParams\n | Record<string, string | number | boolean | null | undefined>\n | string;\n\n/**\n * Identity of a request as Discord's rate limiter sees it.\n *\n * `route` is the shape of the endpoint (ids replaced by `:id`), which is what\n * Discord returns a bucket hash for. `major` is the resource that gives an\n * endpoint its own independent limit. Both are needed: the hash alone would\n * make two channels share one counter.\n */\nexport interface RouteKey {\n /** Uppercased HTTP method. */\n method: string;\n /** Raw request path, without query string. */\n path: string;\n /** Normalized `METHOD:/path/:id` route used for bucket-hash lookup. */\n route: string;\n /** Major parameter scoping the limit (channel, guild, webhook id[:token]). */\n major: string;\n}\n\n/** Normalizes Discord routes for stable bucket discovery. */\nexport function normalizeRoutePath(path: string): string {\n const queryIndex = path.indexOf(\"?\");\n const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);\n // Tokens never enter a route key: keys are stored (Redis keeps them for days).\n // A reaction's emoji is free text: one key per emoji would grow without\n // bound, and Discord limits reactions per channel, not per emoji.\n return redactPath(routePath)\n .replace(/\\/\\d+(?=\\/|$)/g, \"/:id\")\n .replace(/\\/reactions\\/[^/]+/i, \"/reactions/:emoji\");\n}\n\n/**\n * Discord's *major parameters* — the resource ids that give an endpoint its own\n * independent rate limit. Per the API documentation these are `channel_id`,\n * `guild_id` and `webhook_id`; webhook routes are additionally scoped by\n * their token. Any other id in a path shares its limit with sibling resources.\n */\nexport function majorParameter(path: string): string {\n const match = /^\\/(channels|guilds|webhooks)\\/(\\d+)(?:\\/([^/?]+))?/i.exec(\n path,\n );\n if (!match) return \"@none\";\n // A webhook's limit is keyed by its id and its token. The token is hashed\n // so the key (stored, possibly shared through Redis) never holds it.\n if (match[1]!.toLowerCase() === \"webhooks\" && match[3] !== undefined)\n return `${match[2]}:${tokenDigest(match[3])}`;\n return match[2]!;\n}\n\n/** A short, stable digest of a token, identical in every process. */\nfunction tokenDigest(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\").slice(0, 16);\n}\n\n/** Combines a bucket hash (or route) with a major parameter into a bucket key. */\nexport function scopeBucket(hashOrRoute: string, major: string): string {\n return `${hashOrRoute}|${major}`;\n}\n\n/** Builds the routing identity for one request. */\nexport function createRouteKey(method: string, path: string): RouteKey {\n const normalizedMethod = method.toUpperCase();\n return {\n method: normalizedMethod,\n path,\n route: `${normalizedMethod}:${normalizeRoutePath(path)}`,\n major: majorParameter(path),\n };\n}\n\n/** Serialises a query into a `?...` suffix (empty string when nothing to add). */\nexport function serializeQuery(query: RESTQuery | undefined): string {\n if (query === undefined) return \"\";\n if (typeof query === \"string\")\n return query.length === 0 || query.startsWith(\"?\")\n ? query\n : `?${query}`;\n const params =\n query instanceof URLSearchParams\n ? query\n : new URLSearchParams(\n Object.entries(query).flatMap(([key, value]) =>\n value === undefined || value === null\n ? []\n : [[key, String(value)] as [string, string]],\n ),\n );\n const serialized = params.toString();\n return serialized.length === 0 ? \"\" : `?${serialized}`;\n}\n",
|
|
8
|
+
"import { RESTError, abortable } from \"./errors.js\";\n\n/** Limits for {@link RequestScheduler}. */\nexport interface SchedulerOptions {\n /** Most requests waiting for a slot across all buckets. Default unlimited. */\n maxQueued?: number;\n /** Most requests waiting for a slot on one bucket. Default unlimited. */\n maxQueuedPerBucket?: number;\n}\n\n/**\n * Serialises work per rate-limit bucket.\n *\n * One request per bucket key is in flight at a time, so each request sees the\n * limit state its predecessor's response wrote. The queue slot is released on\n * every exit path — including an abort raised while still waiting — because a\n * slot that is never released wedges its bucket for the process lifetime.\n * Buckets queue independently: a slow bucket never holds up another.\n */\nexport class RequestScheduler {\n /** Maximum times a request may be moved to a different bucket queue. */\n public static readonly MAX_REMAPS = 2;\n readonly #queues = new Map<string, Promise<void>>();\n readonly #waiting = new Map<string, number>();\n #queued = 0;\n readonly #maxQueued: number;\n readonly #maxQueuedPerBucket: number;\n\n /** Creates an empty scheduler. @param options Queue limits. */\n public constructor(options: SchedulerOptions = {}) {\n this.#maxQueued = options.maxQueued ?? Number.POSITIVE_INFINITY;\n this.#maxQueuedPerBucket =\n options.maxQueuedPerBucket ?? Number.POSITIVE_INFINITY;\n if (!(this.#maxQueued >= 0) || !(this.#maxQueuedPerBucket >= 0))\n throw new RangeError(\"Queue limits must be non-negative numbers.\");\n }\n\n /** Number of bucket keys currently holding a queue slot. */\n public get size(): number {\n return this.#queues.size;\n }\n\n /** Number of requests waiting for a slot. */\n public get queued(): number {\n return this.#queued;\n }\n\n /**\n * Runs `task` under the bucket key that `resolveKey` reports *at the moment\n * the slot is acquired*, not at enqueue time.\n *\n * Discord can map several routes onto one bucket hash, and a route only\n * learns its hash from a response. A request that queued under its\n * route-derived key while another request discovered the shared hash would\n * otherwise run concurrently with that bucket's other traffic. Re-resolving\n * after the wait moves it onto the right queue instead.\n *\n * @param resolveKey Returns the current bucket key for this request.\n * @param signal Cancellation signal honoured while queued.\n * @param path Request path, used for cancellation error context.\n * @param task Work to run while holding the slot, given the final key and\n * a callback that frees the slot early.\n */\n public async runResolved<T>(\n resolveKey: () => Promise<string>,\n signal: AbortSignal | undefined,\n path: string,\n task: (key: string, release: () => void) => Promise<T>,\n ): Promise<T> {\n let key = await resolveKey();\n // A remap can only chain as far as route key -> shared hash, so a small\n // bound is enough; the final hop runs whatever key it holds.\n for (let hop = 0; hop < RequestScheduler.MAX_REMAPS; hop++) {\n const outcome = await this.run<\n { done: true; value: T } | { done: false; key: string }\n >(key, signal, path, async (release) => {\n const current = await resolveKey();\n if (current !== key) return { done: false, key: current };\n return { done: true, value: await task(key, release) };\n });\n if (outcome.done) return outcome.value;\n key = outcome.key;\n }\n return this.run(key, signal, path, (release) => task(key, release));\n }\n\n /**\n * Runs `task` once every earlier task for `key` has settled, or has freed\n * its slot early through the `release` callback it was given.\n * @param key Bucket key to serialise on.\n * @param signal Cancellation signal honoured while queued.\n * @param path Request path, used for cancellation error context.\n * @param task Work to run while holding the slot, given an idempotent\n * callback that frees the slot before the task settles.\n * @throws {RESTError} Of kind `\"overloaded\"` when a queue limit is reached.\n */\n public async run<T>(\n key: string,\n signal: AbortSignal | undefined,\n path: string,\n task: (release: () => void) => Promise<T>,\n ): Promise<T> {\n const previous = this.#queues.get(key);\n const waiting = this.#waiting.get(key) ?? 0;\n if (\n previous &&\n (this.#queued >= this.#maxQueued ||\n waiting >= this.#maxQueuedPerBucket)\n )\n throw new RESTError(\n \"The REST request queue is full; request not sent.\",\n 0,\n undefined,\n undefined,\n { path, kind: \"overloaded\" },\n );\n let open!: () => void;\n const gate = new Promise<void>((resolve) => {\n open = resolve;\n });\n this.#queues.set(key, gate);\n let released = false;\n const release = (): void => {\n if (released) return;\n released = true;\n open();\n // Drop our entry so idle buckets do not accumulate, but only if it\n // is still ours: a later request may already have replaced it.\n if (this.#queues.get(key) === gate) this.#queues.delete(key);\n };\n try {\n if (previous) {\n this.#queued++;\n this.#waiting.set(key, waiting + 1);\n try {\n await abortable(previous, signal, path);\n } finally {\n this.#queued--;\n const left = (this.#waiting.get(key) ?? 1) - 1;\n if (left > 0) this.#waiting.set(key, left);\n else this.#waiting.delete(key);\n }\n } else await abortable(Promise.resolve(), signal, path);\n return await task(release);\n } finally {\n release();\n }\n }\n}\n",
|
|
9
|
+
"/** Which limit a 429 counts against, from `X-RateLimit-Scope`. */\nexport type RateLimitScope = \"user\" | \"global\" | \"shared\";\n\n/** A response's rate-limit headers, each one validated on its own. */\nexport interface RateLimitHeaders {\n /** `X-RateLimit-Limit`: requests per window. */\n limit?: number;\n /** `X-RateLimit-Remaining`: requests left in the window. */\n remaining?: number;\n /** `X-RateLimit-Reset-After`, in ms from now. */\n resetAfterMs?: number;\n /** `X-RateLimit-Reset`, as epoch ms: identifies the window. */\n resetAtMs?: number;\n /** `X-RateLimit-Bucket`: the server's bucket hash. */\n bucket?: string;\n /** `X-RateLimit-Scope`, when it is one Discord documents. */\n scope?: RateLimitScope;\n /** `X-RateLimit-Global: true`. */\n global: boolean;\n /** `Retry-After`, in ms. */\n retryAfterMs?: number;\n}\n\n/**\n * A non-negative finite number from a header, or `undefined`.\n *\n * `Number(null)` and `Number(\"\")` are 0, which would read a missing header as\n * \"nothing remaining\" or \"reset now\"; a negative or non-numeric value is a\n * broken proxy, not information.\n */\nexport function headerNumber(\n headers: Headers,\n name: string,\n): number | undefined {\n const raw = headers.get(name);\n if (raw === null || raw.trim() === \"\") return undefined;\n const value = Number(raw);\n return Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n\n/** Bucket hashes are short opaque strings; anything else is not one. */\nconst BUCKET_HASH = /^[A-Za-z0-9:_\\-.]{1,64}$/;\n\n/**\n * Discord's per-bucket counts are small; one beyond this is a broken or\n * hostile responder, and reading it would make the bucket unlimited.\n */\nconst MAX_COUNT = 100_000;\nfunction count(value: number | undefined): number | undefined {\n return value === undefined || value > MAX_COUNT\n ? undefined\n : Math.floor(value);\n}\n\n/** Reads every rate-limit header of a response defensively. */\nexport function parseRateLimitHeaders(headers: Headers): RateLimitHeaders {\n const remaining = headerNumber(headers, \"X-RateLimit-Remaining\");\n const limit = headerNumber(headers, \"X-RateLimit-Limit\");\n const resetAfter = headerNumber(headers, \"X-RateLimit-Reset-After\");\n const reset = headerNumber(headers, \"X-RateLimit-Reset\");\n const retryAfter = headerNumber(headers, \"Retry-After\");\n const bucket = headers.get(\"X-RateLimit-Bucket\")?.trim();\n const scope = headers.get(\"X-RateLimit-Scope\")?.trim().toLowerCase();\n return {\n limit: count(limit),\n remaining: count(remaining),\n resetAfterMs: resetAfter === undefined ? undefined : resetAfter * 1000,\n resetAtMs: reset === undefined ? undefined : reset * 1000,\n bucket: bucket && BUCKET_HASH.test(bucket) ? bucket : undefined,\n scope:\n scope === \"user\" || scope === \"global\" || scope === \"shared\"\n ? scope\n : undefined,\n global:\n headers.get(\"X-RateLimit-Global\")?.trim().toLowerCase() === \"true\",\n retryAfterMs: retryAfter === undefined ? undefined : retryAfter * 1000,\n };\n}\n",
|
|
10
|
+
"/**\n * What the REST pipeline reports while it works. Every field is bounded and\n * free of secrets: routes are normalized (ids become `:id`, tokens `:token`,\n * reaction emoji `:emoji`), and no token, fingerprint or body is included.\n */\nexport type RESTTelemetryEvent =\n /** One HTTP attempt got a response. */\n | {\n type: \"response\";\n method: string;\n route: string;\n status: number;\n durationMs: number;\n }\n /** A request settled (after every attempt), successfully or not. */\n | {\n type: \"request\";\n method: string;\n route: string;\n status: number;\n attempts: number;\n durationMs: number;\n ok: boolean;\n }\n /** Discord answered 429. */\n | {\n type: \"rateLimited\";\n route: string;\n scope: \"user\" | \"global\" | \"shared\" | \"unknown\";\n retryAfterMs: number;\n }\n /** A request waited for a limit before it could be sent. */\n | {\n type: \"wait\";\n reason: \"bucket\" | \"global\" | \"probe\";\n durationMs: number;\n }\n /** A reservation was asked of the store. */\n | {\n type: \"reservation\";\n granted: boolean;\n known: boolean;\n latencyMs: number;\n }\n /** A request is being sent again. */\n | {\n type: \"retry\";\n route: string;\n reason: \"rateLimited\" | \"server\" | \"network\";\n attempt: number;\n }\n /** A request was cancelled. */\n | {\n type: \"cancelled\";\n stage: \"queued\" | \"waiting\" | \"transport\" | \"retry\";\n }\n /** A request was refused because a queue was full. */\n | { type: \"overloaded\" }\n /** Discord answered 401 on a bot route: later requests with the token are refused. */\n | { type: \"tokenRejected\" }\n /** A transport attempt failed without a response. */\n | { type: \"networkError\"; timedOut: boolean }\n /** A route learned its bucket hash, or the hash changed. */\n | { type: \"bucket\"; change: \"discovered\" | \"remapped\"; route: string }\n /** One store command (Redis), with its latency. */\n | {\n type: \"store\";\n operation: string;\n latencyMs: number;\n ok: boolean;\n }\n /** The store's coordination mode changed. */\n | { type: \"mode\"; mode: CoordinationMode; previous: CoordinationMode }\n /** Queue depth and in-flight requests, after a change. */\n | { type: \"queue\"; queued: number; inFlight: number };\n\n/**\n * How a store enforces limits right now.\n *\n * - `distributed`: shared state with atomic reservations.\n * - `observational`: shared state without atomic reservations (a client with\n * no `eval`): workers can race on the last unit.\n * - `degraded-local`: shared state is unreachable and this process is\n * limiting alone, from its last known state.\n * - `unavailable`: shared state is unreachable and requests fail\n * (`failurePolicy: \"fail-closed\"`).\n * - `local`: a process-local store.\n */\nexport type CoordinationMode =\n | \"distributed\"\n | \"observational\"\n | \"degraded-local\"\n | \"unavailable\"\n | \"local\";\n\n/** Receives telemetry events. Must be fast and must not throw. */\nexport interface RESTTelemetry {\n emit(event: RESTTelemetryEvent): void;\n}\n\n/** Calls a sink without letting it break the request path. */\nexport function emitTelemetry(\n sink: RESTTelemetry | undefined,\n event: RESTTelemetryEvent,\n): void {\n if (!sink) return;\n try {\n sink.emit(event);\n } catch {\n // Diagnostics never fail a request.\n }\n}\n\n/** Histogram bucket upper bounds, in ms. */\nconst LATENCY_BOUNDS = [\n 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000,\n];\n\ninterface Histogram {\n counts: number[];\n sum: number;\n count: number;\n}\n\n/** A snapshot of {@link RESTMetrics}. */\nexport interface RESTMetricsSnapshot {\n /** `name{label=\"v\",…}` → count. */\n counters: Record<string, number>;\n /** `name` → last value. */\n gauges: Record<string, number>;\n /** `name{…}` → latency distribution in ms. */\n histograms: Record<\n string,\n { count: number; sum: number; buckets: Record<string, number> }\n >;\n}\n\n/**\n * An in-memory metrics aggregator for {@link RESTTelemetry}: counters,\n * gauges and latency histograms, with a hard cap on distinct label sets so\n * an unexpected route shape cannot grow it without bound.\n *\n * ```ts\n * const metrics = new RESTMetrics();\n * const rest = new REST({ token, telemetry: metrics });\n * // later: metrics.snapshot(), or metrics.toPrometheus() for a /metrics endpoint\n * ```\n */\nexport class RESTMetrics implements RESTTelemetry {\n readonly #counters = new Map<string, number>();\n readonly #gauges = new Map<string, number>();\n readonly #histograms = new Map<string, Histogram>();\n readonly #routes = new Set<string>();\n readonly #maxRoutes: number;\n\n /** @param options `maxRoutes`: distinct route labels kept before the rest become `other` (default 500). */\n public constructor(options: { maxRoutes?: number } = {}) {\n this.#maxRoutes = options.maxRoutes ?? 500;\n }\n\n #route(route: string): string {\n if (this.#routes.has(route)) return route;\n if (this.#routes.size >= this.#maxRoutes) return \"other\";\n this.#routes.add(route);\n return route;\n }\n\n #inc(name: string, labels: Record<string, string> = {}, by = 1): void {\n const key = metricKey(name, labels);\n this.#counters.set(key, (this.#counters.get(key) ?? 0) + by);\n }\n\n #observe(\n name: string,\n value: number,\n labels: Record<string, string> = {},\n ): void {\n const key = metricKey(name, labels);\n let h = this.#histograms.get(key);\n if (!h) {\n h = {\n counts: new Array<number>(LATENCY_BOUNDS.length + 1).fill(0),\n sum: 0,\n count: 0,\n };\n this.#histograms.set(key, h);\n }\n let i = LATENCY_BOUNDS.findIndex((bound) => value <= bound);\n if (i === -1) i = LATENCY_BOUNDS.length;\n h.counts[i]!++;\n h.sum += value;\n h.count++;\n }\n\n public emit(event: RESTTelemetryEvent): void {\n switch (event.type) {\n case \"response\":\n this.#inc(\"responses_total\", {\n method: event.method,\n route: this.#route(event.route),\n status: String(event.status),\n });\n this.#observe(\"response_latency_ms\", event.durationMs, {\n method: event.method,\n });\n break;\n case \"request\":\n this.#inc(\"requests_total\", {\n method: event.method,\n outcome: event.ok ? \"ok\" : \"error\",\n });\n this.#observe(\"request_latency_ms\", event.durationMs, {\n method: event.method,\n });\n break;\n case \"rateLimited\":\n this.#inc(\"rate_limited_total\", {\n scope: event.scope,\n route: this.#route(event.route),\n });\n break;\n case \"wait\":\n this.#inc(\"waits_total\", { reason: event.reason });\n this.#observe(\"wait_ms\", event.durationMs, {\n reason: event.reason,\n });\n break;\n case \"reservation\":\n this.#inc(\"reservations_total\", {\n outcome: event.granted ? \"granted\" : \"refused\",\n });\n this.#observe(\"reservation_latency_ms\", event.latencyMs);\n break;\n case \"retry\":\n this.#inc(\"retries_total\", { reason: event.reason });\n break;\n case \"cancelled\":\n this.#inc(\"cancellations_total\", { stage: event.stage });\n break;\n case \"overloaded\":\n this.#inc(\"overloaded_total\");\n break;\n case \"tokenRejected\":\n this.#inc(\"token_rejected_total\");\n break;\n case \"networkError\":\n this.#inc(\"network_errors_total\", {\n timeout: String(event.timedOut),\n });\n break;\n case \"bucket\":\n this.#inc(\"bucket_changes_total\", { change: event.change });\n break;\n case \"store\":\n this.#inc(\"store_commands_total\", {\n operation: event.operation,\n ok: String(event.ok),\n });\n this.#observe(\"store_latency_ms\", event.latencyMs, {\n operation: event.operation,\n });\n break;\n case \"mode\":\n this.#inc(\"mode_changes_total\", { mode: event.mode });\n this.#gauges.set(\n \"degraded\",\n event.mode === \"degraded-local\" ||\n event.mode === \"unavailable\"\n ? 1\n : 0,\n );\n break;\n case \"queue\":\n this.#gauges.set(\"queued\", event.queued);\n this.#gauges.set(\"in_flight\", event.inFlight);\n break;\n }\n }\n\n /** The current values. */\n public snapshot(): RESTMetricsSnapshot {\n const histograms: RESTMetricsSnapshot[\"histograms\"] = {};\n for (const [key, h] of this.#histograms) {\n const buckets: Record<string, number> = {};\n let running = 0;\n LATENCY_BOUNDS.forEach((bound, i) => {\n running += h.counts[i]!;\n buckets[String(bound)] = running;\n });\n buckets[\"+Inf\"] = h.count;\n histograms[key] = { count: h.count, sum: h.sum, buckets };\n }\n return {\n counters: Object.fromEntries(this.#counters),\n gauges: Object.fromEntries(this.#gauges),\n histograms,\n };\n }\n\n /** Clears every value. */\n public reset(): void {\n this.#counters.clear();\n this.#gauges.clear();\n this.#histograms.clear();\n this.#routes.clear();\n }\n\n /** The values in the Prometheus text exposition format. */\n public toPrometheus(prefix = \"lunibee_rest\"): string {\n const lines: string[] = [];\n const typed = new Set<string>();\n const type = (name: string, kind: string): void => {\n if (typed.has(name)) return;\n typed.add(name);\n lines.push(`# TYPE ${name} ${kind}`);\n };\n for (const [key, value] of this.#counters) {\n const name = `${prefix}_${key.split(\"{\")[0]}`;\n type(name, \"counter\");\n lines.push(`${prefix}_${key} ${value}`);\n }\n for (const [key, value] of this.#gauges) {\n type(`${prefix}_${key}`, \"gauge\");\n lines.push(`${prefix}_${key} ${value}`);\n }\n for (const [key, h] of this.#histograms) {\n const [base, rawLabels = \"\"] = key.split(\"{\");\n const labels = rawLabels.replace(/\\}$/, \"\");\n const name = `${prefix}_${base}`;\n type(name, \"histogram\");\n const join = (extra: string): string =>\n `{${[labels, extra].filter(Boolean).join(\",\")}}`;\n let running = 0;\n LATENCY_BOUNDS.forEach((bound, i) => {\n running += h.counts[i]!;\n lines.push(`${name}_bucket${join(`le=\"${bound}\"`)} ${running}`);\n });\n lines.push(`${name}_bucket${join('le=\"+Inf\"')} ${h.count}`);\n lines.push(`${name}_sum${labels ? `{${labels}}` : \"\"} ${h.sum}`);\n lines.push(\n `${name}_count${labels ? `{${labels}}` : \"\"} ${h.count}`,\n );\n }\n return `${lines.join(\"\\n\")}\\n`;\n }\n}\n\nfunction metricKey(name: string, labels: Record<string, string>): string {\n const entries = Object.entries(labels);\n if (entries.length === 0) return name;\n return `${name}{${entries\n .map(([k, v]) => `${k}=\"${v.replace(/[\"\\\\\\n]/g, \"_\")}\"`)\n .join(\",\")}}`;\n}\n",
|
|
11
|
+
"import { RESTError, sanitizeCause, sleep } from \"./errors.js\";\nimport { parseRateLimitHeaders } from \"./headers.js\";\nimport { scopeBucket } from \"./route.js\";\nimport {\n DEFAULT_PROBE_MS,\n reconcileState,\n takeGlobalSlot,\n type BucketUpdate,\n type RateLimitStore,\n} from \"./store.js\";\nimport { emitTelemetry, type RESTTelemetry } from \"./telemetry.js\";\n\n/** Default for `maxBackoff`: ten minutes. */\nexport const DEFAULT_MAX_BACKOFF_MS = 600_000;\n\n/** Refusals in a row without a wait before a store is declared broken. */\nconst MAX_IDLE_REFUSALS = 16;\n/** Route-to-hash mappings cached in-process. */\nconst MAX_CACHED_ROUTES = 10_000;\n\n/** What {@link RateLimiter.acquire} obtained. */\nexport interface Grant {\n /** A unit was reserved atomically (false for a store without `reserve`). */\n reserved: boolean;\n /** The bucket's limit is known, so further requests need not wait for this one's response. */\n known: boolean;\n /** The window the unit came from (store clock), when the store reports it. */\n resetAt?: number;\n}\n\n/** Context for {@link RateLimiter.applyResponse} beyond the response itself. */\nexport interface ApplyResponseOptions {\n /** Bucket-key prefix of the request's identity (see `requestIdentity`). */\n identity?: string;\n /** Global-limit scope the request counts against, if any. */\n global?: string;\n /** The decoded error body, for `retry_after` and `global` on a 429. */\n body?: { retry_after?: number; global?: boolean };\n /** The grant the request was sent under. */\n grant?: Grant;\n}\n\n/**\n * Owns Discord's rate-limit state: when a request may go out, and what a\n * response says about the bucket it landed in.\n *\n * The limiter holds no queueing logic of its own — {@link RequestScheduler}\n * decides ordering — and no HTTP knowledge beyond reading rate-limit headers.\n */\nexport class RateLimiter {\n readonly #store: RateLimitStore;\n /** The longest wait the limiter will impose on the strength of a server header. */\n readonly #maxBackoff: number;\n readonly #telemetry?: RESTTelemetry;\n readonly #probeMs: number;\n /** Route → bucket hash, learned from this process's own responses or the store. */\n readonly #routes = new Map<string, string>();\n readonly #globalLimit: number;\n /** Per-scope request windows, for stores that cannot count globally themselves. */\n readonly #localGlobal = new Map<string, number[]>();\n\n /**\n * @param store Rate-limit state store.\n * @param options `maxBackoff`: the most, in ms, that a rate-limit header can\n * make later requests wait (default 10 minutes). `probeMs`: how long a\n * request discovering an unknown bucket holds it (default 16 s; keep it\n * above the transport timeout). `globalLimit`: most requests per second\n * per global scope, enforced before Discord answers 429 (default none;\n * `REST` passes 50). `telemetry`: event sink. `concurrent` is\n * accepted for compatibility: responses are always merged so a late one\n * cannot raise `remaining`.\n */\n public constructor(\n store: RateLimitStore,\n options: {\n concurrent?: boolean;\n maxBackoff?: number;\n probeMs?: number;\n globalLimit?: number;\n telemetry?: RESTTelemetry;\n } = {},\n ) {\n this.#store = store;\n this.#maxBackoff = options.maxBackoff ?? DEFAULT_MAX_BACKOFF_MS;\n if (!(this.#maxBackoff >= 0))\n throw new RangeError(\"maxBackoff must be a non-negative number.\");\n this.#probeMs = options.probeMs ?? DEFAULT_PROBE_MS;\n if (!(this.#probeMs > 0))\n throw new RangeError(\"probeMs must be a positive number.\");\n this.#telemetry = options.telemetry;\n this.#globalLimit = options.globalLimit ?? 0;\n if (!(this.#globalLimit >= 0) || !Number.isFinite(this.#globalLimit))\n throw new RangeError(\"globalLimit must be a non-negative number.\");\n }\n\n /** The ceiling on a wait a server header can cause, in ms. */\n public get maxBackoff(): number {\n return this.#maxBackoff;\n }\n\n /** A wait no longer than `maxBackoff`: a header cannot wedge the limiter. */\n #capWait(ms: number): number {\n return Math.min(Math.max(0, ms), this.#maxBackoff);\n }\n\n /** The backing store, shared across processes when a distributed store is used. */\n public get store(): RateLimitStore {\n return this.#store;\n }\n\n /** Whether the backing store can reserve allowance atomically. */\n public get reserves(): boolean {\n return typeof this.#store.reserve === \"function\";\n }\n\n #cacheRoute(route: string, hash: string): void {\n this.#routes.delete(route);\n this.#routes.set(route, hash);\n if (this.#routes.size > MAX_CACHED_ROUTES)\n this.#routes.delete(this.#routes.keys().next().value as string);\n }\n\n /**\n * Resolves the bucket key a request should be gated on. With an\n * `identity`, the key is prefixed by it, so two bots never share a bucket.\n */\n public async resolveBucketKey(\n route: string,\n major: string,\n identity?: string,\n ): Promise<string> {\n // What this major parameter's own responses said wins over what the\n // route said for another one: if hashes ever differ between them, or\n // change, a request is not split across two keys of one bucket.\n let hash =\n this.#routes.get(`${route}\\u0000${major}`) ??\n this.#routes.get(route);\n if (hash === undefined) {\n hash = await this.#store.getBucketHash(route);\n if (hash !== undefined) this.#cacheRoute(route, hash);\n }\n const key = scopeBucket(hash ?? route, major);\n return identity === undefined ? key : `${identity}/${key}`;\n }\n\n /** Runs a store call, turning an unexpected failure into a coordination error. */\n async #call<T>(\n path: string,\n operation: string,\n call: () => Promise<T> | T,\n ): Promise<T> {\n try {\n return await call();\n } catch (error) {\n if (error instanceof RESTError) throw error;\n throw new RESTError(\n `The rate-limit store failed (${operation}); request not sent.`,\n 0,\n undefined,\n undefined,\n { path, kind: \"coordination\", cause: sanitizeCause(error) },\n );\n }\n }\n\n /**\n * Waits until both the route bucket and the global limit permit sending.\n *\n * When the store supports it, allowance is *reserved* rather than merely\n * observed. Reading `remaining` and deciding to send is a race across\n * workers: three processes all read `remaining: 1` and all send. A\n * reservation hands the unit to exactly one of them. A request is never\n * sent without its reservation: a store that keeps refusing without a\n * wait, or that fails, ends the request with a `\"coordination\"` error.\n * @throws {RESTError} `\"aborted\"` on cancellation, `\"coordination\"` on a\n * broken store, `\"rateLimited\"` when waiting would exceed `maxBackoff`.\n */\n public async acquire(\n bucketKey: string,\n signal: AbortSignal | undefined,\n path: string,\n options: { global?: string } = {},\n ): Promise<Grant> {\n const store = this.#store;\n // Callers that name no scope get the default one, as before scopes.\n const global = \"global\" in options ? options.global : \"\";\n if (!store.reserve) {\n await this.#waitGlobal(global ?? \"\", signal, path);\n if (global !== undefined)\n await this.#localGlobalSlot(global, signal, path);\n const bucket = await this.#call(path, \"getBucket\", () =>\n store.getBucket(bucketKey),\n );\n if (bucket) {\n const delay = bucket.resetAt - Date.now();\n if (bucket.remaining <= 0 && delay > 0)\n await this.#wait(\"bucket\", delay, signal, path);\n }\n return {\n reserved: false,\n known: !!bucket && bucket.resetAt > Date.now(),\n };\n }\n const started = performance.now();\n let idle = 0;\n for (;;) {\n if (global !== undefined && !store.reservesGlobal) {\n await this.#waitGlobal(global, signal, path);\n await this.#localGlobalSlot(global, signal, path);\n }\n const asked = performance.now();\n const reservation = await this.#call(path, \"reserve\", () =>\n store.reserve!(bucketKey, {\n global,\n probeMs: this.#probeMs,\n globalLimit: this.#globalLimit || undefined,\n }),\n );\n if (\n !reservation ||\n typeof reservation !== \"object\" ||\n typeof reservation.granted !== \"boolean\"\n )\n throw new RESTError(\n \"The rate-limit store returned an invalid reservation; request not sent.\",\n 0,\n undefined,\n undefined,\n { path, kind: \"coordination\" },\n );\n if (reservation.granted) {\n let known = reservation.known;\n // A store that does not say whether the limit is known: look.\n if (known === undefined) {\n const bucket = await this.#call(path, \"getBucket\", () =>\n store.getBucket(bucketKey),\n );\n known = !!bucket && bucket.resetAt > Date.now();\n }\n emitTelemetry(this.#telemetry, {\n type: \"reservation\",\n granted: true,\n known,\n latencyMs: performance.now() - asked,\n });\n return { reserved: true, known, resetAt: reservation.resetAt };\n }\n emitTelemetry(this.#telemetry, {\n type: \"reservation\",\n granted: false,\n known: true,\n latencyMs: performance.now() - asked,\n });\n let wait = Number(reservation.retryAfterMs);\n if (!(wait > 0)) {\n // A refusal with no wait is a broken store. Back off a little\n // and give up after a few, rather than spin or send anyway.\n if (++idle > MAX_IDLE_REFUSALS)\n throw new RESTError(\n \"The rate-limit store keeps refusing without a wait; request not sent.\",\n 0,\n undefined,\n undefined,\n { path, kind: \"coordination\" },\n );\n wait = Math.min(100, 2 ** idle);\n } else idle = 0;\n // One wait is capped at maxBackoff; a request that keeps losing\n // the race for its bucket gives up at twice that, not never.\n if (performance.now() - started + wait > 2 * this.#maxBackoff)\n throw new RESTError(\n `Waited for the rate limit longer than twice maxBackoff (${Math.ceil(this.#maxBackoff / 1000)}s); request not sent.`,\n 429,\n undefined,\n undefined,\n { path },\n );\n await this.#wait(\n reservation.reason ?? \"bucket\",\n wait,\n signal,\n path,\n );\n }\n }\n\n async #wait(\n reason: \"bucket\" | \"global\" | \"probe\",\n delay: number,\n signal: AbortSignal | undefined,\n path: string,\n ): Promise<void> {\n const ms = Math.min(delay, this.#maxBackoff);\n await sleep(ms, signal, path);\n emitTelemetry(this.#telemetry, {\n type: \"wait\",\n reason,\n durationMs: ms,\n });\n }\n\n /**\n * Takes one request of `scope`'s per-second allowance in this process,\n * for a store that cannot count it atomically across processes.\n */\n async #localGlobalSlot(\n scope: string,\n signal: AbortSignal | undefined,\n path: string,\n ): Promise<void> {\n if (!this.#globalLimit) return;\n let log = this.#localGlobal.get(scope);\n if (!log) {\n this.#localGlobal.set(scope, (log = []));\n if (this.#localGlobal.size > MAX_CACHED_ROUTES)\n this.#localGlobal.delete(\n this.#localGlobal.keys().next().value as string,\n );\n }\n for (;;) {\n const wait = takeGlobalSlot(log, Date.now(), this.#globalLimit);\n if (wait === 0) return;\n await this.#wait(\"global\", wait, signal, path);\n }\n }\n\n async #waitGlobal(\n scope: string,\n signal: AbortSignal | undefined,\n path: string,\n ): Promise<void> {\n const globalDelay =\n (await this.#call(path, \"getGlobalReset\", () =>\n this.#store.getGlobalReset(scope),\n )) - Date.now();\n if (globalDelay > 0)\n await this.#wait(\"global\", globalDelay, signal, path);\n }\n\n /**\n * Extends the global limit (of `scope`), never shortening a longer one\n * already recorded. Atomic when the store has `extendGlobalReset`.\n */\n public async noteGlobalReset(resetAt: number, scope = \"\"): Promise<void> {\n const waitMs = this.#capWait(resetAt - Date.now());\n if (this.#store.extendGlobalReset) {\n await this.#store.extendGlobalReset(scope, waitMs);\n return;\n }\n const current = await this.#store.getGlobalReset(scope);\n const target = Date.now() + waitMs;\n if (target > current) await this.#store.setGlobalReset(target, scope);\n }\n\n /**\n * How long a 429 asks to wait, in ms: the body's `retry_after`, else the\n * `Retry-After` header, else `X-RateLimit-Reset-After`, else one second.\n * Negative or non-numeric values are ignored, not read as zero.\n */\n public retryAfterMs(\n response: Response,\n body: { retry_after?: number } = {},\n ): number {\n const fromBody = body.retry_after;\n if (\n typeof fromBody === \"number\" &&\n Number.isFinite(fromBody) &&\n fromBody >= 0\n )\n return fromBody * 1000;\n const headers = parseRateLimitHeaders(response.headers);\n return headers.retryAfterMs ?? headers.resetAfterMs ?? 1000;\n }\n\n /**\n * Records a response's rate-limit headers and returns the bucket key the\n * request actually belongs to: Discord may reveal a server bucket hash that\n * differs from the route-derived key used before the first response.\n */\n public async applyResponse(\n response: Response,\n bucketKey: string,\n route: string,\n major: string,\n options: ApplyResponseOptions = {},\n ): Promise<string> {\n const headers = parseRateLimitHeaders(response.headers);\n let currentKey = bucketKey;\n if (headers.bucket) {\n const previous = this.#routes.get(route);\n this.#cacheRoute(`${route}\\u0000${major}`, headers.bucket);\n if (previous !== headers.bucket) {\n await this.#store.setBucketHash(route, headers.bucket);\n this.#cacheRoute(route, headers.bucket);\n emitTelemetry(this.#telemetry, {\n type: \"bucket\",\n change: previous === undefined ? \"discovered\" : \"remapped\",\n route,\n });\n }\n const key = scopeBucket(headers.bucket, major);\n currentKey =\n options.identity === undefined\n ? key\n : `${options.identity}/${key}`;\n }\n\n const limited = response.status === 429;\n const retryAfterMs = limited\n ? this.#capWait(this.retryAfterMs(response, options.body))\n : undefined;\n const global =\n limited &&\n (options.body?.global === true ||\n headers.global ||\n headers.scope === \"global\");\n // A global 429 on a request that counts against no global limit (an\n // interaction callback) is recorded on its bucket instead.\n const onGlobal = global && options.global !== undefined;\n\n let resetAfterMs = headers.resetAfterMs;\n if (resetAfterMs === undefined && headers.resetAtMs !== undefined)\n resetAfterMs = Math.max(0, headers.resetAtMs - Date.now());\n const update: BucketUpdate = {\n remaining: headers.remaining,\n resetAfterMs:\n resetAfterMs === undefined\n ? undefined\n : this.#capWait(resetAfterMs),\n limit: headers.limit,\n // The window id orders responses; one far from now (a hostile or\n // broken responder) would make every real response look stale.\n window:\n headers.resetAtMs !== undefined &&\n Math.abs(headers.resetAtMs - Date.now()) <= this.#maxBackoff\n ? headers.resetAtMs\n : undefined,\n retryAfterMs: limited && !onGlobal ? retryAfterMs : undefined,\n };\n if (this.#store.reconcile)\n await this.#store.reconcile(currentKey, update);\n else {\n const next = reconcileState(\n await this.#store.getBucket(currentKey),\n update,\n Date.now(),\n );\n if (next === null)\n await this.#store.updateBucket(currentKey, {\n remaining: 0,\n resetAt: 0,\n });\n else if (next) await this.#store.updateBucket(currentKey, next);\n }\n // The request probed under its route key and learned the real one:\n // clear the placeholder, so nobody else waits on it.\n if (\n currentKey !== bucketKey &&\n options.grant?.reserved &&\n this.#store.release\n )\n await this.#store.release(bucketKey, options.grant, false);\n\n if (onGlobal)\n await this.noteGlobalReset(\n Date.now() + retryAfterMs!,\n options.global,\n );\n return currentKey;\n }\n\n /**\n * Gives back what a grant held when the request did not get a response:\n * a probe placeholder is always cleared; the unit itself is returned only\n * with `notSent` (the request certainly never left), because a request\n * that may have reached Discord may also have spent it.\n */\n public async releaseGrant(\n bucketKey: string,\n grant: Grant,\n notSent: boolean,\n ): Promise<void> {\n if (!grant.reserved || !this.#store.release) return;\n try {\n await this.#store.release(bucketKey, grant, notSent);\n } catch {\n // Best effort: the placeholder expires on its own.\n }\n }\n}\n",
|
|
12
|
+
"import { createHash } from \"node:crypto\";\n\n/**\n * Who a request's rate limits belong to.\n *\n * `bucket` prefixes the bucket key: Discord counts a bot's buckets per bot, so\n * two bots sharing one Redis must not share counters. `global` is the scope of\n * the global limit the request counts against, or `undefined` when it counts\n * against none.\n */\nexport interface RequestIdentity {\n bucket: string;\n global?: string;\n}\n\n/**\n * A stable, non-reversible fingerprint of a token, identical in every process.\n * The token itself never enters a key, a label or a log line.\n */\nexport function tokenFingerprint(token: string): string {\n return createHash(\"sha256\").update(token).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * Classifies a request.\n *\n * - Interaction callbacks (`/interactions/:id/:token/…`) and token webhooks\n * (`/webhooks/:id/:token…`, which includes interaction follow-ups) are\n * limited per webhook, whoever sends them: their buckets are shared\n * (`\"shared\"`), so every bot and process agrees on one counter.\n * Interaction callbacks are not bound to any global limit; a token webhook\n * still counts against the sender's, conservatively, because a follow-up\n * cannot be told from a plain webhook by its path.\n * - A request sent without a bot token is limited per caller (`\"anon\"`).\n * - Everything else belongs to the bot that sends it.\n */\nexport function requestIdentity(\n path: string,\n fingerprint: string | undefined,\n): RequestIdentity {\n if (/^\\/interactions\\//i.test(path)) return { bucket: \"shared\" };\n if (/^\\/webhooks\\/[^/?]+\\/[^/?]+/i.test(path))\n return { bucket: \"shared\", global: fingerprint ?? \"anon\" };\n if (!fingerprint) return { bucket: \"anon\", global: \"anon\" };\n return { bucket: fingerprint, global: fingerprint };\n}\n",
|
|
13
|
+
"import { RESTError, abortError, sanitizeCause } from \"./errors.js\";\n\n/** Minimal fetch shape the transport depends on, so a stub needs no extras. */\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\n/** One HTTP attempt, fully resolved: no routing, retry or rate-limit concerns. */\nexport interface TransportRequest {\n /** Uppercased HTTP method. */\n method: string;\n /** Path with query string, relative to the transport's base URL. */\n path: string;\n /** Request headers, already including authorization. */\n headers: Record<string, string>;\n /** Encoded body, or undefined for a bodyless request. */\n body?: BodyInit;\n /** Caller cancellation signal. */\n signal?: AbortSignal;\n}\n\n/** Hosts that are this machine, where cleartext is acceptable. */\nconst LOOPBACK = /^(?:localhost|127(?:\\.\\d{1,3}){3}|\\[::1\\])$/i;\n\n/**\n * Checks a base URL before the bot token is sent to it. It must be an\n * `https:` URL with no credentials, query or fragment; `http:` is accepted\n * for this machine, or anywhere with `allowInsecure`.\n * @throws {TypeError} Without repeating any credentials the URL contained.\n */\nexport function parseBaseURL(raw: string, allowInsecure = false): string {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new TypeError(\"baseURL must be an absolute http(s) URL.\");\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\")\n throw new TypeError(\"baseURL must use https.\");\n if (url.username || url.password)\n throw new TypeError(\"baseURL must not contain credentials.\");\n if (url.search || url.hash || /[?#]/.test(raw))\n throw new TypeError(\"baseURL must not contain a query or fragment.\");\n if (\n url.protocol === \"http:\" &&\n !allowInsecure &&\n !LOOPBACK.test(url.hostname)\n )\n throw new TypeError(\n \"baseURL must use https: the bot token would travel in cleartext (allowInsecureBaseURL: true overrides this for a trusted network).\",\n );\n return url.href.replace(/\\/$/, \"\");\n}\n\n/** Thrown by {@link HttpTransport} when the attempt never produced a response. */\nexport class TransportError extends Error {\n /** Whether the attempt was cut short by the transport timeout or an abort. */\n public readonly timedOut: boolean;\n public constructor(message: string, timedOut: boolean, cause?: unknown) {\n super(message, cause === undefined ? undefined : { cause });\n this.name = \"TransportError\";\n this.timedOut = timedOut;\n }\n}\n\n/**\n * Sends one HTTP attempt and returns the raw {@link Response}.\n *\n * Everything above it — routing, rate limiting, retries, decoding — is somebody\n * else's job, which is what makes the layer substitutable in tests.\n */\nexport class HttpTransport {\n readonly #baseURL: string;\n readonly #timeout: number;\n readonly #fetch: FetchLike;\n\n public constructor(\n options: {\n baseURL?: string;\n /** Allow a cleartext `http://` base URL on a network you trust (a REST proxy). Default false. */\n allowInsecureBaseURL?: boolean;\n timeout?: number;\n fetch?: FetchLike;\n } = {},\n ) {\n this.#baseURL = parseBaseURL(\n options.baseURL ?? \"https://discord.com/api/v10\",\n options.allowInsecureBaseURL,\n );\n this.#timeout = Math.max(1, options.timeout ?? 15_000);\n this.#fetch =\n options.fetch ?? ((input, init) => globalThis.fetch(input, init));\n }\n\n /** Base URL every request path is resolved against. */\n public get baseURL(): string {\n return this.#baseURL;\n }\n /** Per-attempt timeout in milliseconds. */\n public get timeout(): number {\n return this.#timeout;\n }\n\n /**\n * Performs the attempt.\n * @throws {RESTError} If the caller's signal was already aborted.\n * @throws {TransportError} If the attempt times out or the transport fails.\n */\n public async send(request: TransportRequest): Promise<Response> {\n if (request.signal?.aborted)\n throw abortError(request.path, request.signal.reason);\n\n const controller = new AbortController();\n const onAbort = (): void => controller.abort(request.signal?.reason);\n request.signal?.addEventListener(\"abort\", onAbort, { once: true });\n const timer = setTimeout(\n () =>\n controller.abort(\n new DOMException(\"REST request timeout\", \"TimeoutError\"),\n ),\n this.#timeout,\n );\n try {\n return await this.#fetch(`${this.#baseURL}${request.path}`, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: controller.signal,\n // Discord's API does not redirect; one that does is not\n // something to send the token to.\n redirect: \"error\",\n });\n } catch (error) {\n if (error instanceof RESTError) throw error;\n const timedOut =\n error instanceof DOMException &&\n (error.name === \"TimeoutError\" || error.name === \"AbortError\");\n throw new TransportError(\n timedOut\n ? `Discord REST request timed out after ${this.#timeout}ms`\n : \"Discord REST request failed\",\n timedOut,\n sanitizeCause(error),\n );\n } finally {\n clearTimeout(timer);\n request.signal?.removeEventListener(\"abort\", onAbort);\n }\n }\n}\n",
|
|
14
|
+
"/** Normalised shape of a Discord error payload. */\nexport interface DecodedError {\n message?: string;\n code?: number;\n retry_after?: number;\n global?: boolean;\n}\n\n/** Turns a raw {@link Response} into a payload and the error metadata retries need. */\nexport class ResponseDecoder {\n /** Creates an empty instance. */\n public constructor() {}\n\n /** Reads a Discord response payload as JSON, text, or nothing for 204. */\n public async read(response: Response): Promise<unknown> {\n if (response.status === 204) return undefined;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\"))\n return response.json().catch(() => undefined);\n return response.text().catch(() => undefined);\n }\n\n /** Extracts Discord's error metadata from a decoded payload. */\n public errorData(payload: unknown): DecodedError {\n if (!payload || typeof payload !== \"object\") return {};\n const data = payload as Record<string, unknown>;\n return {\n message:\n typeof data.message === \"string\" ? data.message : undefined,\n code: typeof data.code === \"number\" ? data.code : undefined,\n retry_after:\n typeof data.retry_after === \"number\"\n ? data.retry_after\n : undefined,\n global: data.global === true,\n };\n }\n\n /**\n * Resolves Retry-After (seconds) from the payload, falling back to the\n * header and finally to one second.\n *\n * An absent header is explicitly *not* a zero delay: `Number(null)` is 0,\n * which is finite, so reading it directly would answer a 429 that carries\n * no Retry-After by retrying immediately — exactly when backing off matters.\n */\n public retryAfter(response: Response, data: DecodedError): number {\n const raw = response.headers.get(\"Retry-After\");\n const header = raw === null ? Number.NaN : Number(raw);\n const retryAfter =\n data.retry_after ?? (Number.isFinite(header) ? header : 1);\n return Math.max(0, retryAfter);\n }\n}\n",
|
|
12
15
|
"/** Discord REST API route definitions. */\nexport const Routes = {\n /** Returns the current bot user. @returns User route. */\n user: () => \"/users/@me\",\n /** Returns one user. @param userId User identifier. @returns User route. */\n userById: (userId: string) => `/users/${snowflake(userId, \"User ID\")}`,\n /** Returns a guild. @param guildId Guild identifier. @returns Guild route. */\n guild: (guildId: string) => `/guilds/${snowflake(guildId, \"Guild ID\")}`,\n /** Leaves a guild as the current user (DELETE). @param guildId Guild identifier. @returns Current-user guild route. */\n currentUserGuild: (guildId: string) =>\n `/users/@me/guilds/${snowflake(guildId, \"Guild ID\")}`,\n /** Returns a sticker. @param stickerId Sticker identifier. @returns Sticker route. */\n sticker: (stickerId: string) =>\n `/stickers/${snowflake(stickerId, \"Sticker ID\")}`,\n /** Removes one user's reaction (DELETE). @param channelId Channel identifier. @param messageId Message identifier. @param emoji Emoji name or ID. @param userId User identifier. @returns Reaction-user route. */\n messageReactionUser: (\n channelId: string,\n messageId: string,\n emoji: string,\n userId: string,\n ) =>\n `${Routes.messageReactions(channelId, messageId, emoji)}/${snowflake(userId, \"User ID\")}`,\n /** Returns a guild preview. @param guildId Guild identifier. */\n guildPreview: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/preview`,\n /** Returns active threads for a guild. @param guildId Guild identifier. */\n guildActiveThreads: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/threads/active`,\n /** Returns webhooks for a guild. @param guildId Guild identifier. */\n guildWebhooks: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/webhooks`,\n /** Returns invites for a guild. @param guildId Guild identifier. */\n guildInvites: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/invites`,\n /** Returns channels in a guild. @param guildId Guild identifier. @returns Channel collection route. */\n guildChannels: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/channels`,\n /** Returns a guild member. @param guildId Guild identifier. @param userId User identifier. @returns Guild-member route. */\n guildMember: (guildId: string, userId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/${snowflake(userId, \"User ID\")}`,\n /** Returns all guild roles. @param guildId Guild identifier. @returns Guild-role route. */\n guildRoles: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/roles`,\n /** Returns an individual guild role. */\n guildRole: (guildId: string, roleId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/roles/${snowflake(roleId, \"Role ID\")}`,\n /** Returns a member role route. */\n guildMemberRole: (guildId: string, userId: string, roleId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/${snowflake(userId, \"User ID\")}/roles/${snowflake(roleId, \"Role ID\")}`,\n /** Returns guild bans collection. */\n guildBans: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bans`,\n /** Returns an individual guild ban. */\n guildBan: (guildId: string, userId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bans/${snowflake(userId, \"User ID\")}`,\n /** Returns a channel. @param channelId Channel identifier. @returns Channel route. */\n channel: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}`,\n /** Returns messages in a channel. @param channelId Channel identifier. @returns Message collection route. */\n channelMessages: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages`,\n /** Returns a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Message route. */\n message: (channelId: string, messageId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/${snowflake(messageId, \"Message ID\")}`,\n /** Crossposts a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Crosspost route. */\n crosspostMessage: (channelId: string, messageId: string) =>\n `${Routes.message(channelId, messageId)}/crosspost`,\n /** Returns reactions for a message and emoji. @param channelId Channel identifier. @param messageId Message identifier. @param emoji Emoji name or ID. @returns Reaction route. */\n messageReactions: (channelId: string, messageId: string, emoji: string) =>\n `${Routes.message(channelId, messageId)}/reactions/${encodeURIComponent(emoji)}`,\n /** The bot's own reaction: PUT adds it, DELETE removes it. @param channelId Channel identifier. @param messageId Message identifier. @param emoji Emoji name or ID. */\n messageReactionMe: (channelId: string, messageId: string, emoji: string) =>\n `${Routes.messageReactions(channelId, messageId, emoji)}/@me`,\n /** Removes all reactions from a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Reaction collection route. */\n messageReactionsAll: (channelId: string, messageId: string) =>\n `${Routes.message(channelId, messageId)}/reactions`,\n /** Returns pinned messages, newest first, in pages of up to 50 (`before`, `limit`). @param channelId Channel identifier. @returns Pins route. */\n channelMessagesPins: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/pins`,\n /** Pins (PUT) or unpins (DELETE) a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Pin route. */\n channelMessagesPin: (channelId: string, messageId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/pins/${snowflake(messageId, \"Message ID\")}`,\n /** Creates a thread from a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Message-thread route. */\n messageThread: (channelId: string, messageId: string) =>\n `${Routes.message(channelId, messageId)}/threads`,\n /** Bulk deletes messages. @param channelId Channel identifier. @returns Bulk-delete route. */\n channelBulkDelete: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/bulk-delete`,\n /** Returns channel webhooks. @param channelId Channel identifier. @returns Webhook collection route. */\n channelWebhooks: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/webhooks`,\n /** Returns channel invites. @param channelId Channel identifier. @returns Invite collection route. */\n channelInvites: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/invites`,\n /** Returns a webhook. @param webhookId Webhook identifier. @param token Optional webhook token. @returns Webhook route. */\n webhook: (webhookId: string, token?: string) =>\n `/webhooks/${snowflake(webhookId, \"Webhook ID\")}${token ? `/${encodeURIComponent(token)}` : \"\"}`,\n /** Returns a webhook message. */\n webhookMessage: (webhookId: string, token: string, messageId: string) =>\n `/webhooks/${snowflake(webhookId, \"Webhook ID\")}/${encodeURIComponent(token)}/messages/${snowflake(messageId, \"Message ID\")}`,\n /** Returns application commands for an application. @param applicationId Application identifier. @returns Global command route. */\n applicationCommands: (applicationId: string) =>\n `/applications/${snowflake(applicationId, \"Application ID\")}/commands`,\n /** Returns one application command. @param applicationId Application identifier. @param commandId Command identifier. @returns Command route. */\n applicationCommand: (applicationId: string, commandId: string) =>\n `/applications/${snowflake(applicationId, \"Application ID\")}/commands/${snowflake(commandId, \"Command ID\")}`,\n /** Returns guild application commands. @param applicationId Application identifier. @param guildId Guild identifier. @returns Guild command route. */\n guildApplicationCommands: (applicationId: string, guildId: string) =>\n `/applications/${snowflake(applicationId, \"Application ID\")}/guilds/${snowflake(guildId, \"Guild ID\")}/commands`,\n /** Returns a single guild application command. @param applicationId Application identifier. @param guildId Guild identifier. @param commandId Command identifier. @returns Guild command route. */\n guildApplicationCommand: (\n applicationId: string,\n guildId: string,\n commandId: string,\n ) =>\n `/applications/${snowflake(applicationId, \"Application ID\")}/guilds/${snowflake(guildId, \"Guild ID\")}/commands/${snowflake(commandId, \"Command ID\")}`,\n /** Returns an interaction callback endpoint. @param interactionId Interaction identifier. @param token Interaction token. @returns Interaction callback route. */\n interactionCallback: (interactionId: string, token: string) =>\n `/interactions/${snowflake(interactionId, \"Interaction ID\")}/${encodeURIComponent(token)}/callback`,\n /** Returns the original interaction response endpoint. @param applicationId Application identifier. @param token Interaction token. @returns Original-response route. */\n interactionOriginalResponse: (applicationId: string, token: string) =>\n `/webhooks/${snowflake(applicationId, \"Application ID\")}/${encodeURIComponent(token)}/messages/@original`,\n\n /** Returns public archived threads for a channel. @param channelId Channel identifier. @returns Public-archive route. */\n channelPublicArchivedThreads: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/threads/archived/public`,\n /** Returns private archived threads for a channel. @param channelId Channel identifier. @returns Private-archive route. */\n channelPrivateArchivedThreads: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/threads/archived/private`,\n /** Returns the private archived threads the current user joined. @param channelId Channel identifier. */\n channelJoinedPrivateArchivedThreads: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/users/@me/threads/archived/private`,\n /** Returns one member of a thread; `\"@me\"` is the current user (PUT joins, DELETE leaves). @param threadId Thread identifier. @param userId User identifier or `\"@me\"`. */\n threadMember: (threadId: string, userId: string) =>\n `/channels/${snowflake(threadId, \"Thread ID\")}/thread-members/${userOrMe(userId)}`,\n /** Starts a typing indicator (POST). @param channelId Channel identifier. */\n channelTyping: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/typing`,\n /** Creates a thread that is not tied to a message, or a forum or media post (POST). @param channelId Channel identifier. */\n channelThreads: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/threads`,\n /** Returns a thread's members. @param threadId Thread identifier. @returns Thread-member route. */\n threadMembers: (threadId: string) =>\n `/channels/${snowflake(threadId, \"Thread ID\")}/thread-members`,\n /** Returns guild scheduled events. @param guildId Guild identifier. @returns Scheduled-event collection route. */\n guildScheduledEvents: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/scheduled-events`,\n /** Returns one guild scheduled event. @param guildId Guild identifier. @param eventId Event identifier. @returns Scheduled-event route. */\n guildScheduledEvent: (guildId: string, eventId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/scheduled-events/${snowflake(eventId, \"Event ID\")}`,\n /** Returns users subscribed to a scheduled event. @param guildId Guild identifier. @param eventId Event identifier. @returns Scheduled-event users route. */\n guildScheduledEventUsers: (guildId: string, eventId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/scheduled-events/${snowflake(eventId, \"Event ID\")}/users`,\n /** Returns a channel permission overwrite. @param channelId Channel identifier. @param overwriteId Role or user identifier. @returns Permission overwrite route. */\n channelPermission: (channelId: string, overwriteId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/permissions/${snowflake(overwriteId, \"Overwrite ID\")}`,\n /** Returns guild automod rules. @param guildId Guild identifier. @returns Automod-rule collection route. */\n guildAutoModerationRules: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/auto-moderation/rules`,\n /** Returns one automod rule. @param guildId Guild identifier. @param ruleId Rule identifier. @returns Automod-rule route. */\n guildAutoModerationRule: (guildId: string, ruleId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/auto-moderation/rules/${snowflake(ruleId, \"Rule ID\")}`,\n /** Returns current voice regions. @returns Voice-region route. */\n voiceRegions: () => \"/voice/regions\",\n /** Returns the generic Gateway endpoint. @returns Gateway route. */\n gateway: () => \"/gateway\",\n /** Returns the Gateway endpoint and recommended shard count for bots. @returns Gateway bot route. */\n gatewayBot: () => \"/gateway/bot\",\n\n // ─── Audit Logs ──────────────────────────────────────────────────────────────\n /** Returns audit log entries for a guild. @param guildId Guild identifier. @returns Audit log route. */\n guildAuditLog: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/audit-logs`,\n\n // ─── Guild Members (bulk fetch) ───────────────────────────────────────────────\n /** Returns members in a guild (paginated). @param guildId Guild identifier. @returns Members collection route. */\n guildMembers: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members`,\n\n /** Searches members by nickname or username. @param guildId Guild identifier. */\n guildMembersSearch: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/search`,\n /** Edits the bot's own member, e.g. its nickname (PATCH). @param guildId Guild identifier. */\n guildMemberMe: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/@me`,\n /** Bans up to 200 users at once (POST). @param guildId Guild identifier. */\n guildBulkBan: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bulk-ban`,\n /** Edits a voice state in a stage channel (PATCH); `\"@me\"` is the bot. @param guildId Guild identifier. @param userId User identifier or `\"@me\"`. */\n guildVoiceState: (guildId: string, userId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/voice-states/${userOrMe(userId)}`,\n /** Removes an integration from a guild (DELETE). @param guildId Guild identifier. @param integrationId Integration identifier. */\n guildIntegration: (guildId: string, integrationId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/integrations/${snowflake(integrationId, \"Integration ID\")}`,\n /** Returns the guilds the bot is in (paged with `before`, `after`, `limit`). */\n userGuilds: () => \"/users/@me/guilds\",\n /** Returns the sticker packs Nitro users can use, or one pack. @param packId Optional pack identifier. */\n stickerPacks: (packId?: string) =>\n packId\n ? `/sticker-packs/${snowflake(packId, \"Sticker pack ID\")}`\n : \"/sticker-packs\",\n\n // ─── Welcome Screen & Onboarding ──────────────────────────────────────────────\n /** Returns the welcome screen for a guild. @param guildId Guild identifier. */\n guildWelcomeScreen: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/welcome-screen`,\n /** Returns the onboarding configuration for a guild. @param guildId Guild identifier. */\n guildOnboarding: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/onboarding`,\n\n // ─── Guild Prune ──────────────────────────────────────────────────────────────\n /** Returns or executes a prune operation for inactive members. @param guildId Guild identifier. @returns Prune route. */\n guildPrune: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/prune`,\n\n // ─── Stage Instances ──────────────────────────────────────────────────────────\n /** Returns the stage instances collection. @returns Stage instance root route. */\n stageInstances: () => \"/stage-instances\",\n /** Returns a stage instance by its channel. @param channelId Stage channel identifier. @returns Stage instance route. */\n stageInstanceByChannel: (channelId: string) =>\n `/stage-instances/${snowflake(channelId, \"Channel ID\")}`,\n\n // ─── Invites ──────────────────────────────────────────────────────────────────\n /** Returns an invite by code. @param code Invite code. @returns Invite route. */\n invite: (code: string) => `/invites/${encodeURIComponent(code)}`,\n /** Returns the guild vanity invite metadata. @param guildId Guild identifier. @returns Vanity URL route. */\n guildVanity: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/vanity-url`,\n\n // ─── Emojis ───────────────────────────────────────────────────────────────────\n /** Returns guild emojis, or a specific emoji. @param guildId Guild identifier. @param emojiId Optional emoji identifier. */\n guildEmoji: (guildId: string, emojiId?: string) =>\n emojiId\n ? `/guilds/${snowflake(guildId, \"Guild ID\")}/emojis/${snowflake(emojiId, \"Emoji ID\")}`\n : `/guilds/${snowflake(guildId, \"Guild ID\")}/emojis`,\n /** Returns application emojis, or a specific emoji. @param applicationId Application identifier. @param emojiId Optional emoji identifier. */\n applicationEmoji: (applicationId: string, emojiId?: string) =>\n emojiId\n ? `/applications/${snowflake(applicationId, \"Application ID\")}/emojis/${snowflake(emojiId, \"Emoji ID\")}`\n : `/applications/${snowflake(applicationId, \"Application ID\")}/emojis`,\n\n // ─── Polls ────────────────────────────────────────────────────────────────────\n /** Ends a poll early. @param channelId Channel identifier. @param messageId Poll message identifier. */\n pollExpire: (channelId: string, messageId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/polls/${snowflake(messageId, \"Message ID\")}/expire`,\n /** Returns the users who voted for a poll answer. @param answerId Answer identifier (an integer, not a snowflake). */\n pollAnswerVoters: (\n channelId: string,\n messageId: string,\n answerId: number,\n ) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/polls/${snowflake(messageId, \"Message ID\")}/answers/${integer(answerId, \"Answer ID\")}`,\n\n // ─── Stickers ─────────────────────────────────────────────────────────────────\n /** Returns guild stickers, or one sticker. */\n guildSticker: (guildId: string, stickerId?: string) =>\n stickerId\n ? `/guilds/${snowflake(guildId, \"Guild ID\")}/stickers/${snowflake(stickerId, \"Sticker ID\")}`\n : `/guilds/${snowflake(guildId, \"Guild ID\")}/stickers`,\n\n // ─── Soundboard ───────────────────────────────────────────────────────────────\n /** Plays a soundboard sound in a voice channel. */\n sendSoundboardSound: (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/send-soundboard-sound`,\n /** Returns Discord's default soundboard sounds. */\n soundboardDefaultSounds: () => \"/soundboard-default-sounds\",\n /** Returns guild soundboard sounds, or one sound. */\n guildSoundboardSound: (guildId: string, soundId?: string) =>\n soundId\n ? `/guilds/${snowflake(guildId, \"Guild ID\")}/soundboard-sounds/${snowflake(soundId, \"Sound ID\")}`\n : `/guilds/${snowflake(guildId, \"Guild ID\")}/soundboard-sounds`,\n\n // ─── Monetization ─────────────────────────────────────────────────────────────\n /** Returns an application's SKUs. */\n applicationSkus: (applicationId: string) =>\n `/applications/${snowflake(applicationId, \"Application ID\")}/skus`,\n /** Returns an application's entitlements, or one entitlement. */\n applicationEntitlement: (applicationId: string, entitlementId?: string) =>\n entitlementId\n ? `/applications/${snowflake(applicationId, \"Application ID\")}/entitlements/${snowflake(entitlementId, \"Entitlement ID\")}`\n : `/applications/${snowflake(applicationId, \"Application ID\")}/entitlements`,\n /** Marks a one-time-purchase entitlement as consumed. */\n consumeEntitlement: (applicationId: string, entitlementId: string) =>\n `/applications/${snowflake(applicationId, \"Application ID\")}/entitlements/${snowflake(entitlementId, \"Entitlement ID\")}/consume`,\n /** Returns a SKU's subscriptions, or one subscription. */\n skuSubscription: (skuId: string, subscriptionId?: string) =>\n subscriptionId\n ? `/skus/${snowflake(skuId, \"SKU ID\")}/subscriptions/${snowflake(subscriptionId, \"Subscription ID\")}`\n : `/skus/${snowflake(skuId, \"SKU ID\")}/subscriptions`,\n} as const;\n\n/** Validates a non-negative integer route parameter. */\n/** A user id, or `\"@me\"` for the current user. */\nfunction userOrMe(value: string): string {\n return value === \"@me\" ? value : snowflake(value, \"User ID\");\n}\n\nfunction integer(value: number, field: string): string {\n if (!Number.isInteger(value) || value < 0)\n throw new TypeError(`${field} must be a non-negative integer.`);\n return String(value);\n}\n\n/** Validates a Discord snowflake route parameter. Accepts any decimal id that fits an\n * unsigned 64-bit integer, matching the snowflake rule `BaseStructure` applies in\n * `@lunibee/structures`. The guarantee this provides is that no non-numeric segment can\n * reshape the request path; it deliberately does not police id length, because Discord\n * snowflakes are 17 digits for early 2015 ids and reach 20 digits as the epoch advances.\n * @param value Candidate identifier. @param field Human-readable field name. @returns The validated identifier. @throws {TypeError} If the identifier is not a Discord snowflake. */\nfunction snowflake(value: string, field: string): string {\n if (!/^\\d{1,20}$/.test(value))\n throw new TypeError(`${field} must be a valid Discord snowflake.`);\n return value;\n}\n",
|
|
13
16
|
"/**\n * Discord JSON error codes that bots commonly handle, by name, for comparing\n * with `RESTError.code`: `if (error.code === RESTErrorCode.UnknownMessage) …`.\n *\n * Values are from Discord's \"JSON Error Codes\" table\n * (discord/discord-api-docs, developers/topics/opcodes-and-status-codes.mdx,\n * checked at commit 86f0a46). Any other code is still available as a number\n * on `RESTError.code`.\n */\nexport const RESTErrorCode = {\n UnknownAccount: 10001,\n UnknownChannel: 10003,\n UnknownGuild: 10004,\n UnknownInvite: 10006,\n UnknownMember: 10007,\n UnknownMessage: 10008,\n UnknownPermissionOverwrite: 10009,\n UnknownRole: 10011,\n UnknownUser: 10013,\n UnknownEmoji: 10014,\n UnknownWebhook: 10015,\n UnknownBan: 10026,\n UnknownSticker: 10060,\n UnknownInteraction: 10062,\n UnknownScheduledEvent: 10070,\n BotsCannotUseEndpoint: 20001,\n MaxGuilds: 30001,\n MaxPins: 30003,\n MaxRoles: 30005,\n MaxWebhooks: 30007,\n MaxEmojis: 30008,\n MaxReactions: 30010,\n MaxChannels: 30013,\n InteractionAlreadyAcknowledged: 40060,\n MissingAccess: 50001,\n CannotExecuteInDM: 50003,\n CannotEditOthersMessage: 50005,\n CannotSendEmptyMessage: 50006,\n CannotSendMessagesToUser: 50007,\n MissingPermissions: 50013,\n InvalidToken: 50014,\n MessageTooOldToBulkDelete: 50034,\n InvalidFormBody: 50035,\n ThreadArchived: 50083,\n CannotReplyWithoutReadHistory: 160002,\n} as const;\n/** One {@link RESTErrorCode} value, usable as a type. */\nexport type RESTErrorCode = (typeof RESTErrorCode)[keyof typeof RESTErrorCode];\n",
|
|
14
|
-
"import {\n MemoryRateLimitStore,\n type RateLimitStore,\n type BucketState,\n type Reservation,\n} from \"./store.js\";\n\n/**\n * Atomically consumes one unit of a bucket's allowance.\n *\n * Runs inside Redis so the read, the decrement and the write cannot interleave\n * across workers — the whole point of the reservation. Grants when the bucket\n * is unknown or its window has elapsed, because an unknown bucket is only\n * discovered by sending; refusing would deadlock the route across the fleet.\n *\n * `KEYS[1]` bucket key · `ARGV[1]` current time in ms.\n * Returns `{granted, retryAfterMs}`.\n */\nconst RESERVE_SCRIPT = `\nlocal raw = redis.call('GET', KEYS[1])\nif not raw then return {1, 0} end\nlocal ok, state = pcall(cjson.decode, raw)\nif not ok or type(state) ~= 'table' or type(state.resetAt) ~= 'number' then\n return {1, 0}\nend\nlocal now = tonumber(ARGV[1])\nlocal wait = state.resetAt - now\nif wait <= 0 then return {1, 0} end\nif type(state.remaining) == 'number' and state.remaining > 0 then\n state.remaining = state.remaining - 1\n redis.call('SET', KEYS[1], cjson.encode(state), 'PX', math.ceil(wait))\n return {1, 0}\nend\nreturn {0, math.ceil(wait)}\n`;\n\n/**\n * A Redis client interface that matches the subset of ioredis\n * methods needed for the RedisRateLimitStore.\n */\nexport interface MinimalRedisClient {\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<Array<string | null>>;\n set(\n key: string,\n value: string | number,\n mode?: string,\n duration?: number,\n ): Promise<string | null>;\n del(...keys: string[]): Promise<number>;\n /**\n * Optional Lua evaluation (ioredis/node-redis compatible). When present,\n * {@link RedisRateLimitStore.reserve} becomes available and the limiter\n * reserves allowance atomically instead of racing on observed state.\n */\n eval?(\n script: string,\n numKeys: number,\n ...args: Array<string | number>\n ): Promise<unknown>;\n}\n\nexport interface RedisRateLimitStoreOptions {\n /** The Redis client instance (e.g. ioredis). */\n client: MinimalRedisClient;\n /** Prefix to prepend to all Redis keys. Defaults to 'lunibee:rest:' */\n prefix?: string;\n /** Called whenever a Redis operation fails before local fallback behavior is used. */\n onError?: (operation: string, error: unknown) => void;\n}\n\n/**\n * Redis-backed implementation of RateLimitStore for distributed rate limit synchronization.\n * Allows multiple shards or workers to share Discord API rate limits seamlessly.\n */\nexport class RedisRateLimitStore implements RateLimitStore {\n readonly #client: MinimalRedisClient;\n readonly #prefix: string;\n readonly #onErrorCallback?: RedisRateLimitStoreOptions[\"onError\"];\n /**\n * In-process mirror of every write. Reads fall back to it when Redis is\n * unreachable: answering \"no limit known\" would drop every worker to\n * unlimited sending at the same moment, which is how a fleet earns a\n * Cloudflare ban during a Redis blip.\n */\n readonly #local = new MemoryRateLimitStore();\n #lastError?: unknown;\n\n public constructor(options: RedisRateLimitStoreOptions) {\n this.#client = options.client;\n this.#prefix = options.prefix ?? \"lunibee:rest:\";\n this.#onErrorCallback = options.onError;\n // The limiter detects reservation support by presence, so the method\n // exists only when the client can actually run the script atomically.\n // It is assigned here rather than declared on the prototype: a\n // prototype method cannot be removed from one instance.\n if (typeof options.client.eval === \"function\")\n this.reserve = (key) => this.#reserve(key);\n }\n\n /** The most recent Redis error, if any. */\n public get lastError(): unknown {\n return this.#lastError;\n }\n\n /** Whether no Redis operation has failed since construction or the last successful health check. */\n public isHealthy(): boolean {\n return this.#lastError === undefined;\n }\n\n /** Logs and surfaces a Redis failure while retaining safe local fallback behavior. */\n #onError(operation: string, error: unknown): void {\n this.#lastError = error;\n console.warn(\n `[lunibee/rest] Redis rate-limit store ${operation} failed; ` +\n `falling back to local limiting for this call.`,\n error,\n );\n try {\n this.#onErrorCallback?.(operation, error);\n } catch {\n // User-provided diagnostics must never break REST operations.\n }\n }\n\n /** Marks the store healthy after a successful Redis operation. */\n #markHealthy(): void {\n this.#lastError = undefined;\n }\n\n public async getBucketHash(route: string): Promise<string | undefined> {\n try {\n const hash = await this.#client.get(\n `${this.#prefix}route:${route}`,\n );\n this.#markHealthy();\n return hash ?? undefined;\n } catch (error) {\n this.#onError(\"getBucketHash\", error);\n return this.#local.getBucketHash(route);\n }\n }\n\n public async setBucketHash(route: string, hash: string): Promise<void> {\n this.#local.setBucketHash(route, hash);\n try {\n await this.#client.set(\n `${this.#prefix}route:${route}`,\n hash,\n \"EX\",\n 604800,\n );\n this.#markHealthy();\n } catch (error) {\n this.#onError(\"setBucketHash\", error);\n }\n }\n\n public async getBucket(key: string): Promise<BucketState | undefined> {\n try {\n const data = await this.#client.get(`${this.#prefix}bucket:${key}`);\n this.#markHealthy();\n if (!data) return undefined;\n return JSON.parse(data) as BucketState;\n } catch (error) {\n this.#onError(\"getBucket\", error);\n return this.#local.getBucket(key);\n }\n }\n\n public async updateBucket(key: string, state: BucketState): Promise<void> {\n this.#local.updateBucket(key, state);\n const bucketKey = `${this.#prefix}bucket:${key}`;\n // Expired buckets hold no useful limit info; drop them instead of\n // persisting stale state under a clamped 1s TTL.\n const ttl = Math.ceil((state.resetAt - Date.now()) / 1000);\n try {\n if (ttl <= 0) {\n await this.#client.del(bucketKey);\n this.#markHealthy();\n return;\n }\n await this.#client.set(bucketKey, JSON.stringify(state), \"EX\", ttl);\n this.#markHealthy();\n } catch (error) {\n this.#onError(\"updateBucket\", error);\n }\n }\n\n /** Whether the backing client supports atomic reservations. */\n public get supportsReservation(): boolean {\n return typeof this.#client.eval === \"function\";\n }\n\n /**\n * Atomically consumes one unit of a bucket's allowance across the fleet.\n *\n * Assigned in the constructor only when the client exposes `eval`, so a\n * client that cannot reserve atomically does not advertise that it can.\n * A Redis failure falls back to the local mirror rather than blocking:\n * refusing every request during an outage would stall the bot.\n */\n public reserve?: (key: string) => Promise<Reservation>;\n\n async #reserve(key: string): Promise<Reservation> {\n try {\n const result = (await this.#client.eval!(\n RESERVE_SCRIPT,\n 1,\n `${this.#prefix}bucket:${key}`,\n Date.now(),\n )) as [number, number] | undefined;\n this.#markHealthy();\n if (!Array.isArray(result)) return { granted: true };\n const [granted, retryAfterMs] = result;\n return Number(granted) === 1\n ? { granted: true }\n : { granted: false, retryAfterMs: Number(retryAfterMs) || 0 };\n } catch (error) {\n this.#onError(\"reserve\", error);\n return this.#local.reserve(key);\n }\n }\n\n public async getGlobalReset(): Promise<number> {\n try {\n const resetAt = await this.#client.get(`${this.#prefix}global`);\n this.#markHealthy();\n return resetAt ? Number(resetAt) : 0;\n } catch (error) {\n this.#onError(\"getGlobalReset\", error);\n return this.#local.getGlobalReset();\n }\n }\n\n public async setGlobalReset(resetAt: number): Promise<void> {\n this.#local.setGlobalReset(resetAt);\n const globalKey = `${this.#prefix}global`;\n const ttl = Math.ceil((resetAt - Date.now()) / 1000);\n try {\n if (ttl <= 0) {\n await this.#client.del(globalKey);\n this.#markHealthy();\n return;\n }\n await this.#client.set(globalKey, String(resetAt), \"EX\", ttl);\n this.#markHealthy();\n } catch (error) {\n this.#onError(\"setGlobalReset\", error);\n }\n }\n}\n",
|
|
15
|
-
"import { REST, type RESTFileAttachment } from \"./index.js\";\nimport { Routes } from \"./routes.js\";\nimport type {
|
|
16
|
-
"/** Library version and source URL used for the Discord-compliant User-Agent. */\nimport packageJson from \"../package.json\" with { type: \"json\" };\nconst LIBRARY_VERSION = packageJson.version;\nconst LIBRARY_URL = \"https://github.com/Ekretos/lunibee\";\nconst USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${LIBRARY_VERSION})`;\n\nimport { MemoryRateLimitStore, type RateLimitStore } from \"./store.js\";\nimport { RESTError, abortError, redactPath, sleep } from \"./errors.js\";\nimport {\n createRouteKey,\n serializeQuery,\n type RESTQuery,\n type RouteKey,\n} from \"./route.js\";\nimport { RequestScheduler } from \"./scheduler.js\";\nimport { RateLimiter } from \"./limiter.js\";\nimport { HttpTransport, TransportError } from \"./transport.js\";\nimport { ResponseDecoder } from \"./decoder.js\";\n\n/**\n * Refuses a path that URL normalization would rewrite into another route:\n * `.` / `..` segments (also percent-encoded) and backslashes. Without this,\n * an id like `../../channels/1` sends the bot token to a route the caller\n * never asked for. The path is left out of the message: it may hold a token.\n */\nfunction assertSafePath(path: string): void {\n const end = path.search(/[?#]/);\n const pathname = end === -1 ? path : path.slice(0, end);\n if (pathname.includes(\"\\\\\"))\n throw new TypeError(\"REST paths must not contain backslashes.\");\n for (const segment of pathname.split(\"/\")) {\n let decoded: string;\n try {\n decoded = decodeURIComponent(segment);\n } catch {\n throw new TypeError(\"REST path has malformed percent-encoding.\");\n }\n if (decoded === \".\" || decoded === \"..\")\n throw new TypeError(\n \"REST paths must not contain '.' or '..' segments.\",\n );\n }\n}\n\n/** A file attachment sent as part of a multipart REST request. */\nexport interface RESTFileAttachment {\n name: string;\n data: Blob | Uint8Array | ArrayBuffer;\n contentType?: string;\n}\n\n/**\n * Options controlling an individual REST request.\n *\n * Lunibee's canonical call shape is positional (`post(path, body)`); these options\n * add the Discord.js-`@discordjs/rest`-familiar per-request knobs (`query`, `headers`,\n * `reason`, `auth`) additively without changing that positional default.\n */\nexport interface RESTRequestOptions {\n /** Abort signal used to cancel the request and any queued wait. */\n signal?: AbortSignal;\n /** Query-string appended to the path (`URLSearchParams`, a record, or a raw string). */\n query?: RESTQuery;\n /** Extra request headers merged in (library headers such as `Authorization` win). */\n headers?: Record<string, string>;\n /** Audit-log reason, sent as the `X-Audit-Log-Reason` header. */\n reason?: string;\n /** Set `false` to omit the `Authorization` header for this request. Defaults to `true`. */\n auth?: boolean;\n}\n\n/**\n * Discord.js-familiar request payload. Superset of {@link RESTRequestOptions} that also\n * carries the `body` and `files`, matching `@discordjs/rest`'s `RequestData`. It powers the\n * additive `method(path, { body })` overloads without breaking the positional signature.\n */\nexport interface RequestData extends RESTRequestOptions {\n /** Request body. Encoded as JSON unless `files` are present (then multipart). */\n body?: unknown;\n /** File attachments; presence upgrades the request to `multipart/form-data`. */\n files?: RESTFileAttachment[];\n}\n\n/** Keys recognised on a {@link RequestData} wrapper, used to disambiguate it from a raw body. */\nconst REQUEST_DATA_KEYS = new Set([\n \"body\",\n \"files\",\n \"query\",\n \"headers\",\n \"reason\",\n \"auth\",\n \"signal\",\n]);\n\n/**\n * Detects a Discord.js-style `RequestData` wrapper so verb helpers can accept both\n * `post(path, rawBody)` (Lunibee-canonical) and `post(path, { body })` (Discord.js-familiar).\n * Conservative by design: only a plain object whose every own key is a known `RequestData`\n * key counts — a raw Discord payload (which always carries entity keys like `content`/`name`)\n * never matches, so existing positional callers are unaffected.\n */\nfunction isRequestData(value: unknown): value is RequestData {\n if (value === null || typeof value !== \"object\") return false;\n if (Array.isArray(value)) return false;\n if (typeof FormData !== \"undefined\" && value instanceof FormData)\n return false;\n if (\n value instanceof Blob ||\n value instanceof Uint8Array ||\n value instanceof ArrayBuffer\n )\n return false;\n const keys = Object.keys(value);\n if (keys.length === 0) return false;\n return keys.every((key) => REQUEST_DATA_KEYS.has(key));\n}\n\n// ─── REST Hooks ───────────────────────────────────────────────────────────────\n\n/** Context passed to `RESTHooks.onRequest`. */\nexport interface RESTRequestContext {\n method: string;\n path: string;\n attempt: number;\n}\n/** Context passed to `RESTHooks.onResponse`. */\nexport interface RESTResponseContext {\n method: string;\n path: string;\n status: number;\n durationMs: number;\n}\n/** Context passed to `RESTHooks.onRateLimit`. */\nexport interface RESTRateLimitContext {\n method: string;\n path: string;\n retryAfterMs: number;\n global: boolean;\n bucket?: string;\n}\n/** Context passed to `RESTHooks.onRetry`. */\nexport interface RESTRetryContext {\n method: string;\n path: string;\n attempt: number;\n status: number;\n delayMs: number;\n}\n\n/**\n * Lightweight callback hooks for REST request lifecycle events.\n *\n * All callbacks are optional. They are dispatched in isolation from the request\n * path (deferred to a microtask, and any thrown error is swallowed), so a hook\n * cannot break or reject a request. Even so, hooks **must** be fast and\n * non-blocking: heavy synchronous work in a hook still steals event-loop time\n * from in-flight requests. Offload anything expensive to your own queue.\n */\nexport interface RESTHooks {\n /** Called just before every HTTP attempt. */\n onRequest?(ctx: RESTRequestContext): void;\n /** Called after a successful or failed response is received. */\n onResponse?(ctx: RESTResponseContext): void;\n /** Called when a rate limit is encountered and the client is about to wait. */\n onRateLimit?(ctx: RESTRateLimitContext): void;\n /** Called when a failed request is about to be retried. */\n onRetry?(ctx: RESTRetryContext): void;\n}\n\n/** Configures which REST failures may be retried. */\nexport interface RetryPolicy {\n /** Maximum retry attempts. */\n maxRetries: number;\n /** Determines whether a request may be retried. @param method HTTP method. @param status HTTP status, or zero for transport errors. @returns True when retrying is safe under the policy. */\n shouldRetry(method: string, status: number): boolean;\n /** Calculates delay before the next attempt. @param attempt Zero-based retry number. @param retryAfter Server-provided delay, when available. @returns Delay in milliseconds. */\n getDelay(attempt: number, retryAfter?: number): number;\n}\n/** Creates the default conservative REST retry policy. @param maxRetries Maximum retries. @returns Retry policy that retries rate limits and idempotent transient failures. @throws {TypeError} If maxRetries is negative or not finite. */\nexport function createRetryPolicy(maxRetries = 2): RetryPolicy {\n if (!Number.isFinite(maxRetries) || maxRetries < 0)\n throw new TypeError(\"maxRetries must be a non-negative finite number.\");\n return {\n maxRetries: Math.floor(maxRetries),\n shouldRetry(method, status) {\n const normalized = method.toUpperCase();\n const idempotent = [\"GET\", \"HEAD\", \"PUT\", \"DELETE\"].includes(\n normalized,\n );\n // Status 0 is a transport failure (DNS, reset connection, TLS):\n // no response reached Discord's handlers, so replaying an\n // idempotent request is as safe as replaying it after a 5xx.\n if (status === 0) return idempotent;\n return (\n status === 429 || (status >= 500 && status <= 599 && idempotent)\n );\n },\n getDelay(attempt, retryAfter) {\n if (retryAfter !== undefined) return Math.max(0, retryAfter * 1000);\n return Math.min(10_000, 500 * 2 ** attempt) + Math.random() * 250;\n },\n };\n}\n\n/** Bun-native REST transport with Discord bucket-aware rate limiting, retries, and cancellation. */\nexport class REST {\n readonly #retryPolicy: RetryPolicy;\n #token?: string;\n /** Rate-limit state: when a request may go out, what a response says. */\n readonly #limiter: RateLimiter;\n /** Per-bucket ordering. */\n readonly #scheduler = new RequestScheduler();\n /** One HTTP attempt, with timeout and cancellation. */\n readonly #transport: HttpTransport;\n /** Response payload and error metadata. */\n readonly #decoder = new ResponseDecoder();\n #hooks: RESTHooks = {};\n /** Whether requests on a known bucket may run concurrently. */\n readonly #concurrentBuckets: boolean;\n /** Creates a REST transport. @param options Transport configuration. @throws {TypeError} If retry configuration is invalid. */\n public constructor(\n options: {\n token?: string;\n timeout?: number;\n retries?: number;\n retryPolicy?: RetryPolicy;\n baseURL?: string;\n hooks?: RESTHooks;\n store?: RateLimitStore;\n /** Replaces the HTTP stage, e.g. with a recording transport in tests. */\n transport?: HttpTransport;\n /**\n * Lets requests on a bucket whose limit is known run concurrently, up\n * to its `remaining` allowance, instead of one at a time. Requires a\n * store with `reserve`. Requests on one bucket may then complete out\n * of order (e.g. messages to one channel). Defaults to `false`.\n */\n concurrentBuckets?: boolean;\n } = {},\n ) {\n this.#token = options.token;\n this.#retryPolicy =\n options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);\n this.#transport =\n options.transport ??\n new HttpTransport({\n baseURL: options.baseURL,\n timeout: options.timeout,\n });\n const store = options.store ?? new MemoryRateLimitStore();\n this.#concurrentBuckets =\n (options.concurrentBuckets ?? true) &&\n typeof store.reserve === \"function\";\n this.#limiter = new RateLimiter(store, {\n concurrent: this.#concurrentBuckets,\n });\n if (options.hooks) this.#hooks = options.hooks;\n }\n /** The rate-limit store backing this transport. */\n public get store(): RateLimitStore {\n return this.#limiter.store;\n }\n /** Replaces the current hook set. Pass an empty object to clear all hooks. */\n public setHooks(hooks: RESTHooks): this {\n this.#hooks = hooks;\n return this;\n }\n /** Sets the authentication token. @param token Discord bot token. @returns Nothing. @throws {TypeError} If token is empty. */\n public setToken(token: string): void {\n if (!token.trim())\n throw new TypeError(\"A Discord bot token is required.\");\n this.#token = token;\n }\n /** Executes an HTTP request with Discord bucket/global rate limits, retry handling, and AbortSignal support.\n * Accepts a JSON body, a FormData body (for file uploads), or undefined.\n * @param method HTTP method.\n * @param path API path.\n * @param body Optional JSON body, FormData for file uploads, or undefined.\n * @param options Request options: cancellation `signal` plus the Discord.js-familiar\n * `query`, `headers`, `reason`, `auth`, and `files` (which upgrade to multipart).\n * @returns Decoded response body.\n * @throws {RESTError} If Discord or transport rejects the request.\n */\n public async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: RequestData = {},\n ): Promise<T> {\n if (!method.toUpperCase())\n throw new TypeError(\"REST method is required.\");\n if (!path.startsWith(\"/\"))\n throw new TypeError(\"REST paths must start with '/'.\");\n assertSafePath(path);\n const route = createRouteKey(method, path);\n // Upgrade to multipart when file attachments are supplied via options.\n const files = options.files;\n if (files && files.length > 0 && !(body instanceof FormData))\n body = this.#fileForm(body, files);\n const query = serializeQuery(options.query);\n const requestPath = query ? `${path}${query}` : path;\n\n // Discord scopes a rate limit by (bucket hash, major parameter): two\n // channels sharing an endpoint have independent limits, while two\n // different routes can share one hash. The key is therefore resolved\n // again once the slot is acquired, so a request that queued under its\n // route-derived key joins the shared bucket's queue if one was\n // discovered while it waited.\n return this.#scheduler.runResolved(\n () => this.#limiter.resolveBucketKey(route.route, route.major),\n options.signal,\n path,\n (bucketKey, release) =>\n this.#attempts<T>(\n route,\n requestPath,\n body,\n options,\n bucketKey,\n release,\n ),\n );\n }\n /**\n * Runs the retry loop for one scheduled request: acquire limits, send,\n * record what the response says, decode, and decide whether to try again.\n */\n async #attempts<T>(\n route: RouteKey,\n requestPath: string,\n body: unknown,\n options: RequestData,\n initialBucketKey: string,\n release: () => void,\n ): Promise<T> {\n const { method, path } = route;\n let bucketKey = initialBucketKey;\n for (\n let attempt = 0;\n attempt <= this.#retryPolicy.maxRetries;\n attempt++\n ) {\n await this.#limiter.acquire(bucketKey, options.signal, path);\n // The reservation already claimed this request's unit, so once the\n // bucket's window is known the next request need not wait for our\n // response. An unknown bucket stays serial until a response\n // reveals its limit.\n if (this.#concurrentBuckets) {\n const bucket = await this.#limiter.store.getBucket(bucketKey);\n if (bucket && bucket.resetAt > Date.now()) release();\n }\n this.#emit(this.#hooks.onRequest, { method, path, attempt });\n const requestStart = Date.now();\n let response: Response;\n try {\n response = await this.#transport.send({\n method,\n path: requestPath,\n headers: this.#headers(body, options),\n body: this.#encodeBody(body),\n signal: options.signal,\n });\n } catch (error) {\n if (error instanceof RESTError) throw error;\n if (options.signal?.aborted)\n throw abortError(path, options.signal.reason);\n const failure =\n error instanceof TransportError\n ? error\n : new TransportError(\n \"Discord REST request failed\",\n false,\n error,\n );\n if (\n !failure.timedOut &&\n attempt < this.#retryPolicy.maxRetries &&\n this.#retryPolicy.shouldRetry(method, 0)\n ) {\n await sleep(\n this.#retryPolicy.getDelay(attempt),\n options.signal,\n path,\n );\n continue;\n }\n throw new RESTError(failure.message, 0, undefined, undefined, {\n method,\n path,\n cause: failure.cause ?? failure,\n kind: failure.timedOut ? \"timeout\" : \"network\",\n });\n }\n this.#emit(this.#hooks.onResponse, {\n method,\n path,\n status: response.status,\n durationMs: Date.now() - requestStart,\n });\n bucketKey = await this.#limiter.applyResponse(\n response,\n bucketKey,\n route.route,\n route.major,\n );\n const payload = await this.#decoder.read(response);\n if (response.ok)\n return (response.status === 204 ? undefined : payload) as T;\n\n const data = this.#decoder.errorData(payload);\n const retryAfter =\n response.status === 429\n ? this.#decoder.retryAfter(response, data)\n : undefined;\n if (data.global && retryAfter !== undefined)\n await this.#limiter.noteGlobalReset(\n Date.now() + retryAfter * 1000,\n );\n // Fire for ANY 429, including a global limit that arrives without a\n // Retry-After header; fall back to 1s in that case.\n if (response.status === 429)\n this.#emit(this.#hooks.onRateLimit, {\n method,\n path,\n retryAfterMs: (retryAfter ?? 1) * 1000,\n global: data.global ?? false,\n bucket: await this.#limiter.store.getBucketHash(\n route.route,\n ),\n });\n if (\n attempt < this.#retryPolicy.maxRetries &&\n this.#retryPolicy.shouldRetry(method, response.status)\n ) {\n const delay = this.#retryPolicy.getDelay(attempt, retryAfter);\n this.#emit(this.#hooks.onRetry, {\n method,\n path,\n attempt,\n status: response.status,\n delayMs: delay,\n });\n await sleep(delay, options.signal, path);\n continue;\n }\n throw new RESTError(\n data.message ??\n response.statusText ??\n `Discord REST request failed with status ${response.status}`,\n response.status,\n data.code,\n payload,\n { method, path },\n );\n }\n throw new RESTError(\n \"Discord REST request exhausted its retry attempts\",\n 0,\n undefined,\n undefined,\n { method, path },\n );\n }\n /** Builds the header set for one attempt; library headers win over caller headers. */\n #headers(body: unknown, options: RequestData): Record<string, string> {\n const isFormData =\n typeof FormData !== \"undefined\" && body instanceof FormData;\n const headers: Record<string, string> = { ...options.headers };\n // Header names are case-insensitive: drop the caller's spelling first,\n // or `authorization` and `Authorization` are joined into one value.\n const set = (name: string, value: string): void => {\n for (const key of Object.keys(headers))\n if (key.toLowerCase() === name.toLowerCase())\n delete headers[key];\n headers[name] = value;\n };\n if (options.auth !== false && this.#token)\n set(\"Authorization\", `Bot ${this.#token}`);\n set(\"User-Agent\", USER_AGENT);\n if (options.reason !== undefined)\n set(\"X-Audit-Log-Reason\", encodeURIComponent(options.reason));\n if (!isFormData) set(\"Content-Type\", \"application/json\");\n return headers;\n }\n /** Encodes a body as multipart (passed through) or JSON. */\n #encodeBody(body: unknown): BodyInit | undefined {\n if (typeof FormData !== \"undefined\" && body instanceof FormData)\n return body;\n return body === undefined ? undefined : JSON.stringify(body);\n }\n /**\n * Normalizes a verb helper's second argument into `(body, options)`.\n * Supports both `verb(path, rawBody, options?)` (Lunibee-canonical positional) and the\n * Discord.js-familiar `verb(path, { body, query, headers, reason, files })` wrapper.\n */\n #dispatch<T>(\n method: string,\n path: string,\n bodyOrOptions?: unknown,\n options?: RequestData,\n ): Promise<T> {\n if (options === undefined && isRequestData(bodyOrOptions))\n return this.request<T>(\n method,\n path,\n bodyOrOptions.body,\n bodyOrOptions,\n );\n return this.request<T>(method, path, bodyOrOptions, options);\n }\n /** Sends a GET request. @param path API path. @param options Request options (`query`, `headers`, `signal`, …). @returns Decoded response. @throws {RESTError} When the request fails. */\n public get<T>(path: string, options?: RequestData): Promise<T> {\n return this.request<T>(\"GET\", path, options?.body, options);\n }\n /** Sends a POST request. Accepts a raw body (`post(path, body)`) or a Discord.js-familiar `post(path, { body })`. @param path API path. @param body Optional JSON body, or a `RequestData` wrapper. @param options Request options. @returns Decoded response. @throws {RESTError} When the request fails. */\n public post<T>(path: string, options: RequestData): Promise<T>;\n public post<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public post<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T> {\n return this.#dispatch<T>(\"POST\", path, body, options);\n }\n /** Sends a PATCH request. Accepts a raw body (`patch(path, body)`) or a Discord.js-familiar `patch(path, { body })`. @param path API path. @param body Optional JSON body, or a `RequestData` wrapper. @param options Request options. @returns Decoded response. @throws {RESTError} When the request fails. */\n public patch<T>(path: string, options: RequestData): Promise<T>;\n public patch<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public patch<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T> {\n return this.#dispatch<T>(\"PATCH\", path, body, options);\n }\n /** Sends a PUT request. Accepts a raw body (`put(path, body)`) or a Discord.js-familiar `put(path, { body })`. @param path API path. @param body Optional JSON body, or a `RequestData` wrapper. @param options Request options. @returns Decoded response. @throws {RESTError} When the request fails. */\n public put<T>(path: string, options: RequestData): Promise<T>;\n public put<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public put<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T> {\n return this.#dispatch<T>(\"PUT\", path, body, options);\n }\n /** Sends a DELETE request. @param path API path. @param options Request options (`reason`, `headers`, `signal`, …). @returns Decoded response. @throws {RESTError} When the request fails. */\n public delete<T>(path: string, options?: RequestData): Promise<T> {\n return this.request<T>(\"DELETE\", path, options?.body, options);\n }\n /**\n * Builds a multipart form with a JSON payload and file attachments.\n */\n #fileForm(payload: unknown, files: RESTFileAttachment[]): FormData {\n const form = new FormData();\n form.append(\"payload_json\", JSON.stringify(payload));\n for (let i = 0; i < files.length; i++) {\n const file = files[i]!;\n const blob =\n file.data instanceof Blob\n ? file.data\n : new Blob([file.data as ArrayBuffer], {\n type: file.contentType ?? \"application/octet-stream\",\n });\n form.append(`files[${i}]`, blob, file.name);\n }\n return form;\n }\n\n /**\n * Sends a multipart/form-data POST request for file uploads.\n * Attaches a JSON payload as `payload_json` and files as additional fields.\n * @param path API path.\n * @param payload JSON payload (e.g. message content, embeds).\n * @param files Array of file attachments.\n * @param options Cancellation options.\n * @returns Decoded response.\n * @throws {RESTError} When the request fails.\n */\n public postWithFiles<T>(\n path: string,\n payload: unknown,\n files: RESTFileAttachment[],\n options?: RESTRequestOptions,\n ): Promise<T> {\n return this.request<T>(\n \"POST\",\n path,\n this.#fileForm(payload, files),\n options,\n );\n }\n\n /**\n * Sends a multipart/form-data PATCH request for editing messages with files.\n * @param path API path.\n * @param payload JSON payload.\n * @param files Array of file attachments.\n * @param options Cancellation options.\n * @returns Decoded response.\n * @throws {RESTError} When the request fails.\n */\n public patchWithFiles<T>(\n path: string,\n payload: unknown,\n files: RESTFileAttachment[],\n options?: RESTRequestOptions,\n ): Promise<T> {\n return this.request<T>(\n \"PATCH\",\n path,\n this.#fileForm(payload, files),\n options,\n );\n }\n /** Dispatches a lifecycle hook in isolation: deferred to a microtask so it\n * cannot block the request path, with any thrown error swallowed so a faulty\n * hook cannot reject the request. @param hook Optional callback. @param ctx Event context. */\n #emit<C>(hook: ((ctx: C) => void) | undefined, ctx: C): void {\n if (!hook) return;\n const withPath = ctx as { path?: unknown };\n if (typeof withPath.path === \"string\")\n ctx = { ...ctx, path: redactPath(withPath.path) };\n Promise.resolve()\n .then(() => hook(ctx))\n .catch(() => {});\n }\n}\n\nexport { Routes } from \"./routes.js\";\nexport {\n RESTError,\n abortError,\n redactPath,\n type RESTErrorKind,\n} from \"./errors.js\";\nexport { RESTErrorCode } from \"./error-codes.js\";\nexport {\n createRouteKey,\n normalizeRoutePath,\n majorParameter,\n scopeBucket,\n serializeQuery,\n type RouteKey,\n type RESTQuery,\n} from \"./route.js\";\nexport { RequestScheduler } from \"./scheduler.js\";\nexport { RateLimiter } from \"./limiter.js\";\nexport {\n HttpTransport,\n TransportError,\n type TransportRequest,\n type FetchLike,\n} from \"./transport.js\";\nexport { ResponseDecoder, type DecodedError } from \"./decoder.js\";\nexport {\n type RateLimitStore,\n type BucketState,\n type Reservation,\n MemoryRateLimitStore,\n} from \"./store.js\";\nexport {\n RedisRateLimitStore,\n type RedisRateLimitStoreOptions,\n type MinimalRedisClient,\n} from \"./redis.js\";\n\nexport {\n WebhookClient,\n type WebhookClientOptions,\n type WebhookMessageOptions,\n} from \"./webhook.js\";\n\n/**\n * Discord.js-familiar alias for {@link RESTError}. `@discordjs/rest` throws `DiscordAPIError`\n * on a Discord-rejected request; Lunibee keeps `RESTError` as the canonical class and exports\n * this alias so Discord.js-shaped `catch (err) { if (err instanceof DiscordAPIError) … }` works.\n */\nexport { RESTError as DiscordAPIError };\n"
|
|
17
|
+
"import { RESTError, sanitizeCause } from \"./errors.js\";\nimport {\n BUCKET_LINGER_MS,\n DEFAULT_PROBE_MS,\n GLOBAL_WINDOW_MS,\n MemoryRateLimitStore,\n PROBE_POLL_MS,\n type BucketState,\n type BucketUpdate,\n type RateLimitStore,\n type Reservation,\n type ReserveOptions,\n} from \"./store.js\";\nimport {\n emitTelemetry,\n type CoordinationMode,\n type RESTTelemetry,\n} from \"./telemetry.js\";\n\n/**\n * Shared Lua helpers. Every script reads the clock from Redis (`TIME`), so\n * workers whose clocks disagree still agree on when a window resets.\n */\nconst LUA_PRELUDE = `\nlocal function now_ms()\n local t = redis.call('TIME')\n return tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)\nend\nlocal function load(key)\n local raw = redis.call('GET', key)\n if not raw then return nil end\n local ok, s = pcall(cjson.decode, raw)\n if ok and type(s) == 'table' and type(s.resetAt) == 'number'\n and type(s.remaining) == 'number' then return s end\n return nil\nend\nlocal function save(key, s, now, linger)\n local ttl = math.max(1, math.ceil(s.resetAt - now)) + linger\n redis.call('SET', key, cjson.encode(s), 'PX', ttl)\nend\n`;\n\n/**\n * Atomically checks the global limit and consumes one unit of a bucket.\n * A port of `reserveState` in store.ts.\n *\n * `KEYS[1]` bucket · `KEYS[2]` global reset · `KEYS[3]` global request count\n * (both optional) · `ARGV` probe ms, poll ms, linger ms, global limit, window ms.\n * Returns `{1, resetAt, known}` or `{0, waitMs, reason}`.\n */\nexport const RESERVE_SCRIPT = `${LUA_PRELUDE}\nlocal now = now_ms()\nlocal glimit, gwindow = tonumber(ARGV[4]) or 0, tonumber(ARGV[5]) or 1000\nif #KEYS > 1 then\n local g = tonumber(redis.call('GET', KEYS[2]) or '0') or 0\n if g > now then return {0, math.ceil(g - now), 'global'} end\nend\nif #KEYS < 3 then glimit = 0 end\n-- A sliding log of send times: at most glimit in any gwindow span.\nif glimit > 0 then\n redis.call('ZREMRANGEBYSCORE', KEYS[3], '-inf', now - gwindow)\n if redis.call('ZCARD', KEYS[3]) >= glimit then\n local oldest = redis.call('ZRANGE', KEYS[3], 0, 0, 'WITHSCORES')\n return {0, math.max(1, tonumber(oldest[2]) + gwindow - now + 1), 'global'}\n end\nend\nlocal function count()\n if glimit > 0 then\n local seq = redis.call('ZCARD', KEYS[3])\n redis.call('ZADD', KEYS[3], now, now .. ':' .. seq)\n redis.call('PEXPIRE', KEYS[3], gwindow + 1000)\n end\nend\nlocal probe, poll, linger = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])\nlocal s = load(KEYS[1])\nif s and s.resetAt > now then\n if s.remaining > 0 then\n s.remaining = s.remaining - 1\n save(KEYS[1], s, now, linger)\n count()\n return {1, s.resetAt, 1}\n end\n local wait = s.resetAt - now\n if s.provisional then wait = math.min(wait, poll) end\n local reason = 'bucket'\n if s.probe then reason = 'probe' end\n return {0, math.ceil(wait), reason}\nend\nlocal resetAt = now + probe\nif s and type(s.limit) == 'number' and s.limit > 0 then\n save(KEYS[1], {remaining = s.limit - 1, resetAt = resetAt, limit = s.limit, provisional = true}, now, linger)\n count()\n return {1, resetAt, 1}\nend\nsave(KEYS[1], {remaining = 0, resetAt = resetAt, provisional = true, probe = true}, now, 0)\ncount()\nreturn {1, resetAt, 0}\n`;\n\n/**\n * Atomically merges a response into a bucket. A port of `reconcileState`.\n *\n * `KEYS[1]` bucket · `ARGV` remaining, reset-after ms, limit, window, retry ms\n * (each '' when absent), linger ms.\n */\nexport const RECONCILE_SCRIPT = `${LUA_PRELUDE}\nlocal now = now_ms()\nlocal rem, after, limit = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])\nlocal win, retry, linger = tonumber(ARGV[4]), tonumber(ARGV[5]), tonumber(ARGV[6])\nlocal tol = 50\nlocal incoming = nil\nif after then incoming = math.ceil(now + after) end\nlocal s = load(KEYS[1])\nlocal n\nif s and s.resetAt > now and not s.probe then\n n = s\n local stale = win and n.window and win < n.window - tol\n if not stale then\n if rem then n.remaining = math.min(n.remaining, rem) end\n if incoming then\n local newer = win and n.window and win > n.window + tol\n if n.provisional or newer then n.resetAt = incoming\n else n.resetAt = math.max(n.resetAt, incoming) end\n n.provisional = nil\n end\n if win then n.window = math.max(n.window or 0, win) end\n end\nelse\n if not rem and not retry then\n if s and s.probe then redis.call('DEL', KEYS[1]) end\n return 0\n end\n n = {remaining = rem or 0, resetAt = incoming or now}\n if s and type(s.limit) == 'number' then n.limit = s.limit end\n if win then n.window = win end\nend\nif limit and limit > 0 then n.limit = limit end\nif retry then\n n.remaining = 0\n n.resetAt = math.max(n.resetAt, math.ceil(now + retry))\n n.provisional = nil\nend\nsave(KEYS[1], n, now, linger)\nreturn 1\n`;\n\n/** Extends a global limit, never shortening it. `KEYS[1]` global · `ARGV[1]` wait ms. */\nexport const EXTEND_GLOBAL_SCRIPT = `${LUA_PRELUDE}\nlocal now = now_ms()\nlocal target = math.ceil(now + tonumber(ARGV[1]))\nlocal cur = tonumber(redis.call('GET', KEYS[1]) or '0') or 0\nif target > cur then\n redis.call('SET', KEYS[1], tostring(target), 'PX', math.max(1, target - now))\n return target - now\nend\nreturn math.max(0, cur - now)\n`;\n\n/** Clears a probe or refunds a unit. `KEYS[1]` bucket · `ARGV` resetAt, refund (1/0). */\nexport const RELEASE_SCRIPT = `${LUA_PRELUDE}\nlocal s = load(KEYS[1])\nif not s or s.resetAt ~= tonumber(ARGV[1]) then return 0 end\nif s.probe then\n redis.call('DEL', KEYS[1])\n return 1\nend\nif ARGV[2] == '1' then\n local cap = s.remaining + 1\n if type(s.limit) == 'number' then cap = math.min(cap, s.limit) end\n s.remaining = cap\n redis.call('SET', KEYS[1], cjson.encode(s), 'KEEPTTL')\n return 1\nend\nreturn 0\n`;\n\n/** Checks that scripting and `TIME` are available. */\nconst VERIFY_SCRIPT = \"return redis.call('TIME')[1]\";\n\n/**\n * A Redis client interface that matches the subset of ioredis\n * methods needed for the RedisRateLimitStore.\n */\nexport interface MinimalRedisClient {\n get(key: string): Promise<string | null>;\n mget(keys: string[]): Promise<Array<string | null>>;\n set(\n key: string,\n value: string | number,\n mode?: string,\n duration?: number,\n ): Promise<string | null>;\n del(...keys: string[]): Promise<number>;\n /**\n * Optional Lua evaluation (ioredis/node-redis compatible). When present,\n * the store reserves, reconciles and extends limits atomically; without\n * it the store is observational and workers can race on the last unit.\n */\n eval?(\n script: string,\n numKeys: number,\n ...args: Array<string | number>\n ): Promise<unknown>;\n}\n\n/**\n * Adapts Bun's built-in `Bun.RedisClient` (or anything with\n * `send(command, args)`) to {@link MinimalRedisClient}, with `eval`.\n *\n * ```ts\n * const store = new RedisRateLimitStore({\n * client: fromBunRedis(new Bun.RedisClient(process.env.REDIS_URL)),\n * });\n * ```\n */\nexport function fromBunRedis(client: {\n send(command: string, args: string[]): Promise<unknown>;\n}): MinimalRedisClient {\n return {\n get: (key) => client.send(\"GET\", [key]) as Promise<string | null>,\n mget: (keys) =>\n client.send(\"MGET\", keys) as Promise<Array<string | null>>,\n set: (key, value, mode, duration) =>\n client.send(\n \"SET\",\n mode === undefined\n ? [key, String(value)]\n : [key, String(value), mode, String(duration)],\n ) as Promise<string | null>,\n del: (...keys) => client.send(\"DEL\", keys) as Promise<number>,\n eval: (script, numKeys, ...args) =>\n client.send(\"EVAL\", [script, String(numKeys), ...args.map(String)]),\n };\n}\n\n/**\n * What the store does when Redis cannot be reached.\n *\n * - `degraded-local` (default): limit from this process's last known state,\n * report the mode change, and return to Redis when it answers again. Several\n * processes limiting alone can together exceed a limit.\n * - `fail-closed`: refuse to send. Requests fail with a `RESTError` of kind\n * `\"coordination\"` until Redis answers.\n */\nexport type RedisFailurePolicy = \"degraded-local\" | \"fail-closed\";\n\n/** Options for `RedisRateLimitStore`. */\nexport interface RedisRateLimitStoreOptions {\n /** The Redis client instance (e.g. ioredis, or `fromBunRedis(...)`). */\n client: MinimalRedisClient;\n /**\n * Prefix of every key. Defaults to `lunibee:rest:`; keys are then\n * versioned (`lunibee:rest:v1:…`). On Redis Cluster, put a hash tag in it\n * (`{lunibee}:rest:`) so one script's keys share a slot.\n */\n prefix?: string;\n /** Called whenever a Redis operation fails. */\n onError?: (operation: string, error: unknown) => void;\n /** What to do while Redis is unreachable. Default `\"degraded-local\"`. */\n failurePolicy?: RedisFailurePolicy;\n /** Longest one Redis command may take before it counts as failed, in ms. Default 2000. */\n commandTimeout?: number;\n /** Throw at construction when the client has no `eval` (no atomic reservations). Default false. */\n requireAtomic?: boolean;\n /** Receives `store` latency and `mode` events. */\n telemetry?: RESTTelemetry;\n}\n\n/** The store's health, for diagnostics endpoints. */\nexport interface RedisStoreDiagnostics {\n mode: CoordinationMode;\n failurePolicy: RedisFailurePolicy;\n atomic: boolean;\n failures: number;\n lastFailureAt?: number;\n lastError?: string;\n}\n\n/** Bumped when the stored shape changes; old keys are then simply ignored. */\nconst SCHEMA = \"v1\";\n\n/**\n * Redis-backed implementation of RateLimitStore for distributed rate limit\n * synchronization: workers sharing one Redis share every limit, reserve\n * allowance atomically, and merge responses atomically.\n */\nexport class RedisRateLimitStore implements RateLimitStore {\n readonly #client: MinimalRedisClient;\n readonly #prefix: string;\n readonly #onErrorCallback?: RedisRateLimitStoreOptions[\"onError\"];\n readonly #policy: RedisFailurePolicy;\n readonly #timeout: number;\n readonly #telemetry?: RESTTelemetry;\n readonly #atomic: boolean;\n /**\n * In-process mirror of every write. Reads fall back to it when Redis is\n * unreachable: answering \"no limit known\" would drop every worker to\n * unlimited sending at the same moment, which is how a fleet earns a\n * Cloudflare ban during a Redis blip.\n */\n readonly #local = new MemoryRateLimitStore();\n #lastError?: unknown;\n #lastFailureAt?: number;\n #failures = 0;\n #mode: CoordinationMode;\n\n public constructor(options: RedisRateLimitStoreOptions) {\n this.#client = options.client;\n this.#prefix = `${options.prefix ?? \"lunibee:rest:\"}${SCHEMA}:`;\n this.#onErrorCallback = options.onError;\n this.#policy = options.failurePolicy ?? \"degraded-local\";\n if (this.#policy !== \"degraded-local\" && this.#policy !== \"fail-closed\")\n throw new TypeError(\n 'failurePolicy must be \"degraded-local\" or \"fail-closed\".',\n );\n this.#timeout = options.commandTimeout ?? 2000;\n if (!(this.#timeout > 0))\n throw new RangeError(\"commandTimeout must be a positive number.\");\n this.#telemetry = options.telemetry;\n this.#atomic = typeof options.client.eval === \"function\";\n if (options.requireAtomic && !this.#atomic)\n throw new TypeError(\n \"requireAtomic: the Redis client has no eval method, so reservations cannot be atomic.\",\n );\n this.#mode = this.#atomic ? \"distributed\" : \"observational\";\n // The limiter detects these by presence, so they exist only when the\n // client can run them atomically. They are assigned here rather than\n // declared on the prototype: a prototype method cannot be removed\n // from one instance.\n if (this.#atomic) {\n this.reserve = (key, opts) => this.#reserve(key, opts);\n this.reconcile = (key, update) => this.#reconcile(key, update);\n this.extendGlobalReset = (scope, waitMs) =>\n this.#extendGlobal(scope, waitMs);\n this.release = (key, grant, refund) =>\n this.#release(key, grant, refund);\n this.reservesGlobal = true;\n }\n }\n\n /** The most recent Redis error, if any. */\n public get lastError(): unknown {\n return this.#lastError;\n }\n\n /** Whether no Redis operation has failed since construction or the last successful one. */\n public isHealthy(): boolean {\n return this.#lastError === undefined;\n }\n\n /** How limits are being enforced right now. */\n public get mode(): CoordinationMode {\n return this.#mode;\n }\n\n /** Whether the backing client supports atomic reservations. */\n public get supportsReservation(): boolean {\n return this.#atomic;\n }\n\n /** The store's current health. */\n public diagnostics(): RedisStoreDiagnostics {\n const error = this.#lastError;\n return {\n mode: this.#mode,\n failurePolicy: this.#policy,\n atomic: this.#atomic,\n failures: this.#failures,\n lastFailureAt: this.#lastFailureAt,\n lastError:\n error === undefined\n ? undefined\n : String(\n (sanitizeCause(error) as Error | undefined)\n ?.message ?? error,\n ),\n };\n }\n\n /**\n * Checks that the client can run the store's scripts (some managed Redis\n * services disable `EVAL`). Call it at startup to fail fast.\n * @throws {TypeError} If the client has no `eval`.\n * @throws {RESTError} Of kind `\"coordination\"`, if the script cannot run.\n */\n public async verify(): Promise<void> {\n if (!this.#atomic)\n throw new TypeError(\n \"The Redis client has no eval method: reservations cannot be atomic.\",\n );\n try {\n await this.#withTimeout(this.#client.eval!(VERIFY_SCRIPT, 0));\n } catch (error) {\n throw new RESTError(\n \"The Redis rate-limit store cannot run its scripts.\",\n 0,\n undefined,\n undefined,\n { kind: \"coordination\", cause: sanitizeCause(error) },\n );\n }\n }\n\n #setMode(mode: CoordinationMode): void {\n if (mode === this.#mode) return;\n const previous = this.#mode;\n this.#mode = mode;\n emitTelemetry(this.#telemetry, { type: \"mode\", mode, previous });\n console.warn(\n mode === \"degraded-local\"\n ? \"[lunibee/rest] Redis rate-limit store unreachable: limiting locally in this process until it answers.\"\n : mode === \"unavailable\"\n ? \"[lunibee/rest] Redis rate-limit store unreachable: requests fail until it answers (fail-closed).\"\n : \"[lunibee/rest] Redis rate-limit store reachable again: limits are shared.\",\n );\n }\n\n #withTimeout<T>(promise: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n `Redis command timed out after ${this.#timeout}ms`,\n ),\n ),\n this.#timeout,\n );\n }),\n ]).finally(() => clearTimeout(timer));\n }\n\n /**\n * Runs one Redis command with the timeout, telemetry and failure policy.\n * `enforcing` commands decide whether a request may be sent: under\n * `fail-closed` their failure throws. Others (writes after a response,\n * route hashes) fall back to the mirror under either policy, because\n * failing a request Discord already answered helps nobody.\n */\n async #run<T>(\n operation: string,\n command: () => Promise<T>,\n fallback: () => T,\n enforcing: boolean,\n ): Promise<T> {\n const started = performance.now();\n try {\n const value = await this.#withTimeout(command());\n emitTelemetry(this.#telemetry, {\n type: \"store\",\n operation,\n latencyMs: performance.now() - started,\n ok: true,\n });\n this.#lastError = undefined;\n this.#setMode(this.#atomic ? \"distributed\" : \"observational\");\n return value;\n } catch (error) {\n emitTelemetry(this.#telemetry, {\n type: \"store\",\n operation,\n latencyMs: performance.now() - started,\n ok: false,\n });\n this.#lastError = error;\n this.#lastFailureAt = Date.now();\n this.#failures++;\n try {\n this.#onErrorCallback?.(operation, error);\n } catch {\n // User-provided diagnostics must never break REST operations.\n }\n if (this.#policy === \"fail-closed\") {\n this.#setMode(\"unavailable\");\n if (enforcing)\n throw new RESTError(\n `The Redis rate-limit store is unavailable (${operation}); request not sent (failurePolicy: \"fail-closed\").`,\n 0,\n undefined,\n undefined,\n { kind: \"coordination\", cause: sanitizeCause(error) },\n );\n } else this.#setMode(\"degraded-local\");\n return fallback();\n }\n }\n\n #bucketKey(key: string): string {\n return `${this.#prefix}bucket:${key}`;\n }\n #globalKey(scope: string): string {\n return `${this.#prefix}global:${scope}`;\n }\n\n public async getBucketHash(route: string): Promise<string | undefined> {\n return this.#run(\n \"getBucketHash\",\n async () =>\n (await this.#client.get(`${this.#prefix}route:${route}`)) ??\n undefined,\n () => this.#local.getBucketHash(route),\n false,\n );\n }\n\n public async setBucketHash(route: string, hash: string): Promise<void> {\n this.#local.setBucketHash(route, hash);\n await this.#run(\n \"setBucketHash\",\n async () => {\n await this.#client.set(\n `${this.#prefix}route:${route}`,\n hash,\n \"EX\",\n 604800,\n );\n },\n () => undefined,\n false,\n );\n }\n\n public async getBucket(key: string): Promise<BucketState | undefined> {\n return this.#run(\n \"getBucket\",\n async () => {\n const data = await this.#client.get(this.#bucketKey(key));\n if (!data) return undefined;\n try {\n return JSON.parse(data) as BucketState;\n } catch {\n return undefined;\n }\n },\n () => this.#local.getBucket(key),\n true,\n );\n }\n\n public async updateBucket(key: string, state: BucketState): Promise<void> {\n this.#local.updateBucket(key, state);\n // Expired buckets hold no useful limit info; drop them instead of\n // persisting stale state under a clamped TTL.\n const ttl = Math.ceil(state.resetAt - Date.now());\n await this.#run(\n \"updateBucket\",\n async () => {\n if (ttl <= 0) await this.#client.del(this.#bucketKey(key));\n else\n await this.#client.set(\n this.#bucketKey(key),\n JSON.stringify(state),\n \"PX\",\n ttl,\n );\n },\n () => undefined,\n false,\n );\n }\n\n /** Atomically checks the global limit and consumes one unit of the bucket. */\n public reserve?: (\n key: string,\n options?: ReserveOptions,\n ) => Promise<Reservation>;\n /** Atomically merges a response into the bucket. */\n public reconcile?: (key: string, update: BucketUpdate) => Promise<void>;\n /** Atomically extends a global limit, never shortening it. */\n public extendGlobalReset?: (scope: string, waitMs: number) => Promise<void>;\n /** Clears a probe placeholder or refunds an unsent request's unit. */\n public release?: (\n key: string,\n grant: { resetAt?: number },\n refund: boolean,\n ) => Promise<void>;\n /** Whether {@link reserve} checks the global limit itself. */\n public readonly reservesGlobal?: boolean;\n\n async #reserve(\n key: string,\n options: ReserveOptions = {},\n ): Promise<Reservation> {\n const keys = [this.#bucketKey(key)];\n if (options.global !== undefined) {\n keys.push(this.#globalKey(options.global));\n if (options.globalLimit)\n keys.push(`${this.#prefix}global-log:${options.global}`);\n }\n return this.#run(\n \"reserve\",\n async () => {\n const result = await this.#client.eval!(\n RESERVE_SCRIPT,\n keys.length,\n ...keys,\n options.probeMs ?? DEFAULT_PROBE_MS,\n PROBE_POLL_MS,\n BUCKET_LINGER_MS,\n options.globalLimit ?? 0,\n GLOBAL_WINDOW_MS,\n );\n return parseReservation(result);\n },\n () => this.#local.reserve(key, options),\n true,\n );\n }\n\n async #reconcile(key: string, update: BucketUpdate): Promise<void> {\n this.#local.reconcile(key, update);\n const arg = (value: number | undefined): string =>\n value === undefined ? \"\" : String(value);\n await this.#run(\n \"reconcile\",\n async () => {\n await this.#client.eval!(\n RECONCILE_SCRIPT,\n 1,\n this.#bucketKey(key),\n arg(update.remaining),\n arg(update.resetAfterMs),\n arg(update.limit),\n arg(update.window),\n arg(update.retryAfterMs),\n BUCKET_LINGER_MS,\n );\n },\n () => undefined,\n false,\n );\n }\n\n async #extendGlobal(scope: string, waitMs: number): Promise<void> {\n this.#local.extendGlobalReset(scope, waitMs);\n await this.#run(\n \"extendGlobalReset\",\n async () => {\n await this.#client.eval!(\n EXTEND_GLOBAL_SCRIPT,\n 1,\n this.#globalKey(scope),\n Math.max(0, Math.ceil(waitMs)),\n );\n },\n () => undefined,\n false,\n );\n }\n\n async #release(\n key: string,\n grant: { resetAt?: number },\n refund: boolean,\n ): Promise<void> {\n this.#local.release(key, grant, refund);\n if (grant.resetAt === undefined) return;\n await this.#run(\n \"release\",\n async () => {\n await this.#client.eval!(\n RELEASE_SCRIPT,\n 1,\n this.#bucketKey(key),\n grant.resetAt!,\n refund ? \"1\" : \"0\",\n );\n },\n () => undefined,\n false,\n );\n }\n\n public async getGlobalReset(scope = \"\"): Promise<number> {\n return this.#run(\n \"getGlobalReset\",\n async () => {\n const resetAt = await this.#client.get(this.#globalKey(scope));\n return resetAt ? Number(resetAt) || 0 : 0;\n },\n () => this.#local.getGlobalReset(scope),\n true,\n );\n }\n\n public async setGlobalReset(resetAt: number, scope = \"\"): Promise<void> {\n this.#local.setGlobalReset(resetAt, scope);\n const ttl = Math.ceil(resetAt - Date.now());\n await this.#run(\n \"setGlobalReset\",\n async () => {\n if (ttl <= 0) await this.#client.del(this.#globalKey(scope));\n else\n await this.#client.set(\n this.#globalKey(scope),\n String(resetAt),\n \"PX\",\n ttl,\n );\n },\n () => undefined,\n false,\n );\n }\n}\n\n/**\n * Reads the reservation script's reply. Anything unexpected is an error, not\n * a grant: a broken reply must never let a request through unreserved.\n */\nfunction parseReservation(result: unknown): Reservation {\n if (!Array.isArray(result) || result.length < 3)\n throw new Error(\"Unexpected reply from the reservation script.\");\n const [granted, value, flag] = result as [unknown, unknown, unknown];\n const n = Number(value);\n if (!Number.isFinite(n))\n throw new Error(\"Unexpected reply from the reservation script.\");\n if (Number(granted) === 1)\n return { granted: true, known: Number(flag) === 1, resetAt: n };\n const reason = String(flag);\n return {\n granted: false,\n retryAfterMs: Math.max(0, n),\n reason: reason === \"global\" || reason === \"probe\" ? reason : \"bucket\",\n };\n}\n",
|
|
18
|
+
"import { REST, type RESTFileAttachment } from \"./index.js\";\nimport { Routes } from \"./routes.js\";\nimport type {\n AllowedMentions,\n APIEmbed,\n APIMessage,\n APIMessageComponent,\n} from \"@lunibee/types\";\n\n/** Where a `WebhookClient` sends: a Discord webhook `url`, or its `id` and `token`. */\nexport interface WebhookClientOptions {\n id?: string;\n token?: string;\n url?: string;\n}\n\n/** A webhook message for `send()` and `editMessage()`; camelCase aliases (`avatarURL`, `threadId`, `allowedMentions`) are accepted beside the wire names. */\nexport interface WebhookMessageOptions {\n content?: string;\n username?: string;\n avatar_url?: string;\n tts?: boolean;\n embeds?: (APIEmbed | { toJSON(): APIEmbed })[];\n components?: (APIMessageComponent | { toJSON(): APIMessageComponent })[];\n /** Uploaded as multipart form data. */\n files?: RESTFileAttachment[];\n flags?: number;\n thread_id?: string;\n /** Discord.js-familiar alias of `avatar_url`. */\n avatarURL?: string;\n /** Discord.js-familiar alias of `thread_id`. */\n threadId?: string;\n /** Discord.js-familiar alias of `allowed_mentions`. */\n allowedMentions?: AllowedMentions;\n allowed_mentions?: AllowedMentions;\n}\n\n/** Standalone client for sending Discord webhooks. */\nexport class WebhookClient {\n /** Webhook ID. */\n public readonly id: string;\n /** Webhook security token. */\n public readonly token: string;\n readonly #rest: REST;\n\n public constructor(\n options: WebhookClientOptions,\n restOptions?: { timeout?: number; retries?: number },\n ) {\n if (options.url) {\n // Anchored: the URL must be Discord's own webhook URL, not merely contain one.\n const match = options.url.match(\n /^https:\\/\\/(?:(?:ptb|canary)\\.)?discord(?:app)?\\.com\\/api(?:\\/v\\d+)?\\/webhooks\\/(\\d{1,20})\\/([A-Za-z0-9_-]+)\\/?(?:[?#].*)?$/,\n );\n if (!match) throw new Error(\"Invalid Discord webhook URL format.\");\n this.id = match[1]!;\n this.token = match[2]!;\n } else if (options.id && options.token) {\n this.id = options.id;\n this.token = options.token;\n } else {\n throw new Error(\n \"WebhookClient requires either a webhook 'url' or both 'id' and 'token'.\",\n );\n }\n this.#rest = new REST(restOptions);\n }\n\n /** Splits a message payload into its query, JSON body and files, resolving the camelCase aliases and builders. */\n #prepare(\n options: string | WebhookMessageOptions,\n base: Record<string, string> = {},\n ): {\n query: string;\n json: Record<string, unknown>;\n files?: RESTFileAttachment[];\n } {\n const payload =\n typeof options === \"string\" ? { content: options } : options;\n const {\n thread_id: threadWire,\n threadId,\n avatarURL,\n allowedMentions,\n files,\n ...body\n } = payload;\n const thread_id = threadWire ?? threadId;\n // The wire names win when both spellings are given.\n if (avatarURL !== undefined && body.avatar_url === undefined)\n body.avatar_url = avatarURL;\n if (\n allowedMentions !== undefined &&\n body.allowed_mentions === undefined\n )\n body.allowed_mentions = allowedMentions;\n const params = new URLSearchParams(base);\n if (thread_id) params.set(\"thread_id\", String(thread_id));\n const json: Record<string, unknown> = { ...body };\n if (body.embeds)\n json.embeds = body.embeds.map((e) =>\n \"toJSON\" in e ? e.toJSON() : e,\n );\n if (body.components)\n json.components = body.components.map((c) =>\n \"toJSON\" in c ? c.toJSON() : c,\n );\n const text = params.toString();\n return { query: text ? `?${text}` : \"\", json, files };\n }\n\n /** Sends a message to the webhook. */\n public async send(\n options: string | WebhookMessageOptions,\n ): Promise<APIMessage> {\n const { query, json, files } = this.#prepare(options, { wait: \"true\" });\n const path = `${Routes.webhook(this.id, this.token)}${query}`;\n return files?.length\n ? this.#rest.postWithFiles<APIMessage>(path, json, files)\n : this.#rest.post<APIMessage>(path, json);\n }\n\n /** Fetches a message this webhook sent (`threadId` for a message inside a thread). */\n public async fetchMessage(\n messageId: string,\n options: { threadId?: string } = {},\n ): Promise<APIMessage> {\n const query = options.threadId\n ? `?${new URLSearchParams({ thread_id: options.threadId })}`\n : \"\";\n return this.#rest.get<APIMessage>(\n `${Routes.webhookMessage(this.id, this.token, messageId)}${query}`,\n );\n }\n\n /** Edits a previously sent webhook message. Takes the same options as {@link WebhookClient.send}, files included. */\n public async editMessage(\n messageId: string,\n options: string | WebhookMessageOptions,\n ): Promise<APIMessage> {\n const { query, json, files } = this.#prepare(options);\n const path = `${Routes.webhookMessage(this.id, this.token, messageId)}${query}`;\n return files?.length\n ? this.#rest.patchWithFiles<APIMessage>(path, json, files)\n : this.#rest.patch<APIMessage>(path, json);\n }\n\n /** Deletes a previously sent webhook message. */\n public async deleteMessage(\n messageId: string,\n options: { threadId?: string } = {},\n ): Promise<void> {\n const query = options.threadId\n ? `?${new URLSearchParams({ thread_id: options.threadId })}`\n : \"\";\n await this.#rest.delete(\n `${Routes.webhookMessage(this.id, this.token, messageId)}${query}`,\n );\n }\n}\n",
|
|
19
|
+
"/** Library version and source URL used for the Discord-compliant User-Agent. */\nimport packageJson from \"../package.json\" with { type: \"json\" };\nconst LIBRARY_VERSION = packageJson.version;\nconst LIBRARY_URL = \"https://github.com/Ekretos/lunibee\";\nconst USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${LIBRARY_VERSION})`;\n\nimport { MemoryRateLimitStore, type RateLimitStore } from \"./store.js\";\nimport {\n RESTError,\n abortError,\n redactPath,\n sanitizeCause,\n sleep,\n} from \"./errors.js\";\nimport {\n createRouteKey,\n serializeQuery,\n type RESTQuery,\n type RouteKey,\n} from \"./route.js\";\nimport { RequestScheduler } from \"./scheduler.js\";\nimport { RateLimiter, type Grant } from \"./limiter.js\";\nimport {\n requestIdentity,\n tokenFingerprint,\n type RequestIdentity,\n} from \"./identity.js\";\nimport {\n emitTelemetry,\n type CoordinationMode,\n type RESTTelemetry,\n} from \"./telemetry.js\";\nimport { HttpTransport, TransportError } from \"./transport.js\";\nimport { ResponseDecoder } from \"./decoder.js\";\n\n/**\n * Refuses a path that URL normalization would rewrite into another route:\n * `.` / `..` segments (also percent-encoded) and backslashes. Without this,\n * an id like `../../channels/1` sends the bot token to a route the caller\n * never asked for. The path is left out of the message: it may hold a token.\n */\nfunction assertSafePath(path: string): void {\n // The URL parser deletes tab, LF and CR before it resolves dot segments,\n // so `.\\t.` would pass the segment check below and become `..`; a `#`\n // cuts off whatever the route adds after it. Neither belongs in a REST\n // path, and neither do other control characters or spaces.\n if (/[\\u0000-\\u0020\\u007f#]/.test(path))\n throw new TypeError(\n \"REST paths must not contain control characters, spaces or '#'.\",\n );\n const end = path.indexOf(\"?\");\n const pathname = end === -1 ? path : path.slice(0, end);\n if (pathname.includes(\"\\\\\"))\n throw new TypeError(\"REST paths must not contain backslashes.\");\n for (const segment of pathname.split(\"/\")) {\n let decoded: string;\n try {\n decoded = decodeURIComponent(segment);\n } catch {\n throw new TypeError(\"REST path has malformed percent-encoding.\");\n }\n // Percent-encoded whitespace (`%09..`) is judged the way a server\n // that strips it would read the segment.\n const bare = decoded.replace(/[\\u0000-\\u0020\\u007f]/g, \"\");\n if (\n bare === \".\" ||\n bare === \"..\" ||\n (/[\\\\/]/.test(decoded) &&\n decoded.split(/[\\\\/]/).some((s) => {\n const part = s.replace(/[\\u0000-\\u0020\\u007f]/g, \"\");\n return part === \".\" || part === \"..\";\n }))\n )\n throw new TypeError(\n \"REST paths must not contain '.' or '..' segments.\",\n );\n }\n}\n\n/** A file attachment sent as part of a multipart REST request. */\nexport interface RESTFileAttachment {\n name: string;\n /** The bytes, or a promise of them (read just before the request is sent). */\n data:\n | Blob\n | Uint8Array\n | ArrayBuffer\n | PromiseLike<Blob | Uint8Array | ArrayBuffer>;\n contentType?: string;\n}\n\n/**\n * Options controlling an individual REST request.\n *\n * Lunibee's canonical call shape is positional (`post(path, body)`); these options\n * add the Discord.js-`@discordjs/rest`-familiar per-request knobs (`query`, `headers`,\n * `reason`, `auth`) additively without changing that positional default.\n */\nexport interface RESTRequestOptions {\n /** Abort signal used to cancel the request and any queued wait. */\n signal?: AbortSignal;\n /** Query-string appended to the path (`URLSearchParams`, a record, or a raw string). */\n query?: RESTQuery;\n /** Extra request headers merged in (library headers such as `Authorization` win). */\n headers?: Record<string, string>;\n /** Audit-log reason, sent as the `X-Audit-Log-Reason` header. */\n reason?: string;\n /** Set `false` to omit the `Authorization` header for this request. Defaults to `true`. */\n auth?: boolean;\n /**\n * Longest, in ms, this request may wait before it is sent: queued behind\n * its bucket, for a rate limit, or between retries. Past it the request\n * fails with kind `\"overloaded\"`. Overrides the client's `maxQueueWait`.\n */\n maxQueueWait?: number;\n}\n\n/**\n * Discord.js-familiar request payload. Superset of {@link RESTRequestOptions} that also\n * carries the `body` and `files`, matching `@discordjs/rest`'s `RequestData`. It powers the\n * additive `method(path, { body })` overloads without breaking the positional signature.\n */\nexport interface RequestData extends RESTRequestOptions {\n /** Request body. Encoded as JSON unless `files` are present (then multipart). */\n body?: unknown;\n /** File attachments; presence upgrades the request to `multipart/form-data`. */\n files?: RESTFileAttachment[];\n}\n\n/**\n * The single-object form of {@link REST.request}, matching `@discordjs/rest`:\n * the route is `path` (or its Discord.js name, `fullRoute`).\n */\nexport interface RequestOptionsObject extends RequestData {\n /** HTTP method. */\n method: string;\n /** API path, starting with `/`. */\n path?: string;\n /** Alias of `path`, as `@discordjs/rest` names it. */\n fullRoute?: string;\n}\n\n/** Keys recognised on a {@link RequestData} wrapper, used to disambiguate it from a raw body. */\nconst REQUEST_DATA_KEYS = new Set([\n \"body\",\n \"files\",\n \"query\",\n \"headers\",\n \"reason\",\n \"auth\",\n \"signal\",\n \"maxQueueWait\",\n]);\n\n/**\n * Detects a Discord.js-style `RequestData` wrapper so verb helpers can accept both\n * `post(path, rawBody)` (Lunibee-canonical) and `post(path, { body })` (Discord.js-familiar).\n * A raw body whose keys are all option names (e.g. `{ reason: \"x\" }`) is read as a wrapper:\n * pass it as `{ body: { reason: \"x\" } }` to send it as a body.\n * Conservative by design: only a plain object whose every own key is a known `RequestData`\n * key counts — a raw Discord payload (which always carries entity keys like `content`/`name`)\n * never matches, so existing positional callers are unaffected.\n */\nfunction isRequestData(value: unknown): value is RequestData {\n if (value === null || typeof value !== \"object\") return false;\n if (Array.isArray(value)) return false;\n if (typeof FormData !== \"undefined\" && value instanceof FormData)\n return false;\n if (\n value instanceof Blob ||\n value instanceof Uint8Array ||\n value instanceof ArrayBuffer\n )\n return false;\n const keys = Object.keys(value);\n if (keys.length === 0) return false;\n return keys.every((key) => REQUEST_DATA_KEYS.has(key));\n}\n\n// ─── REST Hooks ───────────────────────────────────────────────────────────────\n\n/** Context passed to `RESTHooks.onRequest`. */\nexport interface RESTRequestContext {\n method: string;\n path: string;\n attempt: number;\n}\n/** Context passed to `RESTHooks.onResponse`. */\nexport interface RESTResponseContext {\n method: string;\n path: string;\n status: number;\n durationMs: number;\n}\n/** Context passed to `RESTHooks.onRateLimit`. */\nexport interface RESTRateLimitContext {\n method: string;\n path: string;\n retryAfterMs: number;\n global: boolean;\n bucket?: string;\n}\n/** Context passed to `RESTHooks.onRetry`. */\nexport interface RESTRetryContext {\n method: string;\n path: string;\n attempt: number;\n status: number;\n delayMs: number;\n}\n\n/**\n * Lightweight callback hooks for REST request lifecycle events.\n *\n * All callbacks are optional. They are dispatched in isolation from the request\n * path (deferred to a microtask, and any thrown error is swallowed), so a hook\n * cannot break or reject a request. Even so, hooks **must** be fast and\n * non-blocking: heavy synchronous work in a hook still steals event-loop time\n * from in-flight requests. Offload anything expensive to your own queue.\n */\nexport interface RESTHooks {\n /** Called just before every HTTP attempt. */\n onRequest?(ctx: RESTRequestContext): void;\n /** Called after a successful or failed response is received. */\n onResponse?(ctx: RESTResponseContext): void;\n /** Called when a rate limit is encountered and the client is about to wait. */\n onRateLimit?(ctx: RESTRateLimitContext): void;\n /** Called when a failed request is about to be retried. */\n onRetry?(ctx: RESTRetryContext): void;\n}\n\n/** Configures which REST failures may be retried. */\nexport interface RetryPolicy {\n /** Maximum retry attempts. */\n maxRetries: number;\n /** Determines whether a request may be retried. @param method HTTP method. @param status HTTP status, or zero for transport errors. @returns True when retrying is safe under the policy. */\n shouldRetry(method: string, status: number): boolean;\n /** Calculates delay before the next attempt. @param attempt Zero-based retry number. @param retryAfter Server-provided delay, when available. @returns Delay in milliseconds. */\n getDelay(attempt: number, retryAfter?: number): number;\n}\n/** Creates the default conservative REST retry policy. @param maxRetries Maximum retries. @returns Retry policy that retries rate limits and idempotent transient failures. @throws {TypeError} If maxRetries is negative or not finite. */\nexport function createRetryPolicy(maxRetries = 2): RetryPolicy {\n if (!Number.isFinite(maxRetries) || maxRetries < 0)\n throw new TypeError(\"maxRetries must be a non-negative finite number.\");\n return {\n maxRetries: Math.floor(maxRetries),\n shouldRetry(method, status) {\n const normalized = method.toUpperCase();\n const idempotent = [\"GET\", \"HEAD\", \"PUT\", \"DELETE\"].includes(\n normalized,\n );\n // Status 0 is a transport failure (DNS, reset connection, TLS):\n // no response reached Discord's handlers, so replaying an\n // idempotent request is as safe as replaying it after a 5xx.\n if (status === 0) return idempotent;\n return (\n status === 429 || (status >= 500 && status <= 599 && idempotent)\n );\n },\n getDelay(attempt, retryAfter) {\n if (retryAfter !== undefined) return Math.max(0, retryAfter * 1000);\n return Math.min(10_000, 500 * 2 ** attempt) + Math.random() * 250;\n },\n };\n}\n\n/** A REST client's state, for health checks and diagnostics endpoints. */\nexport interface RESTDiagnostics {\n /** How the store enforces limits right now. */\n mode: CoordinationMode;\n /** Requests waiting for a bucket slot. */\n queued: number;\n /** Requests started and not yet settled, queued ones included. */\n inFlight: number;\n /** Bucket keys holding a queue slot. */\n buckets: number;\n /** Whether `close()` was called. */\n closed: boolean;\n /**\n * `401`, `403` and `429` responses (shared-scope 429s excepted) in the\n * last ten minutes. Discord bans an IP for a while past 10,000.\n */\n invalidRequests: number;\n /** Whether requests are refused because Discord rejected the token (see `stopOnInvalidToken`). */\n tokenRejected: boolean;\n}\n\n/** Abort reason for a request that waited past `maxQueueWait`. */\nclass QueueWaitExceeded extends Error {\n public constructor(public readonly ms: number) {\n super(\"maxQueueWait exceeded\");\n }\n}\n\nfunction checkWait(ms: number | undefined): number | undefined {\n if (ms === undefined) return undefined;\n if (!(ms >= 0) || !Number.isFinite(ms))\n throw new RangeError(\"maxQueueWait must be a non-negative number.\");\n return ms;\n}\n\n/** Bun-native REST transport with Discord bucket-aware rate limiting, retries, and cancellation. */\nexport class REST {\n readonly #retryPolicy: RetryPolicy;\n #token?: string;\n /** Fingerprint of the token: identifies this bot's limits without holding the token. */\n #fingerprint?: string;\n readonly #telemetry?: RESTTelemetry;\n /** Aborted by `close()`: cancels every wait, never an attempt already sent. */\n readonly #closing = new AbortController();\n #inFlight = 0;\n #idle?: () => void;\n /** Fingerprint of a token Discord answered 401 for. */\n #rejected?: string;\n readonly #stopOnInvalidToken: boolean;\n readonly #maxQueueWait?: number;\n /** Invalid responses per minute, for the last ten minutes. */\n readonly #invalid = new Array<number>(10).fill(0);\n #invalidMinute = 0;\n #drained?: Promise<void>;\n /** Rate-limit state: when a request may go out, what a response says. */\n readonly #limiter: RateLimiter;\n /** Per-bucket ordering. */\n readonly #scheduler: RequestScheduler;\n /** One HTTP attempt, with timeout and cancellation. */\n readonly #transport: HttpTransport;\n /** Response payload and error metadata. */\n readonly #decoder = new ResponseDecoder();\n #hooks: RESTHooks = {};\n /** Whether requests on a known bucket may run concurrently. */\n readonly #concurrentBuckets: boolean;\n /** Creates a REST transport. @param options Transport configuration. @throws {TypeError} If retry configuration is invalid. */\n public constructor(\n options: {\n token?: string;\n timeout?: number;\n retries?: number;\n retryPolicy?: RetryPolicy;\n baseURL?: string;\n /** Allow a cleartext `http://` base URL on a network you trust (a REST proxy). Default false. */\n allowInsecureBaseURL?: boolean;\n hooks?: RESTHooks;\n store?: RateLimitStore;\n /** Replaces the HTTP stage, e.g. with a recording transport in tests. */\n transport?: HttpTransport;\n /**\n * Lets requests on a bucket whose limit is known run concurrently, up\n * to its `remaining` allowance, instead of one at a time. Requires a\n * store with `reserve`. Requests on one bucket may then complete out\n * of order (e.g. messages to one channel). Defaults to `true`.\n */\n concurrentBuckets?: boolean;\n /**\n * The longest, in ms, that a rate-limit header from the server may\n * make this client wait. A `Retry-After` beyond it fails the request\n * with the 429 instead of sleeping, and stored limits are capped to it,\n * so a proxy or an attacker cannot stall every request for hours.\n * Default 600,000 (ten minutes).\n */\n maxBackoff?: number;\n /**\n * Receives telemetry events (latency, 429s by scope, waits,\n * reservations, retries, cancellations, queue depth). See\n * `RESTMetrics` for a ready-made aggregator.\n */\n telemetry?: RESTTelemetry;\n /** Most requests waiting for a bucket slot; more fail with kind `\"overloaded\"`. Default unlimited. */\n maxQueued?: number;\n /** Most requests waiting on one bucket; more fail with kind `\"overloaded\"`. Default unlimited. */\n maxQueuedPerBucket?: number;\n /**\n * Requests per second this bot may send across all buckets,\n * enforced before Discord answers with a global 429 (shared through\n * the store, so it holds for every process). Default 50, Discord's\n * global limit; set what Discord granted a large bot, or `false`\n * to rely on 429s alone.\n */\n globalLimit?: number | false;\n /**\n * After a `401` on a bot route, refuse every later request with\n * this token without sending it, until `setToken()`: repeated 401s\n * count towards Discord's invalid-request ban. Default `true`.\n */\n stopOnInvalidToken?: boolean;\n /**\n * Longest, in ms, a request may wait before it is sent (queue,\n * rate limits, pauses between retries); past it the request fails\n * with kind `\"overloaded\"`. Default unlimited. Per request:\n * `maxQueueWait` in the request options.\n */\n maxQueueWait?: number;\n } = {},\n ) {\n this.#token = options.token;\n this.#fingerprint = options.token\n ? tokenFingerprint(options.token)\n : undefined;\n this.#telemetry = options.telemetry;\n this.#scheduler = new RequestScheduler({\n maxQueued: options.maxQueued,\n maxQueuedPerBucket: options.maxQueuedPerBucket,\n });\n this.#retryPolicy =\n options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);\n this.#transport =\n options.transport ??\n new HttpTransport({\n baseURL: options.baseURL,\n allowInsecureBaseURL: options.allowInsecureBaseURL,\n timeout: options.timeout,\n });\n const store = options.store ?? new MemoryRateLimitStore();\n this.#concurrentBuckets =\n (options.concurrentBuckets ?? true) &&\n typeof store.reserve === \"function\";\n this.#limiter = new RateLimiter(store, {\n concurrent: this.#concurrentBuckets,\n maxBackoff: options.maxBackoff,\n // A probe holds an unknown bucket for as long as its attempt can\n // take, so a second one cannot start while it is still out.\n probeMs:\n this.#transport.timeout > 0\n ? this.#transport.timeout + 1000\n : undefined,\n telemetry: options.telemetry,\n globalLimit:\n options.globalLimit === false\n ? undefined\n : (options.globalLimit ?? 50),\n });\n this.#stopOnInvalidToken = options.stopOnInvalidToken ?? true;\n this.#maxQueueWait = checkWait(options.maxQueueWait);\n if (options.hooks) this.#hooks = options.hooks;\n }\n /** The rate-limit store backing this transport. */\n public get store(): RateLimitStore {\n return this.#limiter.store;\n }\n /** Replaces the current hook set. Pass an empty object to clear all hooks. */\n public setHooks(hooks: RESTHooks): this {\n this.#hooks = hooks;\n return this;\n }\n /** Sets the authentication token. @param token Discord bot token. @returns Nothing. @throws {TypeError} If token is empty. */\n public setToken(token: string): void {\n if (!token.trim())\n throw new TypeError(\"A Discord bot token is required.\");\n this.#token = token;\n this.#fingerprint = tokenFingerprint(token);\n this.#rejected = undefined;\n }\n /** The client's current state. */\n public diagnostics(): RESTDiagnostics {\n const store = this.#limiter.store as { mode?: CoordinationMode };\n return {\n mode:\n store.mode ??\n (store instanceof MemoryRateLimitStore\n ? \"local\"\n : \"distributed\"),\n queued: this.#scheduler.queued,\n inFlight: this.#inFlight,\n buckets: this.#scheduler.size,\n closed: this.#closing.signal.aborted,\n invalidRequests: this.#invalidCount(0),\n tokenRejected:\n this.#rejected !== undefined &&\n this.#rejected === this.#fingerprint,\n };\n }\n /**\n * Stops the client: new requests are refused, requests still queued or\n * waiting for a limit fail with kind `\"aborted\"`, and attempts already\n * sent are allowed to finish. Resolves when nothing is in flight.\n */\n public async close(): Promise<void> {\n if (!this.#closing.signal.aborted)\n this.#closing.abort(new Error(\"The REST client was closed.\"));\n if (this.#inFlight === 0) return;\n this.#drained ??= new Promise<void>((resolve) => {\n this.#idle = resolve;\n });\n await this.#drained;\n }\n /** Adds to and returns the invalid responses of the last ten minutes. */\n #invalidCount(add: number): number {\n const minute = Math.floor(Date.now() / 60_000);\n const elapsed = Math.min(10, minute - this.#invalidMinute);\n for (let i = 1; i <= elapsed; i++)\n this.#invalid[(this.#invalidMinute + i) % 10] = 0;\n if (elapsed > 0) this.#invalidMinute = minute;\n this.#invalid[minute % 10]! += add;\n return this.#invalid.reduce((a, b) => a + b, 0);\n }\n #queueChanged(): void {\n emitTelemetry(this.#telemetry, {\n type: \"queue\",\n queued: this.#scheduler.queued,\n inFlight: this.#inFlight,\n });\n }\n /** Executes an HTTP request with Discord bucket/global rate limits, retry handling, and AbortSignal support.\n * Accepts a JSON body, a FormData body (for file uploads), or undefined.\n * @param method HTTP method.\n * @param path API path.\n * @param body Optional JSON body, FormData for file uploads, or undefined.\n * @param options Request options: cancellation `signal` plus the Discord.js-familiar\n * `query`, `headers`, `reason`, `auth`, and `files` (which upgrade to multipart).\n * @returns Decoded response body.\n * @throws {RESTError} If Discord or transport rejects the request.\n */\n public request<T>(options: RequestOptionsObject): Promise<T>;\n public request<T>(\n method: string,\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public async request<T>(\n methodOrOptions: string | RequestOptionsObject,\n pathArg?: string,\n bodyArg?: unknown,\n optionsArg: RequestData = {},\n ): Promise<T> {\n // Discord.js-familiar form: request({ method, path, body, ... }).\n let method: string;\n let path: string;\n let body = bodyArg;\n let options = optionsArg;\n if (typeof methodOrOptions === \"object\" && methodOrOptions !== null) {\n const { method: m, path: p, fullRoute, ...rest } = methodOrOptions;\n method = m;\n path = p ?? fullRoute ?? \"\";\n body = rest.body;\n options = rest;\n } else {\n method = methodOrOptions;\n path = pathArg as string;\n }\n if (typeof method !== \"string\" || !method.toUpperCase())\n throw new TypeError(\"REST method is required.\");\n if (!path.startsWith(\"/\"))\n throw new TypeError(\"REST paths must start with '/'.\");\n assertSafePath(path);\n const route = createRouteKey(method, path);\n // Upgrade to multipart when file attachments are supplied via options.\n const files = options.files;\n if (files && files.length > 0 && !(body instanceof FormData))\n body = this.#fileForm(\n body,\n await Promise.all(\n files.map(async (file) => ({\n ...file,\n data: await file.data,\n })),\n ),\n );\n const query = serializeQuery(options.query);\n const requestPath = query ? `${path}${query}` : path;\n if (this.#closing.signal.aborted)\n throw abortError(path, this.#closing.signal.reason);\n const identity = requestIdentity(\n path,\n options.auth !== false && this.#token\n ? this.#fingerprint\n : undefined,\n );\n if (\n identity.bucket === this.#fingerprint &&\n this.#rejected === this.#fingerprint\n )\n throw new RESTError(\n \"Discord rejected this bot token (401); request not sent. Call setToken() with a valid token.\",\n 401,\n undefined,\n undefined,\n { method: route.method, path, kind: \"authentication\" },\n );\n // Waits end on the caller's signal or on close(); a sent attempt only\n // on the caller's signal.\n const queueWait = checkWait(options.maxQueueWait) ?? this.#maxQueueWait;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n const sources = [this.#closing.signal];\n if (options.signal) sources.push(options.signal);\n if (queueWait !== undefined) {\n const expired = new AbortController();\n deadline = setTimeout(\n () => expired.abort(new QueueWaitExceeded(queueWait)),\n queueWait,\n );\n sources.push(expired.signal);\n }\n const waitSignal =\n sources.length === 1 ? sources[0]! : AbortSignal.any(sources);\n const state = { attempts: 0, status: 0 };\n const started = performance.now();\n this.#inFlight++;\n this.#queueChanged();\n\n // Discord scopes a rate limit by (bucket hash, major parameter): two\n // channels sharing an endpoint have independent limits, while two\n // different routes can share one hash. The key is therefore resolved\n // again once the slot is acquired, so a request that queued under its\n // route-derived key joins the shared bucket's queue if one was\n // discovered while it waited.\n try {\n return await this.#scheduler.runResolved(\n () =>\n this.#limiter.resolveBucketKey(\n route.route,\n route.major,\n identity.bucket,\n ),\n waitSignal,\n path,\n (bucketKey, release) => {\n this.#queueChanged();\n return this.#attempts<T>(\n route,\n requestPath,\n body,\n options,\n { identity, waitSignal, state },\n bucketKey,\n release,\n );\n },\n );\n } catch (caught) {\n let error = caught;\n if (\n error instanceof RESTError &&\n error.kind === \"aborted\" &&\n error.cause instanceof QueueWaitExceeded\n )\n error = new RESTError(\n `The request waited longer than maxQueueWait (${error.cause.ms}ms) before it could be sent.`,\n 0,\n undefined,\n undefined,\n { method: route.method, path, kind: \"overloaded\" },\n );\n if (error instanceof RESTError) {\n if (error.kind === \"aborted\" && state.attempts === 0)\n emitTelemetry(this.#telemetry, {\n type: \"cancelled\",\n stage: \"queued\",\n });\n else if (error.kind === \"overloaded\")\n emitTelemetry(this.#telemetry, { type: \"overloaded\" });\n if (state.status === 0) state.status = error.status;\n }\n throw error;\n } finally {\n clearTimeout(deadline);\n emitTelemetry(this.#telemetry, {\n type: \"request\",\n method: route.method,\n route: route.route,\n status: state.status,\n attempts: state.attempts,\n durationMs: performance.now() - started,\n ok: state.status >= 200 && state.status < 300,\n });\n if (--this.#inFlight === 0 && this.#idle) {\n this.#idle();\n this.#idle = undefined;\n this.#drained = undefined;\n }\n this.#queueChanged();\n }\n }\n /**\n * Runs the retry loop for one scheduled request: acquire limits, send,\n * record what the response says, decode, and decide whether to try again.\n */\n async #attempts<T>(\n route: RouteKey,\n requestPath: string,\n body: unknown,\n options: RequestData,\n context: {\n identity: RequestIdentity;\n waitSignal: AbortSignal;\n state: { attempts: number; status: number };\n },\n initialBucketKey: string,\n release: () => void,\n ): Promise<T> {\n const { method, path } = route;\n const { identity, waitSignal, state } = context;\n const label = route.route;\n let bucketKey = initialBucketKey;\n for (\n let attempt = 0;\n attempt <= this.#retryPolicy.maxRetries;\n attempt++\n ) {\n let grant: Grant;\n try {\n grant = await this.#limiter.acquire(\n bucketKey,\n waitSignal,\n path,\n { global: identity.global },\n );\n } catch (error) {\n if (error instanceof RESTError && error.kind === \"aborted\")\n emitTelemetry(this.#telemetry, {\n type: \"cancelled\",\n stage: \"waiting\",\n });\n throw error;\n }\n // The reservation already claimed this request's unit, so once the\n // bucket's limit is known the next request need not wait for our\n // response. An unknown bucket stays serial until a response\n // reveals its limit.\n if (this.#concurrentBuckets && grant.known) release();\n if (waitSignal.aborted) {\n // Never sent: the unit goes back to its window.\n await this.#limiter.releaseGrant(bucketKey, grant, true);\n emitTelemetry(this.#telemetry, {\n type: \"cancelled\",\n stage: \"waiting\",\n });\n throw abortError(path, waitSignal.reason);\n }\n this.#emit(this.#hooks.onRequest, { method, path, attempt });\n state.attempts = attempt + 1;\n const requestStart = Date.now();\n let response: Response;\n try {\n response = await this.#transport.send({\n method,\n path: requestPath,\n headers: this.#headers(body, options),\n body: this.#encodeBody(body),\n signal: options.signal,\n });\n } catch (error) {\n // No response: the request may or may not have reached\n // Discord, so its unit stays spent; only a probe placeholder\n // (or a request refused before it left) gives anything back.\n const notSent =\n error instanceof RESTError && error.kind === \"aborted\";\n await this.#limiter.releaseGrant(bucketKey, grant, notSent);\n if (error instanceof RESTError) {\n if (error.kind === \"aborted\")\n emitTelemetry(this.#telemetry, {\n type: \"cancelled\",\n stage: \"transport\",\n });\n throw error;\n }\n if (options.signal?.aborted) {\n emitTelemetry(this.#telemetry, {\n type: \"cancelled\",\n stage: \"transport\",\n });\n throw abortError(path, options.signal.reason);\n }\n const failure =\n error instanceof TransportError\n ? error\n : new TransportError(\n \"Discord REST request failed\",\n false,\n sanitizeCause(error),\n );\n emitTelemetry(this.#telemetry, {\n type: \"networkError\",\n timedOut: failure.timedOut,\n });\n if (\n !failure.timedOut &&\n attempt < this.#retryPolicy.maxRetries &&\n this.#retryPolicy.shouldRetry(method, 0)\n ) {\n const delay = this.#retryPolicy.getDelay(attempt);\n // Status 0: the retry follows a network failure, not a response.\n this.#emit(this.#hooks.onRetry, {\n method,\n path,\n attempt,\n status: 0,\n delayMs: delay,\n });\n emitTelemetry(this.#telemetry, {\n type: \"retry\",\n route: label,\n reason: \"network\",\n attempt,\n });\n await this.#retrySleep(delay, waitSignal, path);\n continue;\n }\n throw new RESTError(failure.message, 0, undefined, undefined, {\n method,\n path,\n cause: failure.cause ?? failure,\n kind: failure.timedOut ? \"timeout\" : \"network\",\n });\n }\n state.status = response.status;\n if (\n response.status === 401 ||\n response.status === 403 ||\n (response.status === 429 &&\n response.headers.get(\"X-RateLimit-Scope\") !== \"shared\")\n )\n this.#invalidCount(1);\n if (\n response.status === 401 &&\n this.#stopOnInvalidToken &&\n identity.bucket === this.#fingerprint\n ) {\n this.#rejected = this.#fingerprint;\n emitTelemetry(this.#telemetry, { type: \"tokenRejected\" });\n }\n const durationMs = Date.now() - requestStart;\n this.#emit(this.#hooks.onResponse, {\n method,\n path,\n status: response.status,\n durationMs,\n });\n emitTelemetry(this.#telemetry, {\n type: \"response\",\n method,\n route: label,\n status: response.status,\n durationMs,\n });\n const payload = await this.#decoder.read(response);\n const data = response.ok ? {} : this.#decoder.errorData(payload);\n bucketKey = await this.#limiter.applyResponse(\n response,\n bucketKey,\n route.route,\n route.major,\n {\n identity: identity.bucket,\n global: identity.global,\n body: data,\n grant,\n },\n );\n if (response.ok)\n return (response.status === 204 ? undefined : payload) as T;\n\n const limited = response.status === 429;\n const retryAfterMs = limited\n ? this.#limiter.retryAfterMs(response, data)\n : undefined;\n // Fire for ANY 429, including a global limit that arrives without a\n // Retry-After header (one second is assumed then).\n if (limited) {\n const global =\n data.global === true ||\n response.headers.get(\"X-RateLimit-Global\") === \"true\";\n const scope = response.headers\n .get(\"X-RateLimit-Scope\")\n ?.toLowerCase();\n emitTelemetry(this.#telemetry, {\n type: \"rateLimited\",\n route: label,\n scope: global\n ? \"global\"\n : scope === \"user\" || scope === \"shared\"\n ? scope\n : \"unknown\",\n retryAfterMs: retryAfterMs!,\n });\n this.#emit(this.#hooks.onRateLimit, {\n method,\n path,\n retryAfterMs: retryAfterMs!,\n global,\n bucket:\n response.headers.get(\"X-RateLimit-Bucket\") ??\n (await this.#limiter.store.getBucketHash(route.route)),\n });\n }\n if (\n attempt < this.#retryPolicy.maxRetries &&\n this.#retryPolicy.shouldRetry(method, response.status)\n ) {\n const delay = this.#retryPolicy.getDelay(\n attempt,\n limited ? retryAfterMs! / 1000 : undefined,\n );\n if (delay > this.#limiter.maxBackoff)\n throw new RESTError(\n `Discord asked to wait ${Math.ceil(delay / 1000)}s before retrying, longer than maxBackoff (${Math.ceil(this.#limiter.maxBackoff / 1000)}s).`,\n response.status,\n data.code,\n payload,\n { method, path },\n );\n this.#emit(this.#hooks.onRetry, {\n method,\n path,\n attempt,\n status: response.status,\n delayMs: delay,\n });\n emitTelemetry(this.#telemetry, {\n type: \"retry\",\n route: label,\n reason: limited ? \"rateLimited\" : \"server\",\n attempt,\n });\n // A 429's wait is now in the limiter's state, where every\n // request on the bucket (in every process) honours it; the\n // next acquire waits it out. Sleep only what the retry policy\n // adds on top.\n await this.#retrySleep(\n limited ? delay - retryAfterMs! : delay,\n waitSignal,\n path,\n );\n continue;\n }\n throw new RESTError(\n data.message ??\n response.statusText ??\n `Discord REST request failed with status ${response.status}`,\n response.status,\n data.code,\n payload,\n { method, path },\n );\n }\n throw new RESTError(\n \"Discord REST request exhausted its retry attempts\",\n 0,\n undefined,\n undefined,\n { method, path },\n );\n }\n /** The pause between attempts, reported as a cancellation when cut short. */\n async #retrySleep(\n delay: number,\n signal: AbortSignal,\n path: string,\n ): Promise<void> {\n try {\n await sleep(delay, signal, path);\n } catch (error) {\n emitTelemetry(this.#telemetry, {\n type: \"cancelled\",\n stage: \"retry\",\n });\n throw error;\n }\n }\n /** Builds the header set for one attempt; library headers win over caller headers. */\n #headers(body: unknown, options: RequestData): Record<string, string> {\n const isFormData =\n typeof FormData !== \"undefined\" && body instanceof FormData;\n const headers: Record<string, string> = { ...options.headers };\n // Header names are case-insensitive: drop the caller's spelling first,\n // or `authorization` and `Authorization` are joined into one value.\n const set = (name: string, value: string): void => {\n for (const key of Object.keys(headers))\n if (key.toLowerCase() === name.toLowerCase())\n delete headers[key];\n headers[name] = value;\n };\n if (options.auth !== false && this.#token)\n set(\"Authorization\", `Bot ${this.#token}`);\n set(\"User-Agent\", USER_AGENT);\n if (options.reason !== undefined)\n set(\"X-Audit-Log-Reason\", encodeURIComponent(options.reason));\n if (!isFormData) set(\"Content-Type\", \"application/json\");\n return headers;\n }\n /** Encodes a body as multipart (passed through) or JSON. */\n #encodeBody(body: unknown): BodyInit | undefined {\n if (typeof FormData !== \"undefined\" && body instanceof FormData)\n return body;\n return body === undefined ? undefined : JSON.stringify(body);\n }\n /**\n * Normalizes a verb helper's second argument into `(body, options)`.\n * Supports both `verb(path, rawBody, options?)` (Lunibee-canonical positional) and the\n * Discord.js-familiar `verb(path, { body, query, headers, reason, files })` wrapper.\n */\n #dispatch<T>(\n method: string,\n path: string,\n bodyOrOptions?: unknown,\n options?: RequestData,\n ): Promise<T> {\n if (options === undefined && isRequestData(bodyOrOptions))\n return this.request<T>(\n method,\n path,\n bodyOrOptions.body,\n bodyOrOptions,\n );\n return this.request<T>(method, path, bodyOrOptions, options);\n }\n /** Sends a GET request. @param path API path. @param options Request options (`query`, `headers`, `signal`, …). @returns Decoded response. @throws {RESTError} When the request fails. */\n public get<T>(path: string, options?: RequestData): Promise<T> {\n return this.request<T>(\"GET\", path, options?.body, options);\n }\n /** Sends a POST request. Accepts a raw body (`post(path, body)`) or a Discord.js-familiar `post(path, { body })`. @param path API path. @param body Optional JSON body, or a `RequestData` wrapper. @param options Request options. @returns Decoded response. @throws {RESTError} When the request fails. */\n public post<T>(path: string, options: RequestData): Promise<T>;\n public post<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public post<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T> {\n return this.#dispatch<T>(\"POST\", path, body, options);\n }\n /** Sends a PATCH request. Accepts a raw body (`patch(path, body)`) or a Discord.js-familiar `patch(path, { body })`. @param path API path. @param body Optional JSON body, or a `RequestData` wrapper. @param options Request options. @returns Decoded response. @throws {RESTError} When the request fails. */\n public patch<T>(path: string, options: RequestData): Promise<T>;\n public patch<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public patch<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T> {\n return this.#dispatch<T>(\"PATCH\", path, body, options);\n }\n /** Sends a PUT request. Accepts a raw body (`put(path, body)`) or a Discord.js-familiar `put(path, { body })`. @param path API path. @param body Optional JSON body, or a `RequestData` wrapper. @param options Request options. @returns Decoded response. @throws {RESTError} When the request fails. */\n public put<T>(path: string, options: RequestData): Promise<T>;\n public put<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T>;\n public put<T>(\n path: string,\n body?: unknown,\n options?: RequestData,\n ): Promise<T> {\n return this.#dispatch<T>(\"PUT\", path, body, options);\n }\n /** Sends a DELETE request. @param path API path. @param options Request options (`reason`, `headers`, `signal`, …). @returns Decoded response. @throws {RESTError} When the request fails. */\n public delete<T>(path: string, options?: RequestData): Promise<T> {\n return this.request<T>(\"DELETE\", path, options?.body, options);\n }\n /**\n * Builds a multipart form with a JSON payload and file attachments.\n */\n #fileForm(\n payload: unknown,\n files: (RESTFileAttachment & {\n data: Blob | Uint8Array | ArrayBuffer;\n })[],\n ): FormData {\n const form = new FormData();\n form.append(\"payload_json\", JSON.stringify(payload));\n for (let i = 0; i < files.length; i++) {\n const file = files[i]!;\n const blob =\n file.data instanceof Blob\n ? file.data\n : new Blob([file.data as ArrayBuffer], {\n type: file.contentType ?? \"application/octet-stream\",\n });\n form.append(\n `files[${i}]`,\n blob,\n file.name.replace(/[\\r\\n\"\\\\]/g, \"_\"),\n );\n }\n return form;\n }\n\n /**\n * Sends a multipart/form-data POST request for file uploads.\n * Attaches a JSON payload as `payload_json` and files as additional fields.\n * @param path API path.\n * @param payload JSON payload (e.g. message content, embeds).\n * @param files Array of file attachments.\n * @param options Cancellation options.\n * @returns Decoded response.\n * @throws {RESTError} When the request fails.\n */\n public postWithFiles<T>(\n path: string,\n payload: unknown,\n files: RESTFileAttachment[],\n options?: RESTRequestOptions,\n ): Promise<T> {\n return this.request<T>(\"POST\", path, payload, { ...options, files });\n }\n\n /**\n * Sends a multipart/form-data PATCH request for editing messages with files.\n * @param path API path.\n * @param payload JSON payload.\n * @param files Array of file attachments.\n * @param options Cancellation options.\n * @returns Decoded response.\n * @throws {RESTError} When the request fails.\n */\n public patchWithFiles<T>(\n path: string,\n payload: unknown,\n files: RESTFileAttachment[],\n options?: RESTRequestOptions,\n ): Promise<T> {\n return this.request<T>(\"PATCH\", path, payload, { ...options, files });\n }\n /** Dispatches a lifecycle hook in isolation: deferred to a microtask so it\n * cannot block the request path, with any thrown error swallowed so a faulty\n * hook cannot reject the request. @param hook Optional callback. @param ctx Event context. */\n #emit<C>(hook: ((ctx: C) => void) | undefined, ctx: C): void {\n if (!hook) return;\n const withPath = ctx as { path?: unknown };\n if (typeof withPath.path === \"string\")\n ctx = { ...ctx, path: redactPath(withPath.path) };\n Promise.resolve()\n .then(() => hook(ctx))\n .catch(() => {});\n }\n}\n\nexport { Routes } from \"./routes.js\";\nexport {\n RESTError,\n abortError,\n redactPath,\n type RESTErrorKind,\n} from \"./errors.js\";\nexport { RESTErrorCode } from \"./error-codes.js\";\nexport {\n createRouteKey,\n normalizeRoutePath,\n majorParameter,\n scopeBucket,\n serializeQuery,\n type RouteKey,\n type RESTQuery,\n} from \"./route.js\";\nexport { RequestScheduler, type SchedulerOptions } from \"./scheduler.js\";\nexport {\n RateLimiter,\n DEFAULT_MAX_BACKOFF_MS,\n type Grant,\n type ApplyResponseOptions,\n} from \"./limiter.js\";\nexport {\n parseRateLimitHeaders,\n headerNumber,\n type RateLimitHeaders,\n type RateLimitScope,\n} from \"./headers.js\";\nexport {\n requestIdentity,\n tokenFingerprint,\n type RequestIdentity,\n} from \"./identity.js\";\nexport {\n RESTMetrics,\n type RESTMetricsSnapshot,\n type RESTTelemetry,\n type RESTTelemetryEvent,\n type CoordinationMode,\n} from \"./telemetry.js\";\nexport {\n HttpTransport,\n TransportError,\n type TransportRequest,\n type FetchLike,\n} from \"./transport.js\";\nexport { ResponseDecoder, type DecodedError } from \"./decoder.js\";\nexport {\n type RateLimitStore,\n type BucketState,\n type BucketUpdate,\n type Reservation,\n type ReserveOptions,\n MemoryRateLimitStore,\n} from \"./store.js\";\nexport {\n RedisRateLimitStore,\n fromBunRedis,\n type RedisRateLimitStoreOptions,\n type RedisFailurePolicy,\n type RedisStoreDiagnostics,\n type MinimalRedisClient,\n} from \"./redis.js\";\n\nexport {\n WebhookClient,\n type WebhookClientOptions,\n type WebhookMessageOptions,\n} from \"./webhook.js\";\n\n/**\n * Discord.js-familiar alias for {@link RESTError}. `@discordjs/rest` throws `DiscordAPIError`\n * on a Discord-rejected request; Lunibee keeps `RESTError` as the canonical class and exports\n * this alias so Discord.js-shaped `catch (err) { if (err instanceof DiscordAPIError) … }` works.\n */\nexport { RESTError as DiscordAPIError };\n"
|
|
17
20
|
],
|
|
18
|
-
"mappings": "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",
|
|
19
|
-
"debugId": "
|
|
21
|
+
"mappings": "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",
|
|
22
|
+
"debugId": "8464D7D05D07B1F864756E2164756E21",
|
|
20
23
|
"names": []
|
|
21
24
|
}
|