lunibee 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +225 -223
- package/dist/builders/commands.d.ts +6 -4
- package/dist/builders/components.d.ts +23 -0
- package/dist/builders/index.d.ts +2 -2
- package/dist/builders/index.js +120 -24
- package/dist/builders/index.js.map +9 -8
- package/dist/collection/index.d.ts +68 -1
- package/dist/collection/index.js +335 -1
- package/dist/collection/index.js.map +4 -4
- package/dist/core/collector.d.ts +13 -0
- package/dist/core/events.d.ts +112 -2
- package/dist/core/index.d.ts +30 -93
- package/dist/core/index.js +3386 -957
- package/dist/core/index.js.map +48 -24
- package/dist/core/permissions.d.ts +48 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3806 -1193
- package/dist/index.js.map +64 -43
- package/dist/managers/advanced.d.ts +116 -0
- package/dist/managers/base.d.ts +13 -2
- package/dist/managers/emoji.d.ts +1 -1
- package/dist/managers/guild-resources.d.ts +109 -0
- package/dist/managers/guild.d.ts +33 -2
- package/dist/managers/index.d.ts +44 -6
- package/dist/managers/index.js +1186 -150
- package/dist/managers/index.js.map +27 -21
- package/dist/managers/message.d.ts +10 -1
- package/dist/rest/decoder.d.ts +23 -0
- package/dist/rest/errors.d.ts +53 -0
- package/dist/rest/index.d.ts +21 -17
- package/dist/rest/index.js +526 -264
- package/dist/rest/index.js.map +14 -8
- package/dist/rest/limiter.d.ts +42 -0
- package/dist/rest/redis.d.ts +18 -1
- package/dist/rest/route.d.ts +31 -0
- package/dist/rest/routes.d.ts +22 -0
- package/dist/rest/scheduler.d.ts +42 -0
- package/dist/rest/store.d.ts +29 -0
- package/dist/rest/transport.d.ts +40 -0
- package/dist/sharding/bus.d.ts +31 -0
- package/dist/sharding/cluster.d.ts +10 -0
- package/dist/sharding/index.d.ts +39 -2
- package/dist/sharding/index.js +1105 -404
- package/dist/sharding/index.js.map +18 -8
- package/dist/structures/base.d.ts +10 -0
- package/dist/structures/channels.d.ts +56 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +305 -311
- package/dist/structures/index.js.map +18 -14
- package/dist/structures/interactions.d.ts +38 -52
- package/dist/structures/options.d.ts +55 -0
- package/dist/structures/resources.d.ts +3 -3
- package/dist/types/gateway-events.d.ts +132 -0
- package/dist/types/gateway.d.ts +197 -0
- package/dist/types/index.d.ts +53 -260
- package/dist/types/index.js +44 -2
- package/dist/types/index.js.map +5 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/voice/index.d.ts +32 -1
- package/dist/voice/index.js +73 -7
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/close-codes.d.ts +23 -0
- package/dist/ws/decoder.d.ts +58 -0
- package/dist/ws/heartbeat.d.ts +87 -0
- package/dist/ws/index.d.ts +18 -65
- package/dist/ws/index.js +911 -385
- package/dist/ws/index.js.map +15 -5
- package/dist/ws/opcodes.d.ts +14 -0
- package/dist/ws/protocol.d.ts +110 -0
- package/dist/ws/reconnect.d.ts +108 -0
- package/dist/ws/send-budget.d.ts +17 -0
- package/dist/ws/session.d.ts +100 -0
- package/dist/ws/state.d.ts +27 -0
- package/dist/ws/transport.d.ts +80 -0
- package/package.json +21 -20
package/dist/rest/index.js.map
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../packages/rest/src/store.ts", "../../packages/rest/src/routes.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/limiter.ts", "../../packages/rest/src/transport.ts", "../../packages/rest/src/decoder.ts", "../../packages/rest/src/routes.ts", "../../packages/rest/src/redis.ts", "../../packages/rest/src/webhook.ts", "../../packages/rest/src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export interface BucketState {\
|
|
6
|
-
"/** Discord REST API route definitions. */\r\nexport const Routes = {\r\n /** Returns the current bot user. @returns User route. */ user: () =>\r\n \"/users/@me\",\r\n /** Returns one user. @param userId User identifier. @returns User route. */ userById:\r\n (userId: string) => `/users/${snowflake(userId, \"User ID\")}`,\r\n /** Returns a guild. @param guildId Guild identifier. @returns Guild route. */ guild: (\r\n guildId: string,\r\n ) => `/guilds/${snowflake(guildId, \"Guild ID\")}`,\r\n /** Returns a guild preview. @param guildId Guild identifier. */ guildPreview:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/preview`,\r\n /** Returns active threads for a guild. @param guildId Guild identifier. */ guildActiveThreads:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/threads/active`,\r\n /** Returns webhooks for a guild. @param guildId Guild identifier. */ guildWebhooks:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/webhooks`,\r\n /** Returns invites for a guild. @param guildId Guild identifier. */ guildInvites:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/invites`,\r\n /** Returns channels in a guild. @param guildId Guild identifier. @returns Channel collection route. */ guildChannels:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/channels`,\r\n /** Returns a guild member. @param guildId Guild identifier. @param userId User identifier. @returns Guild-member route. */ guildMember:\r\n (guildId: string, userId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/${snowflake(userId, \"User ID\")}`,\r\n /** Returns all guild roles. @param guildId Guild identifier. @returns Guild-role route. */ guildRoles:\r\n (guildId: string) => `/guilds/${snowflake(guildId, \"Guild ID\")}/roles`,\r\n /** Returns an individual guild role. */ guildRole: (\r\n guildId: string,\r\n roleId: string,\r\n ) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/roles/${snowflake(roleId, \"Role ID\")}`,\r\n /** Returns a member role route. */ guildMemberRole: (\r\n guildId: string,\r\n userId: string,\r\n roleId: string,\r\n ) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/${snowflake(userId, \"User ID\")}/roles/${snowflake(roleId, \"Role ID\")}`,\r\n /** Returns guild bans collection. */ guildBans: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bans`,\r\n /** Returns an individual guild ban. */ guildBan: (\r\n guildId: string,\r\n userId: string,\r\n ) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bans/${snowflake(userId, \"User ID\")}`,\r\n /** Returns a channel. @param channelId Channel identifier. @returns Channel route. */ channel:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}`,\r\n /** Returns messages in a channel. @param channelId Channel identifier. @returns Message collection route. */ channelMessages:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages`,\r\n /** Returns a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Message route. */ message:\r\n (channelId: string, messageId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/${snowflake(messageId, \"Message ID\")}`,\r\n /** Crossposts a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Crosspost route. */ crosspostMessage:\r\n (channelId: string, messageId: string) =>\r\n `${Routes.message(channelId, messageId)}/crosspost`,\r\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. */ messageReactions:\r\n (channelId: string, messageId: string, emoji: string) =>\r\n `${Routes.message(channelId, messageId)}/reactions/${encodeURIComponent(emoji)}`,\r\n /** Removes all reactions from a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Reaction collection route. */ messageReactionsAll:\r\n (channelId: string, messageId: string) =>\r\n `${Routes.message(channelId, messageId)}/reactions`,\r\n /** Returns pinned messages. @param channelId Channel identifier. @returns Pins route. */ channelPins:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/pins`,\r\n /** Pins a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Pin route. */ channelPin:\r\n (channelId: string, messageId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/pins/${snowflake(messageId, \"Message ID\")}`,\r\n /** Creates a thread from a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Message-thread route. */ messageThread:\r\n (channelId: string, messageId: string) =>\r\n `${Routes.message(channelId, messageId)}/threads`,\r\n /** Bulk deletes messages. @param channelId Channel identifier. @returns Bulk-delete route. */ channelBulkDelete:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/bulk-delete`,\r\n /** Returns channel webhooks. @param channelId Channel identifier. @returns Webhook collection route. */ channelWebhooks:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/webhooks`,\r\n /** Returns channel invites. @param channelId Channel identifier. @returns Invite collection route. */ channelInvites:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/invites`,\r\n /** Returns a webhook. @param webhookId Webhook identifier. @param token Optional webhook token. @returns Webhook route. */ webhook:\r\n (webhookId: string, token?: string) =>\r\n `/webhooks/${snowflake(webhookId, \"Webhook ID\")}${token ? `/${encodeURIComponent(token)}` : \"\"}`,\r\n /** Returns a webhook message. */ webhookMessage: (\r\n webhookId: string,\r\n token: string,\r\n messageId: string,\r\n ) =>\r\n `/webhooks/${snowflake(webhookId, \"Webhook ID\")}/${encodeURIComponent(token)}/messages/${snowflake(messageId, \"Message ID\")}`,\r\n /** Returns application commands for an application. @param applicationId Application identifier. @returns Global command route. */ applicationCommands:\r\n (applicationId: string) =>\r\n `/applications/${snowflake(applicationId, \"Application ID\")}/commands`,\r\n /** Returns one application command. @param applicationId Application identifier. @param commandId Command identifier. @returns Command route. */ applicationCommand:\r\n (applicationId: string, commandId: string) =>\r\n `/applications/${snowflake(applicationId, \"Application ID\")}/commands/${snowflake(commandId, \"Command ID\")}`,\r\n /** Returns guild application commands. @param applicationId Application identifier. @param guildId Guild identifier. @returns Guild command route. */ guildApplicationCommands:\r\n (applicationId: string, guildId: string) =>\r\n `/applications/${snowflake(applicationId, \"Application ID\")}/guilds/${snowflake(guildId, \"Guild ID\")}/commands`,\r\n /** Returns a single guild application command. @param applicationId Application identifier. @param guildId Guild identifier. @param commandId Command identifier. @returns Guild command route. */ guildApplicationCommand:\r\n (applicationId: string, guildId: string, commandId: string) =>\r\n `/applications/${snowflake(applicationId, \"Application ID\")}/guilds/${snowflake(guildId, \"Guild ID\")}/commands/${snowflake(commandId, \"Command ID\")}`,\r\n /** Returns an interaction callback endpoint. @param interactionId Interaction identifier. @param token Interaction token. @returns Interaction callback route. */ interactionCallback:\r\n (interactionId: string, token: string) =>\r\n `/interactions/${snowflake(interactionId, \"Interaction ID\")}/${encodeURIComponent(token)}/callback`,\r\n /** Returns the original interaction response endpoint. @param applicationId Application identifier. @param token Interaction token. @returns Original-response route. */ interactionOriginalResponse:\r\n (applicationId: string, token: string) =>\r\n `/webhooks/${snowflake(applicationId, \"Application ID\")}/${encodeURIComponent(token)}/messages/@original`,\r\n\r\n /** Returns public archived threads for a channel. @param channelId Channel identifier. @returns Public-archive route. */ channelPublicArchivedThreads:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/threads/archived/public`,\r\n /** Returns private archived threads for a channel. @param channelId Channel identifier. @returns Private-archive route. */ channelPrivateArchivedThreads:\r\n (channelId: string) =>\r\n `/channels/${snowflake(channelId, \"Channel ID\")}/threads/archived/private`,\r\n /** Returns a thread's members. @param threadId Thread identifier. @returns Thread-member route. */ threadMembers:\r\n (threadId: string) =>\r\n `/channels/${snowflake(threadId, \"Thread ID\")}/thread-members`,\r\n /** Returns guild scheduled events. @param guildId Guild identifier. @returns Scheduled-event collection route. */ guildScheduledEvents:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/scheduled-events`,\r\n /** Returns one guild scheduled event. @param guildId Guild identifier. @param eventId Event identifier. @returns Scheduled-event route. */ guildScheduledEvent:\r\n (guildId: string, eventId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/scheduled-events/${snowflake(eventId, \"Event ID\")}`,\r\n /** Returns guild automod rules. @param guildId Guild identifier. @returns Automod-rule collection route. */ guildAutoModerationRules:\r\n (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/auto-moderation/rules`,\r\n /** Returns one automod rule. @param guildId Guild identifier. @param ruleId Rule identifier. @returns Automod-rule route. */ guildAutoModerationRule:\r\n (guildId: string, ruleId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/auto-moderation/rules/${snowflake(ruleId, \"Rule ID\")}`,\r\n /** Returns current voice regions. @returns Voice-region route. */ voiceRegions:\r\n () => \"/voice/regions\",\r\n /** Returns the generic Gateway endpoint. @returns Gateway route. */ gateway:\r\n () => \"/gateway\",\r\n /** Returns the Gateway endpoint and recommended shard count for bots. @returns Gateway bot route. */ gatewayBot:\r\n () => \"/gateway/bot\",\r\n\r\n // ─── Audit Logs ──────────────────────────────────────────────────────────────\r\n /** Returns audit log entries for a guild. @param guildId Guild identifier. @returns Audit log route. */\r\n guildAuditLog: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/audit-logs`,\r\n\r\n // ─── Guild Members (bulk fetch) ───────────────────────────────────────────────\r\n /** Returns members in a guild (paginated). @param guildId Guild identifier. @returns Members collection route. */\r\n guildMembers: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members`,\r\n \r\n // ─── Welcome Screen & Onboarding ──────────────────────────────────────────────\r\n /** Returns the welcome screen for a guild. @param guildId Guild identifier. */\r\n guildWelcomeScreen: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/welcome-screen`,\r\n /** Returns the onboarding configuration for a guild. @param guildId Guild identifier. */\r\n guildOnboarding: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/onboarding`,\r\n\r\n // ─── Guild Prune ──────────────────────────────────────────────────────────────\r\n /** Returns or executes a prune operation for inactive members. @param guildId Guild identifier. @returns Prune route. */\r\n guildPrune: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/prune`,\r\n\r\n // ─── Stage Instances ──────────────────────────────────────────────────────────\r\n /** Returns the stage instances collection. @returns Stage instance root route. */\r\n stageInstances: () => \"/stage-instances\",\r\n /** Returns a stage instance by its channel. @param channelId Stage channel identifier. @returns Stage instance route. */\r\n stageInstanceByChannel: (channelId: string) =>\r\n `/stage-instances/${snowflake(channelId, \"Channel ID\")}`,\r\n\r\n // ─── Invites ──────────────────────────────────────────────────────────────────\r\n /** Returns an invite by code. @param code Invite code. @returns Invite route. */\r\n invite: (code: string) => `/invites/${encodeURIComponent(code)}`,\r\n /** Returns the guild vanity invite metadata. @param guildId Guild identifier. @returns Vanity URL route. */\r\n guildVanity: (guildId: string) =>\r\n `/guilds/${snowflake(guildId, \"Guild ID\")}/vanity-url`,\r\n\r\n // ─── Emojis ───────────────────────────────────────────────────────────────────\r\n /** Returns guild emojis, or a specific emoji. @param guildId Guild identifier. @param emojiId Optional emoji identifier. */\r\n guildEmoji: (guildId: string, emojiId?: string) =>\r\n emojiId\r\n ? `/guilds/${snowflake(guildId, \"Guild ID\")}/emojis/${snowflake(emojiId, \"Emoji ID\")}`\r\n : `/guilds/${snowflake(guildId, \"Guild ID\")}/emojis`,\r\n /** Returns application emojis, or a specific emoji. @param applicationId Application identifier. @param emojiId Optional emoji identifier. */\r\n applicationEmoji: (applicationId: string, emojiId?: string) =>\r\n emojiId\r\n ? `/applications/${snowflake(applicationId, \"Application ID\")}/emojis/${snowflake(emojiId, \"Emoji ID\")}`\r\n : `/applications/${snowflake(applicationId, \"Application ID\")}/emojis`,\r\n} as const;\r\n\r\n/** Validates a Discord snowflake route parameter. Accepts any decimal id that fits an\r\n * unsigned 64-bit integer, matching the snowflake rule `BaseStructure` applies in\r\n * `@lunibee/structures`. The guarantee this provides is that no non-numeric segment can\r\n * reshape the request path; it deliberately does not police id length, because Discord\r\n * snowflakes are 17 digits for early 2015 ids and reach 20 digits as the epoch advances.\r\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. */\r\nfunction snowflake(value: string, field: string): string {\r\n if (!/^\\d{1,20}$/.test(value))\r\n throw new TypeError(`${field} must be a valid Discord snowflake.`);\r\n return value;\r\n}\r\n",
|
|
7
|
-
"
|
|
8
|
-
"import {
|
|
9
|
-
"/** Error thrown when Discord rejects a REST request. */\r\nexport class RESTError extends Error {\r\n /** HTTP status returned by Discord. */ public readonly status: number;\r\n /** Discord API error code, when provided. */ public readonly code?: number;\r\n /** Raw Discord validation/error payload. */ public readonly errors?: unknown;\r\n /** HTTP method used for the failed request. */ public readonly method?: string;\r\n /** API path used for the failed request. */ public readonly path?: string;\r\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. */\r\n public constructor(\r\n message: string,\r\n status: number,\r\n code?: number,\r\n errors?: unknown,\r\n options: { method?: string; path?: string; cause?: unknown } = {},\r\n ) {\r\n super(\r\n message,\r\n options.cause === undefined ? undefined : { cause: options.cause },\r\n );\r\n this.name = \"RESTError\";\r\n this.status = status;\r\n this.code = code;\r\n this.errors = errors;\r\n this.method = options.method;\r\n this.path = options.path;\r\n }\r\n}\r\n\r\n/** Library version and source URL used for the Discord-compliant User-Agent. */\r\nimport packageJson from \"../package.json\" with { type: \"json\" };\r\nconst LIBRARY_VERSION = packageJson.version;\r\nconst LIBRARY_URL = \"https://github.com/Ekretos/lunibee\";\r\nconst USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${packageJson.version})`;\r\n\r\nimport {\r\n MemoryRateLimitStore,\r\n type RateLimitStore,\r\n type BucketState,\r\n} from \"./store.js\";\r\n\r\n/** Internal state shared by requests mapped to one Discord rate-limit bucket. */\r\ntype Bucket = { remaining: number; resetAt: number };\r\n/** A file attachment sent as part of a multipart REST request. */\r\nexport interface RESTFileAttachment {\r\n name: string;\r\n data: Blob | Uint8Array | ArrayBuffer;\r\n contentType?: string;\r\n}\r\n\r\n/** Query-string value accepted by {@link RESTRequestOptions.query}. */\r\nexport type RESTQuery =\r\n | URLSearchParams\r\n | Record<string, string | number | boolean | null | undefined>\r\n | string;\r\n\r\n/**\r\n * Options controlling an individual REST request.\r\n *\r\n * Lunibee's canonical call shape is positional (`post(path, body)`); these options\r\n * add the Discord.js-`@discordjs/rest`-familiar per-request knobs (`query`, `headers`,\r\n * `reason`, `auth`) additively without changing that positional default.\r\n */\r\nexport interface RESTRequestOptions {\r\n /** Abort signal used to cancel the request and any queued wait. */ signal?: AbortSignal;\r\n /** Query-string appended to the path (`URLSearchParams`, a record, or a raw string). */ query?: RESTQuery;\r\n /** Extra request headers merged in (library headers such as `Authorization` win). */ headers?: Record<\r\n string,\r\n string\r\n >;\r\n /** Audit-log reason, sent as the `X-Audit-Log-Reason` header. */ reason?: string;\r\n /** Set `false` to omit the `Authorization` header for this request. Defaults to `true`. */ auth?: boolean;\r\n}\r\n\r\n/**\r\n * Discord.js-familiar request payload. Superset of {@link RESTRequestOptions} that also\r\n * carries the `body` and `files`, matching `@discordjs/rest`'s `RequestData`. It powers the\r\n * additive `method(path, { body })` overloads without breaking the positional signature.\r\n */\r\nexport interface RequestData extends RESTRequestOptions {\r\n /** Request body. Encoded as JSON unless `files` are present (then multipart). */ body?: unknown;\r\n /** File attachments; presence upgrades the request to `multipart/form-data`. */ files?: RESTFileAttachment[];\r\n}\r\n\r\n/** Keys recognised on a {@link RequestData} wrapper, used to disambiguate it from a raw body. */\r\nconst REQUEST_DATA_KEYS = new Set([\r\n \"body\",\r\n \"files\",\r\n \"query\",\r\n \"headers\",\r\n \"reason\",\r\n \"auth\",\r\n \"signal\",\r\n]);\r\n\r\n/**\r\n * Detects a Discord.js-style `RequestData` wrapper so verb helpers can accept both\r\n * `post(path, rawBody)` (Lunibee-canonical) and `post(path, { body })` (Discord.js-familiar).\r\n * Conservative by design: only a plain object whose every own key is a known `RequestData`\r\n * key counts — a raw Discord payload (which always carries entity keys like `content`/`name`)\r\n * never matches, so existing positional callers are unaffected.\r\n */\r\nfunction isRequestData(value: unknown): value is RequestData {\r\n if (value === null || typeof value !== \"object\") return false;\r\n if (Array.isArray(value)) return false;\r\n if (typeof FormData !== \"undefined\" && value instanceof FormData)\r\n return false;\r\n if (\r\n value instanceof Blob ||\r\n value instanceof Uint8Array ||\r\n value instanceof ArrayBuffer\r\n )\r\n return false;\r\n const keys = Object.keys(value);\r\n if (keys.length === 0) return false;\r\n return keys.every((key) => REQUEST_DATA_KEYS.has(key));\r\n}\r\n\r\n/** Serialises a {@link RESTQuery} into a `?...` suffix (empty string when nothing to add). */\r\nfunction serializeQuery(query: RESTQuery | undefined): string {\r\n if (query === undefined) return \"\";\r\n if (typeof query === \"string\")\r\n return query.length === 0 || query.startsWith(\"?\")\r\n ? query\r\n : `?${query}`;\r\n const params =\r\n query instanceof URLSearchParams\r\n ? query\r\n : new URLSearchParams(\r\n Object.entries(query).flatMap(([key, value]) =>\r\n value === undefined || value === null\r\n ? []\r\n : [[key, String(value)] as [string, string]],\r\n ),\r\n );\r\n const serialized = params.toString();\r\n return serialized.length === 0 ? \"\" : `?${serialized}`;\r\n}\r\n\r\n/**\r\n * Discord's *major parameters* — the resource ids that give an endpoint its own\r\n * independent rate limit. Per the API documentation these are `channel_id`,\r\n * `guild_id` and `webhook_id`; webhook routes are additionally scoped by\r\n * their token. Any other id in a path shares its limit with sibling resources.\r\n */\r\nfunction majorParameter(path: string): string {\r\n const match =\r\n /^\\/(channels|guilds|webhooks)\\/(\\d+)(?:\\/([^/?]+))?/.exec(path);\r\n if (!match) return \"@none\";\r\n // A webhook's limit is keyed by both its id and its token.\r\n if (match[1] === \"webhooks\" && match[3] !== undefined)\r\n return `${match[2]}:${match[3]}`;\r\n return match[2]!;\r\n}\r\n\r\n/** Combines a bucket hash (or route) with a major parameter into a bucket key. */\r\nfunction scopeBucket(hashOrRoute: string, major: string): string {\r\n return `${hashOrRoute}|${major}`;\r\n}\r\n\r\n// ─── REST Hooks ───────────────────────────────────────────────────────────────\r\n\r\n/** Context passed to `RESTHooks.onRequest`. */\r\nexport interface RESTRequestContext {\r\n method: string;\r\n path: string;\r\n attempt: number;\r\n}\r\n/** Context passed to `RESTHooks.onResponse`. */\r\nexport interface RESTResponseContext {\r\n method: string;\r\n path: string;\r\n status: number;\r\n durationMs: number;\r\n}\r\n/** Context passed to `RESTHooks.onRateLimit`. */\r\nexport interface RESTRateLimitContext {\r\n method: string;\r\n path: string;\r\n retryAfterMs: number;\r\n global: boolean;\r\n bucket?: string;\r\n}\r\n/** Context passed to `RESTHooks.onRetry`. */\r\nexport interface RESTRetryContext {\r\n method: string;\r\n path: string;\r\n attempt: number;\r\n status: number;\r\n delayMs: number;\r\n}\r\n\r\n/**\r\n * Lightweight callback hooks for REST request lifecycle events.\r\n *\r\n * All callbacks are optional. They are dispatched in isolation from the request\r\n * path (deferred to a microtask, and any thrown error is swallowed), so a hook\r\n * cannot break or reject a request. Even so, hooks **must** be fast and\r\n * non-blocking: heavy synchronous work in a hook still steals event-loop time\r\n * from in-flight requests. Offload anything expensive to your own queue.\r\n */\r\nexport interface RESTHooks {\r\n /** Called just before every HTTP attempt. */\r\n onRequest?(ctx: RESTRequestContext): void;\r\n /** Called after a successful or failed response is received. */\r\n onResponse?(ctx: RESTResponseContext): void;\r\n /** Called when a rate limit is encountered and the client is about to wait. */\r\n onRateLimit?(ctx: RESTRateLimitContext): void;\r\n /** Called when a failed request is about to be retried. */\r\n onRetry?(ctx: RESTRetryContext): void;\r\n}\r\n\r\n/** Configures which REST failures may be retried. */\r\nexport interface RetryPolicy {\r\n /** Maximum retry attempts. */ maxRetries: number;\r\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. */ shouldRetry(\r\n method: string,\r\n status: number,\r\n ): boolean;\r\n /** Calculates delay before the next attempt. @param attempt Zero-based retry number. @param retryAfter Server-provided delay, when available. @returns Delay in milliseconds. */ getDelay(\r\n attempt: number,\r\n retryAfter?: number,\r\n ): number;\r\n}\r\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. */\r\nexport function createRetryPolicy(maxRetries = 2): RetryPolicy {\r\n if (!Number.isFinite(maxRetries) || maxRetries < 0)\r\n throw new TypeError(\"maxRetries must be a non-negative finite number.\");\r\n return {\r\n maxRetries: Math.floor(maxRetries),\r\n shouldRetry(method, status) {\r\n const normalized = method.toUpperCase();\r\n return (\r\n status === 429 ||\r\n (status >= 500 &&\r\n status <= 599 &&\r\n [\"GET\", \"HEAD\", \"PUT\", \"DELETE\"].includes(normalized))\r\n );\r\n },\r\n getDelay(attempt, retryAfter) {\r\n if (retryAfter !== undefined) return Math.max(0, retryAfter * 1000);\r\n return Math.min(10_000, 500 * 2 ** attempt) + Math.random() * 250;\r\n },\r\n };\r\n}\r\n\r\n/** Bun-native REST transport with Discord bucket-aware rate limiting, retries, and cancellation. */\r\nexport class REST {\r\n readonly #baseURL: string;\r\n readonly #timeout: number;\r\n readonly #retryPolicy: RetryPolicy;\r\n #token?: string;\r\n readonly #store: RateLimitStore;\r\n readonly #localQueues = new Map<string, Promise<void>>();\r\n #hooks: RESTHooks = {};\r\n /** Creates a REST transport. @param options Transport configuration. @throws {TypeError} If retry configuration is invalid. */\r\n public constructor(\r\n options: {\r\n token?: string;\r\n timeout?: number;\r\n retries?: number;\r\n retryPolicy?: RetryPolicy;\r\n baseURL?: string;\r\n hooks?: RESTHooks;\r\n store?: RateLimitStore;\r\n } = {},\r\n ) {\r\n this.#token = options.token;\r\n this.#timeout = Math.max(1, options.timeout ?? 15_000);\r\n this.#retryPolicy =\r\n options.retryPolicy ?? createRetryPolicy(options.retries ?? 2);\r\n this.#baseURL = (\r\n options.baseURL ?? \"https://discord.com/api/v10\"\r\n ).replace(/\\/$/, \"\");\r\n this.#store = options.store ?? new MemoryRateLimitStore();\r\n if (options.hooks) this.#hooks = options.hooks;\r\n }\r\n /** Replaces the current hook set. Pass an empty object to clear all hooks. */\r\n public setHooks(hooks: RESTHooks): this {\r\n this.#hooks = hooks;\r\n return this;\r\n }\r\n /** Sets the authentication token. @param token Discord bot token. @returns Nothing. @throws {TypeError} If token is empty. */\r\n public setToken(token: string): void {\r\n if (!token.trim())\r\n throw new TypeError(\"A Discord bot token is required.\");\r\n this.#token = token;\r\n }\r\n /** Executes an HTTP request with Discord bucket/global rate limits, retry handling, and AbortSignal support.\r\n * Accepts a JSON body, a FormData body (for file uploads), or undefined.\r\n * @param method HTTP method.\r\n * @param path API path.\r\n * @param body Optional JSON body, FormData for file uploads, or undefined.\r\n * @param options Request options: cancellation `signal` plus the Discord.js-familiar\r\n * `query`, `headers`, `reason`, `auth`, and `files` (which upgrade to multipart).\r\n * @returns Decoded response body.\r\n * @throws {RESTError} If Discord or transport rejects the request.\r\n */\r\n public async request<T>(\r\n method: string,\r\n path: string,\r\n body?: unknown,\r\n options: RequestData = {},\r\n ): Promise<T> {\r\n const normalizedMethod = method.toUpperCase();\r\n if (!normalizedMethod) throw new TypeError(\"REST method is required.\");\r\n if (!path.startsWith(\"/\"))\r\n throw new TypeError(\"REST paths must start with '/'.\");\r\n // Upgrade to multipart when file attachments are supplied via options.\r\n const files = options.files;\r\n if (files && files.length > 0 && !(body instanceof FormData))\r\n body = this.#fileForm(body, files);\r\n const query = serializeQuery(options.query);\r\n const finalPath = query ? `${path}${query}` : path;\r\n const route = `${normalizedMethod}:${this.#normalizeRoute(path)}`;\r\n // Discord scopes a rate limit by (bucket hash, major parameter): two\r\n // channels sharing an endpoint have independent limits. The hash stays\r\n // cached per normalized route (Discord returns the same hash for an\r\n // endpoint regardless of major parameter), but every key that gates\r\n // sending must carry the major parameter, or unrelated resources\r\n // serialize against one another and share one `remaining` counter.\r\n const major = majorParameter(path);\r\n let bucketKey = scopeBucket(\r\n (await this.#store.getBucketHash(route)) ?? route,\r\n major,\r\n );\r\n\r\n // The map is keyed by the bucket as known at enqueue time; `bucketKey`\r\n // may be reassigned to the server bucket hash during the request, so we\r\n // keep `queueKey` fixed for cleanup.\r\n const queueKey = bucketKey;\r\n const previous = this.#localQueues.get(queueKey) ?? Promise.resolve();\r\n let release!: () => void;\r\n const gate = new Promise<void>((resolve) => {\r\n release = resolve;\r\n });\r\n this.#localQueues.set(queueKey, gate);\r\n await this.#abortable(previous, options.signal, path);\r\n try {\r\n for (\r\n let attempt = 0;\r\n attempt <= this.#retryPolicy.maxRetries;\r\n attempt++\r\n ) {\r\n await this.#wait(bucketKey, options.signal, path);\r\n await this.#waitGlobal(options.signal, path);\r\n const controller = new AbortController();\r\n const onAbort = () => controller.abort(options.signal?.reason);\r\n if (options.signal?.aborted)\r\n throw this.#abortError(path, options.signal.reason);\r\n options.signal?.addEventListener(\"abort\", onAbort, {\r\n once: true,\r\n });\r\n const timer = setTimeout(\r\n () =>\r\n controller.abort(\r\n new DOMException(\r\n \"REST request timeout\",\r\n \"TimeoutError\",\r\n ),\r\n ),\r\n this.#timeout,\r\n );\r\n // Build headers and body based on whether this is a multipart request\r\n const isFormData =\r\n typeof FormData !== \"undefined\" && body instanceof FormData;\r\n // Caller headers first; library-managed headers below take precedence.\r\n const headers: Record<string, string> = { ...options.headers };\r\n if (options.auth !== false && this.#token)\r\n headers[\"Authorization\"] = `Bot ${this.#token}`;\r\n headers[\"User-Agent\"] = USER_AGENT;\r\n if (options.reason !== undefined)\r\n headers[\"X-Audit-Log-Reason\"] = encodeURIComponent(\r\n options.reason,\r\n );\r\n if (!isFormData) headers[\"Content-Type\"] = \"application/json\";\r\n const fetchBody = isFormData\r\n ? (body as FormData)\r\n : body === undefined\r\n ? undefined\r\n : JSON.stringify(body);\r\n try {\r\n this.#emit(this.#hooks.onRequest, {\r\n method: normalizedMethod,\r\n path,\r\n attempt,\r\n });\r\n const requestStart = Date.now();\r\n const response = await fetch(\r\n `${this.#baseURL}${finalPath}`,\r\n {\r\n method: normalizedMethod,\r\n headers,\r\n body: fetchBody,\r\n signal: controller.signal,\r\n },\r\n );\r\n this.#emit(this.#hooks.onResponse, {\r\n method: normalizedMethod,\r\n path,\r\n status: response.status,\r\n durationMs: Date.now() - requestStart,\r\n });\r\n bucketKey = await this.#update(\r\n response,\r\n bucketKey,\r\n route,\r\n major,\r\n );\r\n const payload = await this.#readPayload(response);\r\n if (response.ok)\r\n return (\r\n response.status === 204 ? undefined : payload\r\n ) as T;\r\n const data = this.#errorData(payload);\r\n const retryAfter =\r\n response.status === 429\r\n ? this.#retryAfter(response, data)\r\n : undefined;\r\n if (data.global && retryAfter !== undefined) {\r\n const currentGlobal =\r\n await this.#store.getGlobalReset();\r\n await this.#store.setGlobalReset(\r\n Math.max(\r\n currentGlobal,\r\n Date.now() + retryAfter * 1000,\r\n ),\r\n );\r\n }\r\n // Fire for ANY 429, including a global limit that arrives\r\n // without a Retry-After header; fall back to 1s in that case.\r\n if (response.status === 429)\r\n this.#emit(this.#hooks.onRateLimit, {\r\n method: normalizedMethod,\r\n path,\r\n retryAfterMs: (retryAfter ?? 1) * 1000,\r\n global: data.global ?? false,\r\n bucket: await this.#store.getBucketHash(route),\r\n });\r\n if (\r\n attempt < this.#retryPolicy.maxRetries &&\r\n this.#retryPolicy.shouldRetry(\r\n normalizedMethod,\r\n response.status,\r\n )\r\n ) {\r\n const delay = this.#retryPolicy.getDelay(\r\n attempt,\r\n retryAfter,\r\n );\r\n this.#emit(this.#hooks.onRetry, {\r\n method: normalizedMethod,\r\n path,\r\n attempt,\r\n status: response.status,\r\n delayMs: delay,\r\n });\r\n await this.#sleep(delay, options.signal, path);\r\n continue;\r\n }\r\n throw new RESTError(\r\n data.message ??\r\n response.statusText ??\r\n `Discord REST request failed with status ${response.status}`,\r\n response.status,\r\n data.code,\r\n payload,\r\n { method: normalizedMethod, path },\r\n );\r\n } catch (error) {\r\n if (error instanceof RESTError) throw error;\r\n if (options.signal?.aborted)\r\n throw this.#abortError(path, options.signal.reason);\r\n const isTimeout =\r\n error instanceof DOMException &&\r\n (error.name === \"TimeoutError\" ||\r\n error.name === \"AbortError\");\r\n if (\r\n !isTimeout &&\r\n attempt < this.#retryPolicy.maxRetries &&\r\n this.#retryPolicy.shouldRetry(normalizedMethod, 0)\r\n ) {\r\n await this.#sleep(\r\n this.#retryPolicy.getDelay(attempt),\r\n options.signal,\r\n path,\r\n );\r\n continue;\r\n }\r\n throw new RESTError(\r\n isTimeout\r\n ? `Discord REST request timed out after ${this.#timeout}ms`\r\n : \"Discord REST request failed\",\r\n 0,\r\n undefined,\r\n undefined,\r\n { method: normalizedMethod, path, cause: error },\r\n );\r\n } finally {\r\n clearTimeout(timer);\r\n options.signal?.removeEventListener(\"abort\", onAbort);\r\n }\r\n }\r\n throw new RESTError(\r\n \"Discord REST request exhausted its retry attempts\",\r\n 0,\r\n undefined,\r\n undefined,\r\n { method: normalizedMethod, path },\r\n );\r\n } finally {\r\n release();\r\n // Drop our entry so idle buckets don't accumulate. Only delete if it\r\n // is still ours: a later request for the same key may have replaced it.\r\n if (this.#localQueues.get(queueKey) === gate)\r\n this.#localQueues.delete(queueKey);\r\n }\r\n }\r\n /**\r\n * Normalizes a verb helper's second argument into `(body, options)`.\r\n * Supports both `verb(path, rawBody, options?)` (Lunibee-canonical positional) and the\r\n * Discord.js-familiar `verb(path, { body, query, headers, reason, files })` wrapper.\r\n */\r\n #dispatch<T>(\r\n method: string,\r\n path: string,\r\n bodyOrOptions?: unknown,\r\n options?: RequestData,\r\n ): Promise<T> {\r\n if (options === undefined && isRequestData(bodyOrOptions))\r\n return this.request<T>(\r\n method,\r\n path,\r\n bodyOrOptions.body,\r\n bodyOrOptions,\r\n );\r\n return this.request<T>(method, path, bodyOrOptions, options);\r\n }\r\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. */ public get<\r\n T,\r\n >(path: string, options?: RequestData): Promise<T> {\r\n return this.request<T>(\"GET\", path, options?.body, options);\r\n }\r\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. */\r\n public post<T>(path: string, options: RequestData): Promise<T>;\r\n public post<T>(\r\n path: string,\r\n body?: unknown,\r\n options?: RequestData,\r\n ): Promise<T>;\r\n public post<T>(\r\n path: string,\r\n body?: unknown,\r\n options?: RequestData,\r\n ): Promise<T> {\r\n return this.#dispatch<T>(\"POST\", path, body, options);\r\n }\r\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. */\r\n public patch<T>(path: string, options: RequestData): Promise<T>;\r\n public patch<T>(\r\n path: string,\r\n body?: unknown,\r\n options?: RequestData,\r\n ): Promise<T>;\r\n public patch<T>(\r\n path: string,\r\n body?: unknown,\r\n options?: RequestData,\r\n ): Promise<T> {\r\n return this.#dispatch<T>(\"PATCH\", path, body, options);\r\n }\r\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. */\r\n public put<T>(path: string, options: RequestData): Promise<T>;\r\n public put<T>(\r\n path: string,\r\n body?: unknown,\r\n options?: RequestData,\r\n ): Promise<T>;\r\n public put<T>(\r\n path: string,\r\n body?: unknown,\r\n options?: RequestData,\r\n ): Promise<T> {\r\n return this.#dispatch<T>(\"PUT\", path, body, options);\r\n }\r\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. */ public delete<\r\n T,\r\n >(path: string, options?: RequestData): Promise<T> {\r\n return this.request<T>(\"DELETE\", path, options?.body, options);\r\n }\r\n /**\r\n * Builds a multipart form with a JSON payload and file attachments.\r\n */\r\n #fileForm(payload: unknown, files: RESTFileAttachment[]): FormData {\r\n const form = new FormData();\r\n form.append(\"payload_json\", JSON.stringify(payload));\r\n for (let i = 0; i < files.length; i++) {\r\n const file = files[i]!;\r\n const blob =\r\n file.data instanceof Blob\r\n ? file.data\r\n : new Blob([file.data as ArrayBuffer], {\r\n type: file.contentType ?? \"application/octet-stream\",\r\n });\r\n form.append(`files[${i}]`, blob, file.name);\r\n }\r\n return form;\r\n }\r\n\r\n /**\r\n * Sends a multipart/form-data POST request for file uploads.\r\n * Attaches a JSON payload as `payload_json` and files as additional fields.\r\n * @param path API path.\r\n * @param payload JSON payload (e.g. message content, embeds).\r\n * @param files Array of file attachments.\r\n * @param options Cancellation options.\r\n * @returns Decoded response.\r\n * @throws {RESTError} When the request fails.\r\n */\r\n public postWithFiles<T>(\r\n path: string,\r\n payload: unknown,\r\n files: RESTFileAttachment[],\r\n options?: RESTRequestOptions,\r\n ): Promise<T> {\r\n return this.request<T>(\r\n \"POST\",\r\n path,\r\n this.#fileForm(payload, files),\r\n options,\r\n );\r\n }\r\n\r\n /**\r\n * Sends a multipart/form-data PATCH request for editing messages with files.\r\n * @param path API path.\r\n * @param payload JSON payload.\r\n * @param files Array of file attachments.\r\n * @param options Cancellation options.\r\n * @returns Decoded response.\r\n * @throws {RESTError} When the request fails.\r\n */\r\n public patchWithFiles<T>(\r\n path: string,\r\n payload: unknown,\r\n files: RESTFileAttachment[],\r\n options?: RESTRequestOptions,\r\n ): Promise<T> {\r\n return this.request<T>(\r\n \"PATCH\",\r\n path,\r\n this.#fileForm(payload, files),\r\n options,\r\n );\r\n }\r\n /** Dispatches a lifecycle hook in isolation: deferred to a microtask so it\r\n * cannot block the request path, with any thrown error swallowed so a faulty\r\n * hook cannot reject the request. @param hook Optional callback. @param ctx Event context. */\r\n #emit<C>(hook: ((ctx: C) => void) | undefined, ctx: C): void {\r\n if (!hook) return;\r\n Promise.resolve().then(() => hook(ctx)).catch(() => {});\r\n }\r\n /** Waits for a route bucket while respecting cancellation. @param bucketKey Bucket identifier. @param signal Optional cancellation signal. @param path Request path for error context. @returns Promise fulfilled when sending is permitted. @throws {RESTError} If the request is aborted. */\r\n async #wait(\r\n bucketKey: string,\r\n signal: AbortSignal | undefined,\r\n path: string,\r\n ): Promise<void> {\r\n const bucket = await this.#store.getBucket(bucketKey);\r\n if (!bucket) return;\r\n const delay = bucket.resetAt - Date.now();\r\n if (bucket.remaining <= 0 && delay > 0)\r\n await this.#sleep(delay, signal, path);\r\n }\r\n /** Waits for the global Discord rate limit while respecting cancellation. @param signal Optional cancellation signal. @param path Request path for error context. @returns Promise fulfilled when the global limit expires. @throws {RESTError} If the request is aborted. */\r\n async #waitGlobal(\r\n signal: AbortSignal | undefined,\r\n path: string,\r\n ): Promise<void> {\r\n const globalResetAt = await this.#store.getGlobalReset();\r\n const delay = globalResetAt - Date.now();\r\n if (delay > 0) await this.#sleep(delay, signal, path);\r\n }\r\n /** Waits for a delay or aborts immediately. @param delay Delay in milliseconds. @param signal Optional cancellation signal. @param path Request path. @returns Promise fulfilled after delay. @throws {RESTError} If aborted. */\r\n async #sleep(\r\n delay: number,\r\n signal: AbortSignal | undefined,\r\n path: string,\r\n ): Promise<void> {\r\n if (delay <= 0) return;\r\n await new Promise<void>((resolve, reject) => {\r\n const timer = setTimeout(resolve, delay);\r\n const abort = () => {\r\n clearTimeout(timer);\r\n reject(this.#abortError(path, signal?.reason));\r\n };\r\n if (signal?.aborted) return abort();\r\n signal?.addEventListener(\"abort\", abort, { once: true });\r\n });\r\n }\r\n /** Waits for a queued request or aborts. @param promise Queue predecessor. @param signal Optional cancellation signal. @param path Request path. @returns Promise fulfilled when predecessor releases. @throws {RESTError} If aborted. */\r\n async #abortable(\r\n promise: Promise<void>,\r\n signal: AbortSignal | undefined,\r\n path: string,\r\n ): Promise<void> {\r\n if (!signal) return promise;\r\n if (signal.aborted) throw this.#abortError(path, signal.reason);\r\n return Promise.race([\r\n promise,\r\n new Promise<void>((_, reject) =>\r\n signal.addEventListener(\r\n \"abort\",\r\n () => reject(this.#abortError(path, signal.reason)),\r\n { once: true },\r\n ),\r\n ),\r\n ]);\r\n }\r\n /** Creates a consistent cancellation error. @param path Request path. @param reason Abort reason. @returns REST cancellation error. */\r\n #abortError(path: string, reason: unknown): RESTError {\r\n return new RESTError(\r\n reason instanceof Error\r\n ? reason.message\r\n : \"Discord REST request was aborted\",\r\n 0,\r\n undefined,\r\n undefined,\r\n { path, cause: reason },\r\n );\r\n }\r\n /** Reads a Discord response payload. @param response HTTP response. @returns JSON or text payload. */\r\n async #readPayload(response: Response): Promise<unknown> {\r\n if (response.status === 204) return undefined;\r\n const contentType = response.headers.get(\"content-type\") ?? \"\";\r\n if (contentType.includes(\"application/json\"))\r\n return response.json().catch(() => undefined);\r\n return response.text().catch(() => undefined);\r\n }\r\n /** Extracts retry metadata from a Discord payload. @param payload Response payload. @returns Normalized error metadata. */\r\n #errorData(payload: unknown): {\r\n message?: string;\r\n code?: number;\r\n retry_after?: number;\r\n global?: boolean;\r\n } {\r\n if (!payload || typeof payload !== \"object\") return {};\r\n const data = payload as Record<string, unknown>;\r\n return {\r\n message:\r\n typeof data.message === \"string\" ? data.message : undefined,\r\n code: typeof data.code === \"number\" ? data.code : undefined,\r\n retry_after:\r\n typeof data.retry_after === \"number\"\r\n ? data.retry_after\r\n : undefined,\r\n global: data.global === true,\r\n };\r\n }\r\n /** Resolves Retry-After from headers or JSON. @param response HTTP response. @param data Parsed response metadata. @returns Retry delay in seconds. */\r\n #retryAfter(response: Response, data: { retry_after?: number }): number {\r\n const header = Number(response.headers.get(\"Retry-After\"));\r\n const retryAfter =\r\n data.retry_after ?? (Number.isFinite(header) ? header : 1);\r\n return Math.max(0, retryAfter);\r\n }\r\n /** Normalizes Discord routes for stable bucket discovery. @param path API path. @returns Normalized route. */\r\n #normalizeRoute(path: string): string {\r\n const queryIndex = path.indexOf(\"?\");\r\n const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);\r\n return routePath.replace(/\\/\\d+(?=\\/|$)/g, \"/:id\");\r\n }\r\n /** Updates bucket state from Discord's rate-limit headers and records the server bucket hash. @param response HTTP response. @param bucket Current bucket. @param route Normalized local route key. @param bucketKey Current bucket identifier. @param major Major parameter scoping the bucket. @returns Bucket state used for subsequent attempts. */\r\n async #update(\r\n response: Response,\r\n bucketKey: string,\r\n route: string,\r\n major: string,\r\n ): Promise<string> {\r\n let currentKey = bucketKey;\r\n const serverBucket = response.headers.get(\"X-RateLimit-Bucket\");\r\n if (serverBucket) {\r\n await this.#store.setBucketHash(route, serverBucket);\r\n currentKey = scopeBucket(serverBucket, major);\r\n }\r\n\r\n let bucket = (await this.#store.getBucket(currentKey)) ?? {\r\n remaining: 1,\r\n resetAt: 0,\r\n };\r\n const remaining = Number(response.headers.get(\"X-RateLimit-Remaining\"));\r\n const resetAfter = Number(\r\n response.headers.get(\"X-RateLimit-Reset-After\"),\r\n );\r\n const reset = Number(response.headers.get(\"X-RateLimit-Reset\"));\r\n\r\n if (Number.isFinite(remaining))\r\n bucket.remaining = Math.max(0, remaining);\r\n if (Number.isFinite(resetAfter))\r\n bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;\r\n else if (Number.isFinite(reset)) bucket.resetAt = reset * 1000;\r\n\r\n if (response.status === 429) {\r\n const retryAfter = Number(response.headers.get(\"Retry-After\"));\r\n if (Number.isFinite(retryAfter))\r\n bucket.resetAt = Math.max(\r\n bucket.resetAt,\r\n Date.now() + retryAfter * 1000,\r\n );\r\n }\r\n await this.#store.updateBucket(currentKey, bucket);\r\n\r\n if (response.headers.get(\"X-RateLimit-Global\") === \"true\") {\r\n const retryAfter = Number(response.headers.get(\"Retry-After\"));\r\n if (Number.isFinite(retryAfter)) {\r\n const currentGlobal = await this.#store.getGlobalReset();\r\n await this.#store.setGlobalReset(\r\n Math.max(currentGlobal, Date.now() + retryAfter * 1000),\r\n );\r\n }\r\n }\r\n\r\n return currentKey;\r\n }\r\n}\r\n\r\nexport { Routes } from \"./routes.js\";\r\nexport {\r\n type RateLimitStore,\r\n type BucketState,\r\n MemoryRateLimitStore,\r\n} from \"./store.js\";\r\nexport {\r\n RedisRateLimitStore,\r\n type RedisRateLimitStoreOptions,\r\n type MinimalRedisClient,\r\n} from \"./redis.js\";\r\n\r\nexport {\r\n WebhookClient,\r\n type WebhookClientOptions,\r\n type WebhookMessageOptions,\r\n} from \"./webhook.js\";\r\n\r\n/**\r\n * Discord.js-familiar alias for {@link RESTError}. `@discordjs/rest` throws `DiscordAPIError`\r\n * on a Discord-rejected request; Lunibee keeps `RESTError` as the canonical class and exports\r\n * this alias so Discord.js-shaped `catch (err) { if (err instanceof DiscordAPIError) … }` works.\r\n */\r\nexport { RESTError as DiscordAPIError };\r\n"
|
|
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\\/[^/?]+\\/)[^/?]+/, \"$1:token\")\n .replace(/^(\\/interactions\\/[^/?]+\\/)[^/?]+/, \"$1:token\")\n );\n}\n\n/** Error thrown when Discord rejects a REST request. */\nexport class RESTError extends Error {\n /** Failure category. */ public readonly kind: RESTErrorKind;\n /** HTTP status returned by Discord. */ public readonly status: number;\n /** Discord API error code, when provided. */ public readonly code?: number;\n /** Raw Discord validation/error payload. */ public readonly errors?: unknown;\n /** HTTP method used for the failed request. */ public readonly method?: string;\n /** API path used for the failed request. */ public readonly path?: 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.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 const timer = setTimeout(resolve, delay);\n const abort = (): void => {\n clearTimeout(timer);\n reject(abortError(path, signal?.reason));\n };\n if (signal?.aborted) return abort();\n signal?.addEventListener(\"abort\", abort, { 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 Promise.race([\n promise,\n new Promise<void>((_, reject) =>\n signal.addEventListener(\n \"abort\",\n () => reject(abortError(path, signal.reason)),\n { once: true },\n ),\n ),\n ]);\n}\n",
|
|
7
|
+
"/** 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. */ method: string;\n /** Raw request path, without query string. */ path: string;\n /** Normalized `METHOD:/path/:id` route used for bucket-hash lookup. */ route: string;\n /** Major parameter scoping the limit (channel, guild, webhook id[:token]). */ 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 return 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+)(?:\\/([^/?]+))?/.exec(\n path,\n );\n if (!match) return \"@none\";\n // A webhook's limit is keyed by both its id and its token.\n if (match[1] === \"webhooks\" && match[3] !== undefined)\n return `${match[2]}:${match[3]}`;\n return match[2]!;\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) ?? Promise.resolve();\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 await abortable(previous, signal, path);\n return await task(release);\n } finally {\n release();\n }\n }\n}\n",
|
|
9
|
+
"import { sleep } from \"./errors.js\";\nimport { scopeBucket } from \"./route.js\";\nimport type { RateLimitStore } from \"./store.js\";\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 /** Whether responses may arrive out of order for one bucket. */\n readonly #concurrent: boolean;\n\n /**\n * @param store Rate-limit state store.\n * @param options `concurrent`: set when several requests per bucket can be\n * in flight, so a late response cannot raise `remaining` within its window.\n */\n public constructor(\n store: RateLimitStore,\n options: { concurrent?: boolean } = {},\n ) {\n this.#store = store;\n this.#concurrent = options.concurrent ?? false;\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 /** Resolves the bucket key a request should be gated on. */\n public async resolveBucketKey(\n route: string,\n major: string,\n ): Promise<string> {\n return scopeBucket(\n (await this.#store.getBucketHash(route)) ?? route,\n major,\n );\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 /**\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.\n */\n public async acquire(\n bucketKey: string,\n signal: AbortSignal | undefined,\n path: string,\n ): Promise<void> {\n await this.#waitGlobal(signal, path);\n if (!this.#store.reserve) {\n const bucket = await this.#store.getBucket(bucketKey);\n if (bucket) {\n const delay = bucket.resetAt - Date.now();\n if (bucket.remaining <= 0 && delay > 0)\n await sleep(delay, signal, path);\n }\n return;\n }\n // Each refusal carries the wait until the window resets, so the loop\n // advances by a real interval every time; the bound only guards against\n // a pathological store that keeps refusing with a zero wait.\n for (let attempt = 0; attempt < 8; attempt++) {\n const reservation = await this.#store.reserve(bucketKey);\n if (reservation.granted) return;\n await sleep(Math.max(1, reservation.retryAfterMs), signal, path);\n await this.#waitGlobal(signal, path);\n }\n }\n\n async #waitGlobal(\n signal: AbortSignal | undefined,\n path: string,\n ): Promise<void> {\n const globalDelay = (await this.#store.getGlobalReset()) - Date.now();\n if (globalDelay > 0) await sleep(globalDelay, signal, path);\n }\n\n /** Extends the global limit, never shortening a longer one already recorded. */\n public async noteGlobalReset(resetAt: number): Promise<void> {\n const current = await this.#store.getGlobalReset();\n await this.#store.setGlobalReset(Math.max(current, resetAt));\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 ): Promise<string> {\n let currentKey = bucketKey;\n const serverBucket = response.headers.get(\"X-RateLimit-Bucket\");\n if (serverBucket) {\n await this.#store.setBucketHash(route, serverBucket);\n currentKey = scopeBucket(serverBucket, major);\n }\n\n const stored = await this.#store.getBucket(currentKey);\n const bucket = stored ? { ...stored } : { remaining: 1, resetAt: 0 };\n const windowActive =\n this.#concurrent && !!stored && stored.resetAt > Date.now();\n const remaining = Number(response.headers.get(\"X-RateLimit-Remaining\"));\n const resetAfter = Number(\n response.headers.get(\"X-RateLimit-Reset-After\"),\n );\n const reset = Number(response.headers.get(\"X-RateLimit-Reset\"));\n\n if (Number.isFinite(remaining))\n bucket.remaining = Math.max(0, remaining);\n if (Number.isFinite(resetAfter))\n bucket.resetAt = Date.now() + Math.max(0, resetAfter) * 1000;\n else if (Number.isFinite(reset)) bucket.resetAt = reset * 1000;\n // With concurrent requests, responses from one window can arrive out of\n // order: never let a late one hand back allowance already reserved.\n if (windowActive && stored) {\n bucket.remaining = Math.min(bucket.remaining, stored.remaining);\n bucket.resetAt = Math.max(bucket.resetAt, stored.resetAt);\n }\n\n if (response.status === 429) {\n const retryAfter = Number(response.headers.get(\"Retry-After\"));\n if (Number.isFinite(retryAfter))\n bucket.resetAt = Math.max(\n bucket.resetAt,\n Date.now() + retryAfter * 1000,\n );\n }\n await this.#store.updateBucket(currentKey, bucket);\n\n if (response.headers.get(\"X-RateLimit-Global\") === \"true\") {\n const retryAfter = Number(response.headers.get(\"Retry-After\"));\n if (Number.isFinite(retryAfter))\n await this.noteGlobalReset(Date.now() + retryAfter * 1000);\n }\n\n return currentKey;\n }\n}\n",
|
|
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. */ method: string;\n /** Path with query string, relative to the transport's base URL. */ path: string;\n /** Request headers, already including authorization. */ headers: Record<\n string,\n string\n >;\n /** Encoded body, or undefined for a bodyless request. */ body?: BodyInit;\n /** Caller cancellation signal. */ 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",
|
|
12
|
+
"/** Discord REST API route definitions. */\nexport const Routes = {\n /** Returns the current bot user. @returns User route. */ user: () =>\n \"/users/@me\",\n /** Returns one user. @param userId User identifier. @returns User route. */ userById:\n (userId: string) => `/users/${snowflake(userId, \"User ID\")}`,\n /** Returns a guild. @param guildId Guild identifier. @returns Guild route. */ guild: (\n guildId: string,\n ) => `/guilds/${snowflake(guildId, \"Guild ID\")}`,\n /** Returns a guild preview. @param guildId Guild identifier. */ guildPreview:\n (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/preview`,\n /** Returns active threads for a guild. @param guildId Guild identifier. */ guildActiveThreads:\n (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/threads/active`,\n /** Returns webhooks for a guild. @param guildId Guild identifier. */ guildWebhooks:\n (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/webhooks`,\n /** Returns invites for a guild. @param guildId Guild identifier. */ guildInvites:\n (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/invites`,\n /** Returns channels in a guild. @param guildId Guild identifier. @returns Channel collection route. */ guildChannels:\n (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. */ guildMember:\n (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. */ guildRoles:\n (guildId: string) => `/guilds/${snowflake(guildId, \"Guild ID\")}/roles`,\n /** Returns an individual guild role. */ guildRole: (\n guildId: string,\n roleId: string,\n ) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/roles/${snowflake(roleId, \"Role ID\")}`,\n /** Returns a member role route. */ guildMemberRole: (\n guildId: string,\n userId: string,\n roleId: string,\n ) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/members/${snowflake(userId, \"User ID\")}/roles/${snowflake(roleId, \"Role ID\")}`,\n /** Returns guild bans collection. */ guildBans: (guildId: string) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bans`,\n /** Returns an individual guild ban. */ guildBan: (\n guildId: string,\n userId: string,\n ) =>\n `/guilds/${snowflake(guildId, \"Guild ID\")}/bans/${snowflake(userId, \"User ID\")}`,\n /** Returns a channel. @param channelId Channel identifier. @returns Channel route. */ channel:\n (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}`,\n /** Returns messages in a channel. @param channelId Channel identifier. @returns Message collection route. */ channelMessages:\n (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages`,\n /** Returns a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Message route. */ message:\n (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. */ crosspostMessage:\n (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. */ messageReactions:\n (channelId: string, messageId: string, emoji: string) =>\n `${Routes.message(channelId, messageId)}/reactions/${encodeURIComponent(emoji)}`,\n /** Removes all reactions from a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Reaction collection route. */ messageReactionsAll:\n (channelId: string, messageId: string) =>\n `${Routes.message(channelId, messageId)}/reactions`,\n /** Returns pinned messages. @param channelId Channel identifier. @returns Pins route. */ channelPins:\n (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/pins`,\n /** Pins a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Pin route. */ channelPin:\n (channelId: string, messageId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/pins/${snowflake(messageId, \"Message ID\")}`,\n /** Creates a thread from a message. @param channelId Channel identifier. @param messageId Message identifier. @returns Message-thread route. */ messageThread:\n (channelId: string, messageId: string) =>\n `${Routes.message(channelId, messageId)}/threads`,\n /** Bulk deletes messages. @param channelId Channel identifier. @returns Bulk-delete route. */ channelBulkDelete:\n (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/messages/bulk-delete`,\n /** Returns channel webhooks. @param channelId Channel identifier. @returns Webhook collection route. */ channelWebhooks:\n (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/webhooks`,\n /** Returns channel invites. @param channelId Channel identifier. @returns Invite collection route. */ channelInvites:\n (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. */ webhook:\n (webhookId: string, token?: string) =>\n `/webhooks/${snowflake(webhookId, \"Webhook ID\")}${token ? `/${encodeURIComponent(token)}` : \"\"}`,\n /** Returns a webhook message. */ webhookMessage: (\n webhookId: string,\n token: string,\n messageId: string,\n ) =>\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. */ applicationCommands:\n (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. */ applicationCommand:\n (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. */ guildApplicationCommands:\n (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. */ guildApplicationCommand:\n (applicationId: string, guildId: string, commandId: string) =>\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. */ interactionCallback:\n (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. */ interactionOriginalResponse:\n (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. */ channelPublicArchivedThreads:\n (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. */ channelPrivateArchivedThreads:\n (channelId: string) =>\n `/channels/${snowflake(channelId, \"Channel ID\")}/threads/archived/private`,\n /** Returns a thread's members. @param threadId Thread identifier. @returns Thread-member route. */ threadMembers:\n (threadId: string) =>\n `/channels/${snowflake(threadId, \"Thread ID\")}/thread-members`,\n /** Returns guild scheduled events. @param guildId Guild identifier. @returns Scheduled-event collection route. */ guildScheduledEvents:\n (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. */ guildScheduledEvent:\n (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. */ guildScheduledEventUsers:\n (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. */ channelPermission:\n (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. */ guildAutoModerationRules:\n (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. */ guildAutoModerationRule:\n (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. */ voiceRegions:\n () => \"/voice/regions\",\n /** Returns the generic Gateway endpoint. @returns Gateway route. */ gateway:\n () => \"/gateway\",\n /** Returns the Gateway endpoint and recommended shard count for bots. @returns Gateway bot route. */ gatewayBot:\n () => \"/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 // ─── 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. */\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
|
+
"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<any>;\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",
|
|
14
|
+
"import { REST } from \"./index.js\";\nimport { Routes } from \"./routes.js\";\nimport type { APIEmbed, APIMessage } from \"@lunibee/types\";\n\nexport interface WebhookClientOptions {\n id?: string;\n token?: string;\n url?: string;\n}\n\nexport interface WebhookMessageOptions {\n content?: string;\n username?: string;\n avatar_url?: string;\n tts?: boolean;\n embeds?: (APIEmbed | { toJSON(): APIEmbed })[];\n components?: any[];\n files?: any[];\n flags?: number;\n thread_id?: string;\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 const match = options.url.match(\n /discord(?:app)?.com\\/api\\/webhooks\\/(\\d+)\\/([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 /** Sends a message to the webhook. */\n public async send(\n options: string | WebhookMessageOptions,\n ): Promise<APIMessage> {\n const payload =\n typeof options === \"string\" ? { content: options } : options;\n const { thread_id, ...body } = payload;\n const query = thread_id\n ? `?thread_id=${thread_id}&wait=true`\n : `?wait=true`;\n const formattedEmbeds: APIEmbed[] | undefined = body.embeds?.map((e) =>\n \"toJSON\" in e ? e.toJSON() : e,\n );\n return this.#rest.post<APIMessage>(\n `${Routes.webhook(this.id, this.token)}${query}`,\n {\n ...body,\n embeds: formattedEmbeds,\n },\n );\n }\n\n /** Edits a previously sent webhook message. */\n public async editMessage(\n messageId: string,\n options: string | { content?: string; embeds?: any[] },\n ): Promise<any> {\n const payload =\n typeof options === \"string\" ? { content: options } : options;\n return this.#rest.patch(\n Routes.webhookMessage(this.id, this.token, messageId),\n payload,\n );\n }\n\n /** Deletes a previously sent webhook message. */\n public async deleteMessage(messageId: string): Promise<void> {\n await this.#rest.delete(\n Routes.webhookMessage(this.id, this.token, messageId),\n );\n }\n}\n",
|
|
15
|
+
"/** 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}, ${packageJson.version})`;\n\nimport {\n MemoryRateLimitStore,\n type RateLimitStore,\n type BucketState,\n} 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/** Internal state shared by requests mapped to one Discord rate-limit bucket. */\ntype Bucket = { remaining: number; resetAt: number };\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. */ signal?: AbortSignal;\n /** Query-string appended to the path (`URLSearchParams`, a record, or a raw string). */ query?: RESTQuery;\n /** Extra request headers merged in (library headers such as `Authorization` win). */ headers?: Record<\n string,\n string\n >;\n /** Audit-log reason, sent as the `X-Audit-Log-Reason` header. */ reason?: string;\n /** Set `false` to omit the `Authorization` header for this request. Defaults to `true`. */ 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). */ body?: unknown;\n /** File attachments; presence upgrades the request to `multipart/form-data`. */ 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. */ 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. */ shouldRetry(\n method: string,\n status: number,\n ): 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. */ getDelay(\n attempt: number,\n retryAfter?: number,\n ): 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 ?? false) &&\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 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 if (options.auth !== false && this.#token)\n headers[\"Authorization\"] = `Bot ${this.#token}`;\n headers[\"User-Agent\"] = USER_AGENT;\n if (options.reason !== undefined)\n headers[\"X-Audit-Log-Reason\"] = encodeURIComponent(options.reason);\n if (!isFormData) headers[\"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. */ public get<\n T,\n >(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. */ public delete<\n T,\n >(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 {\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"
|
|
10
16
|
],
|
|
11
|
-
"mappings": "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",
|
|
12
|
-
"debugId": "
|
|
17
|
+
"mappings": "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",
|
|
18
|
+
"debugId": "3E6A1976705386FE64756E2164756E21",
|
|
13
19
|
"names": []
|
|
14
20
|
}
|