lunibee 0.1.6 → 0.1.7
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/README.md +3 -3
- package/dist/builders/commands.d.ts +37 -0
- package/dist/builders/components.d.ts +19 -12
- package/dist/builders/embed.d.ts +3 -1
- package/dist/builders/index.d.ts +1 -1
- package/dist/builders/index.js +146 -19
- package/dist/builders/index.js.map +8 -7
- package/dist/collection/index.d.ts +30 -0
- package/dist/collection/index.js +78 -5
- package/dist/collection/index.js.map +4 -4
- package/dist/core/events.d.ts +2 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +754 -175
- package/dist/core/index.js.map +24 -22
- package/dist/core/permissions.d.ts +112 -53
- package/dist/formatters/index.d.ts +43 -3
- package/dist/formatters/index.js +64 -1
- package/dist/formatters/index.js.map +3 -3
- package/dist/handlers/index.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1762 -291
- package/dist/index.js.map +43 -38
- package/dist/managers/emoji.d.ts +38 -0
- package/dist/managers/guild.d.ts +112 -1
- package/dist/managers/index.d.ts +31 -1
- package/dist/managers/index.js +803 -77
- package/dist/managers/index.js.map +21 -20
- package/dist/rest/index.d.ts +101 -11
- package/dist/rest/index.js +301 -45
- package/dist/rest/index.js.map +8 -6
- package/dist/rest/redis.d.ts +37 -0
- package/dist/rest/routes.d.ts +22 -0
- package/dist/rest/store.d.ts +38 -0
- package/dist/rest/webhook.d.ts +2 -2
- package/dist/sharding/cluster.d.ts +61 -0
- package/dist/sharding/index.d.ts +8 -0
- package/dist/sharding/index.js +286 -18
- package/dist/sharding/index.js.map +8 -7
- package/dist/structures/audit-log.d.ts +26 -0
- package/dist/structures/base.d.ts +32 -0
- package/dist/structures/index.d.ts +29 -1
- package/dist/structures/index.js +1022 -86
- package/dist/structures/index.js.map +14 -10
- package/dist/structures/resources.d.ts +192 -4
- package/dist/types/index.d.ts +76 -0
- package/dist/types/index.js.map +2 -2
- package/dist/utils/index.js.map +2 -2
- package/dist/voice/index.d.ts +88 -0
- package/dist/voice/index.js +204 -5
- package/dist/voice/index.js.map +3 -3
- package/dist/ws/index.d.ts +40 -1
- package/dist/ws/index.js +102 -18
- package/dist/ws/index.js.map +4 -4
- package/package.json +16 -16
- package/dist/structures/permissions.d.ts +0 -73
package/dist/rest/index.js.map
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../packages/rest/src/routes.ts", "../../packages/rest/src/webhook.ts", "../../packages/rest/src/index.ts"],
|
|
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"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/** 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) => `/guilds/${snowflake(guildId, \"Guild ID\")}`,\r\n /** Returns a guild preview. @param guildId Guild identifier. */ guildPreview:\r\n (guildId: string) => `/guilds/${snowflake(guildId, \"Guild ID\")}/preview`,\r\n /** Returns active threads for a guild. @param guildId Guild identifier. */ guildActiveThreads:\r\n (guildId: string) => `/guilds/${snowflake(guildId, \"Guild ID\")}/threads/active`,\r\n /** Returns webhooks for a guild. @param guildId Guild identifier. */ guildWebhooks:\r\n (guildId: string) => `/guilds/${snowflake(guildId, \"Guild ID\")}/webhooks`,\r\n /** Returns invites for a guild. @param guildId Guild identifier. */ guildInvites:\r\n (guildId: string) => `/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) => `/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) => `/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} as const;\r\n\r\n/** Validates a Discord snowflake route parameter. @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",
|
|
6
|
-
"import { REST } from \"./index.js\";\nimport { Routes } from \"./routes.js\";\nimport type { APIEmbed } 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(options: string | WebhookMessageOptions): Promise<any> {\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 = body.embeds?.map((e) =>\n typeof e === \"object\" && e !== null && \"toJSON\" in e\n ? (e as any).toJSON()\n : e,\n );\n return this.#rest.post(`${Routes.webhook(this.id, this.token)}${query}`, {\n ...body,\n embeds: formattedEmbeds,\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",
|
|
7
|
-
"/** 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\n/** Internal state shared by requests mapped to one Discord rate-limit bucket. */\r\ntype Bucket = { remaining: number; resetAt: number; queue: Promise<void> };\r\n/** Options controlling an individual REST request. */\r\nexport interface RESTRequestOptions {\r\n /** Abort signal used to cancel the request and any queued wait. */ signal?: AbortSignal;\r\n}\r\n\r\nexport interface RESTFileAttachment {\r\n name: string;\r\n data: Blob | Uint8Array | ArrayBuffer;\r\n contentType?: string;\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 #globalResetAt = 0;\r\n readonly #buckets = new Map<string, Bucket>();\r\n readonly #routeBuckets = new Map<string, string>();\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 } = {},\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 = (options.baseURL ?? \"https://discord.com/api/v10\").replace(\r\n /\\/$/,\r\n \"\",\r\n );\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()) 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 Cancellation options.\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: RESTRequestOptions = {},\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 const route = `${normalizedMethod}:${this.#normalizeRoute(path)}`;\r\n let bucketKey = this.#routeBuckets.get(route) ?? route;\r\n let bucket = this.#buckets.get(bucketKey) ?? {\r\n remaining: 1,\r\n resetAt: 0,\r\n queue: Promise.resolve(),\r\n };\r\n this.#buckets.set(bucketKey, bucket);\r\n const previous = bucket.queue;\r\n let release!: () => void;\r\n bucket.queue = new Promise((resolve) => {\r\n release = resolve;\r\n });\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(bucket, 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, { once: true });\r\n const timer = setTimeout(\r\n () =>\r\n controller.abort(\r\n new DOMException(\"REST request timeout\", \"TimeoutError\"),\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 const headers: Record<string, string> = {\r\n ...(this.#token ? { Authorization: `Bot ${this.#token}` } : {}),\r\n \"User-Agent\": USER_AGENT,\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 const response = await fetch(`${this.#baseURL}${path}`, {\r\n method: normalizedMethod,\r\n headers,\r\n body: fetchBody,\r\n signal: controller.signal,\r\n });\r\n bucket = this.#update(response, bucket, route, bucketKey);\r\n bucketKey = this.#routeBuckets.get(route) ?? bucketKey;\r\n const payload = await this.#readPayload(response);\r\n if (response.ok)\r\n return (response.status === 204 ? undefined : payload) 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 this.#globalResetAt = Date.now() + retryAfter * 1000;\r\n if (\r\n attempt < this.#retryPolicy.maxRetries &&\r\n this.#retryPolicy.shouldRetry(normalizedMethod, response.status)\r\n ) {\r\n await this.#sleep(\r\n this.#retryPolicy.getDelay(attempt, retryAfter),\r\n options.signal,\r\n path,\r\n );\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\" || 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 }\r\n }\r\n /** Sends a GET request. @param path API path. @param options Cancellation options. @returns Decoded response. @throws {RESTError} When the request fails. */ public get<\r\n T,\r\n >(path: string, options?: RESTRequestOptions): Promise<T> {\r\n return this.request<T>(\"GET\", path, undefined, options);\r\n }\r\n /** Sends a POST request. @param path API path. @param body Optional JSON body. @param options Cancellation options. @returns Decoded response. @throws {RESTError} When the request fails. */ public post<\r\n T,\r\n >(path: string, body?: unknown, options?: RESTRequestOptions): Promise<T> {\r\n return this.request<T>(\"POST\", path, body, options);\r\n }\r\n /** Sends a PATCH request. @param path API path. @param body Optional JSON body. @param options Cancellation options. @returns Decoded response. @throws {RESTError} When the request fails. */ public patch<\r\n T,\r\n >(path: string, body?: unknown, options?: RESTRequestOptions): Promise<T> {\r\n return this.request<T>(\"PATCH\", path, body, options);\r\n }\r\n /** Sends a PUT request. @param path API path. @param body Optional JSON body. @param options Cancellation options. @returns Decoded response. @throws {RESTError} When the request fails. */ public put<\r\n T,\r\n >(path: string, body?: unknown, options?: RESTRequestOptions): Promise<T> {\r\n return this.request<T>(\"PUT\", path, body, options);\r\n }\r\n /** Sends a DELETE request. @param path API path. @param options Cancellation options. @returns Decoded response. @throws {RESTError} When the request fails. */ public delete<\r\n T,\r\n >(path: string, options?: RESTRequestOptions): Promise<T> {\r\n return this.request<T>(\"DELETE\", path, undefined, 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>(\"POST\", path, this.#fileForm(payload, files), options);\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>(\"PATCH\", path, this.#fileForm(payload, files), options);\r\n }\r\n /** Waits for a route bucket while respecting cancellation. @param bucket Bucket state. @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 bucket: Bucket,\r\n signal: AbortSignal | undefined,\r\n path: string,\r\n ): Promise<void> {\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 delay = this.#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: 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\" ? data.retry_after : 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. @returns Bucket state used for subsequent attempts. */\r\n #update(\r\n response: Response,\r\n bucket: Bucket,\r\n route: string,\r\n bucketKey: string,\r\n ): Bucket {\r\n const serverBucket = response.headers.get(\"X-RateLimit-Bucket\");\r\n if (serverBucket) {\r\n this.#routeBuckets.set(route, serverBucket);\r\n if (serverBucket !== bucketKey) {\r\n const shared = this.#buckets.get(serverBucket) ?? bucket;\r\n this.#buckets.set(serverBucket, shared);\r\n bucket = shared;\r\n }\r\n }\r\n const remaining = Number(response.headers.get(\"X-RateLimit-Remaining\"));\r\n const resetAfter = Number(response.headers.get(\"X-RateLimit-Reset-After\"));\r\n const reset = Number(response.headers.get(\"X-RateLimit-Reset\"));\r\n if (Number.isFinite(remaining)) 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 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 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 this.#globalResetAt = Math.max(\r\n this.#globalResetAt,\r\n Date.now() + retryAfter * 1000,\r\n );\r\n }\r\n return bucket;\r\n }\r\n}\r\n\r\nexport { Routes } from \"./routes.js\";\r\n\r\nexport {\r\n WebhookClient,\r\n type WebhookClientOptions,\r\n type WebhookMessageOptions,\r\n} from \"./webhook.js\";\r\n"
|
|
5
|
+
"export interface BucketState {\r\n remaining: number;\r\n resetAt: number;\r\n}\r\n\r\n/** Interface for distributed or local rate limit synchronization. */\r\nexport interface RateLimitStore {\r\n /** Gets the server bucket hash for a normalized route. */\r\n getBucketHash(route: string): Promise<string | undefined> | string | undefined;\r\n /** Associates a route with a server bucket hash. */\r\n setBucketHash(route: string, hash: string): Promise<void> | void;\r\n\r\n /** Gets the current state for a bucket key. */\r\n getBucket(key: string): Promise<BucketState | undefined> | BucketState | undefined;\r\n /** Updates the state for a bucket key. */\r\n updateBucket(key: string, state: BucketState): Promise<void> | void;\r\n\r\n /** Gets the global reset timestamp. */\r\n getGlobalReset(): Promise<number> | number;\r\n /** Sets the global reset timestamp. */\r\n setGlobalReset(resetAt: number): Promise<void> | void;\r\n\r\n /**\r\n * Evicts expired bucket entries. Optional: stores with native key\r\n * expiry (e.g. Redis) can omit it. `maxAge` (ms) additionally drops\r\n * buckets whose reset is older than that window; defaults to 0 (only\r\n * already-reset buckets are removed).\r\n */\r\n prune?(maxAge?: number): Promise<void> | void;\r\n}\r\n\r\n/** Local memory implementation of RateLimitStore. */\r\nexport class MemoryRateLimitStore implements RateLimitStore {\r\n readonly #buckets = new Map<string, BucketState>();\r\n readonly #routeBuckets = new Map<string, string>();\r\n #globalResetAt = 0;\r\n /** Minimum interval between automatic prunes, in ms. */\r\n static readonly #PRUNE_INTERVAL = 60_000;\r\n #lastPruneAt = 0;\r\n\r\n public getBucketHash(route: string): string | undefined {\r\n return this.#routeBuckets.get(route);\r\n }\r\n public setBucketHash(route: string, hash: string): void {\r\n this.#routeBuckets.set(route, hash);\r\n }\r\n public getBucket(key: string): BucketState | undefined {\r\n return this.#buckets.get(key);\r\n }\r\n public updateBucket(key: string, state: BucketState): void {\r\n this.#buckets.set(key, state);\r\n // Amortised eviction: sweep expired buckets at most once per interval\r\n // so the map cannot grow unbounded across long-lived processes.\r\n const now = Date.now();\r\n if (now - this.#lastPruneAt >= MemoryRateLimitStore.#PRUNE_INTERVAL) {\r\n this.#lastPruneAt = now;\r\n this.prune();\r\n }\r\n }\r\n /** Removes buckets whose reset is in the past (or older than `maxAge` ms). */\r\n public prune(maxAge = 0): void {\r\n const cutoff = Date.now() - Math.max(0, maxAge);\r\n for (const [key, state] of this.#buckets) {\r\n if (state.resetAt <= cutoff) this.#buckets.delete(key);\r\n }\r\n }\r\n public getGlobalReset(): number {\r\n return this.#globalResetAt;\r\n }\r\n public setGlobalReset(resetAt: number): void {\r\n this.#globalResetAt = resetAt;\r\n }\r\n}\r\n",
|
|
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
|
+
"import type { RateLimitStore, BucketState } from \"./store.js\";\r\n\r\n/**\r\n * A Redis client interface that matches the subset of ioredis\r\n * methods needed for the RedisRateLimitStore.\r\n */\r\nexport interface MinimalRedisClient {\r\n get(key: string): Promise<string | null>;\r\n mget(keys: string[]): Promise<Array<string | null>>;\r\n set(\r\n key: string,\r\n value: string | number,\r\n mode?: string,\r\n duration?: number,\r\n ): Promise<any>;\r\n del(...keys: string[]): Promise<number>;\r\n}\r\n\r\nexport interface RedisRateLimitStoreOptions {\r\n /** The Redis client instance (e.g. ioredis). */\r\n client: MinimalRedisClient;\r\n /** Prefix to prepend to all Redis keys. Defaults to 'lunibee:rest:' */\r\n prefix?: string;\r\n /** Called whenever a Redis operation fails before local fallback behavior is used. */\r\n onError?: (operation: string, error: unknown) => void;\r\n}\r\n\r\n/**\r\n * Redis-backed implementation of RateLimitStore for distributed rate limit synchronization.\r\n * Allows multiple shards or workers to share Discord API rate limits seamlessly.\r\n */\r\nexport class RedisRateLimitStore implements RateLimitStore {\r\n readonly #client: MinimalRedisClient;\r\n readonly #prefix: string;\r\n readonly #onErrorCallback?: RedisRateLimitStoreOptions[\"onError\"];\r\n #lastError?: unknown;\r\n\r\n public constructor(options: RedisRateLimitStoreOptions) {\r\n this.#client = options.client;\r\n this.#prefix = options.prefix ?? \"lunibee:rest:\";\r\n this.#onErrorCallback = options.onError;\r\n }\r\n\r\n /** The most recent Redis error, if any. */\r\n public get lastError(): unknown {\r\n return this.#lastError;\r\n }\r\n\r\n /** Whether no Redis operation has failed since construction or the last successful health check. */\r\n public isHealthy(): boolean {\r\n return this.#lastError === undefined;\r\n }\r\n\r\n /** Logs and surfaces a Redis failure while retaining safe local fallback behavior. */\r\n #onError(operation: string, error: unknown): void {\r\n this.#lastError = error;\r\n console.warn(\r\n `[lunibee/rest] Redis rate-limit store ${operation} failed; ` +\r\n `falling back to local limiting for this call.`,\r\n error,\r\n );\r\n try {\r\n this.#onErrorCallback?.(operation, error);\r\n } catch {\r\n // User-provided diagnostics must never break REST operations.\r\n }\r\n }\r\n\r\n /** Marks the store healthy after a successful Redis operation. */\r\n #markHealthy(): void {\r\n this.#lastError = undefined;\r\n }\r\n\r\n public async getBucketHash(route: string): Promise<string | undefined> {\r\n try {\r\n const hash = await this.#client.get(`${this.#prefix}route:${route}`);\r\n this.#markHealthy();\r\n return hash ?? undefined;\r\n } catch (error) {\r\n this.#onError(\"getBucketHash\", error);\r\n return undefined;\r\n }\r\n }\r\n\r\n public async setBucketHash(route: string, hash: string): Promise<void> {\r\n try {\r\n await this.#client.set(\r\n `${this.#prefix}route:${route}`,\r\n hash,\r\n \"EX\",\r\n 604800,\r\n );\r\n this.#markHealthy();\r\n } catch (error) {\r\n this.#onError(\"setBucketHash\", error);\r\n }\r\n }\r\n\r\n public async getBucket(key: string): Promise<BucketState | undefined> {\r\n try {\r\n const data = await this.#client.get(`${this.#prefix}bucket:${key}`);\r\n this.#markHealthy();\r\n if (!data) return undefined;\r\n return JSON.parse(data) as BucketState;\r\n } catch (error) {\r\n this.#onError(\"getBucket\", error);\r\n return undefined;\r\n }\r\n }\r\n\r\n public async updateBucket(key: string, state: BucketState): Promise<void> {\r\n const bucketKey = `${this.#prefix}bucket:${key}`;\r\n // Expired buckets hold no useful limit info; drop them instead of\r\n // persisting stale state under a clamped 1s TTL.\r\n const ttl = Math.ceil((state.resetAt - Date.now()) / 1000);\r\n try {\r\n if (ttl <= 0) {\r\n await this.#client.del(bucketKey);\r\n this.#markHealthy();\r\n return;\r\n }\r\n await this.#client.set(\r\n bucketKey,\r\n JSON.stringify(state),\r\n \"EX\",\r\n ttl,\r\n );\r\n this.#markHealthy();\r\n } catch (error) {\r\n this.#onError(\"updateBucket\", error);\r\n }\r\n }\r\n\r\n public async getGlobalReset(): Promise<number> {\r\n try {\r\n const resetAt = await this.#client.get(`${this.#prefix}global`);\r\n this.#markHealthy();\r\n return resetAt ? Number(resetAt) : 0;\r\n } catch (error) {\r\n this.#onError(\"getGlobalReset\", error);\r\n return 0;\r\n }\r\n }\r\n\r\n public async setGlobalReset(resetAt: number): Promise<void> {\r\n const globalKey = `${this.#prefix}global`;\r\n const ttl = Math.ceil((resetAt - Date.now()) / 1000);\r\n try {\r\n if (ttl <= 0) {\r\n await this.#client.del(globalKey);\r\n this.#markHealthy();\r\n return;\r\n }\r\n await this.#client.set(\r\n globalKey,\r\n String(resetAt),\r\n \"EX\",\r\n ttl,\r\n );\r\n this.#markHealthy();\r\n } catch (error) {\r\n this.#onError(\"setGlobalReset\", error);\r\n }\r\n }\r\n}\r\n",
|
|
8
|
+
"import { REST } from \"./index.js\";\r\nimport { Routes } from \"./routes.js\";\r\nimport type { APIEmbed, APIMessage } from \"@lunibee/types\";\r\n\r\nexport interface WebhookClientOptions {\r\n id?: string;\r\n token?: string;\r\n url?: string;\r\n}\r\n\r\nexport interface WebhookMessageOptions {\r\n content?: string;\r\n username?: string;\r\n avatar_url?: string;\r\n tts?: boolean;\r\n embeds?: (APIEmbed | { toJSON(): APIEmbed })[];\r\n components?: any[];\r\n files?: any[];\r\n flags?: number;\r\n thread_id?: string;\r\n}\r\n\r\n/** Standalone client for sending Discord webhooks. */\r\nexport class WebhookClient {\r\n /** Webhook ID. */\r\n public readonly id: string;\r\n /** Webhook security token. */\r\n public readonly token: string;\r\n readonly #rest: REST;\r\n\r\n public constructor(\r\n options: WebhookClientOptions,\r\n restOptions?: { timeout?: number; retries?: number },\r\n ) {\r\n if (options.url) {\r\n const match = options.url.match(\r\n /discord(?:app)?.com\\/api\\/webhooks\\/(\\d+)\\/([A-Za-z0-9_-]+)/,\r\n );\r\n if (!match) throw new Error(\"Invalid Discord webhook URL format.\");\r\n this.id = match[1]!;\r\n this.token = match[2]!;\r\n } else if (options.id && options.token) {\r\n this.id = options.id;\r\n this.token = options.token;\r\n } else {\r\n throw new Error(\r\n \"WebhookClient requires either a webhook 'url' or both 'id' and 'token'.\",\r\n );\r\n }\r\n this.#rest = new REST(restOptions);\r\n }\r\n\r\n /** Sends a message to the webhook. */\r\n public async send(\r\n options: string | WebhookMessageOptions,\r\n ): Promise<APIMessage> {\r\n const payload =\r\n typeof options === \"string\" ? { content: options } : options;\r\n const { thread_id, ...body } = payload;\r\n const query = thread_id\r\n ? `?thread_id=${thread_id}&wait=true`\r\n : `?wait=true`;\r\n const formattedEmbeds: APIEmbed[] | undefined = body.embeds?.map((e) =>\r\n \"toJSON\" in e ? e.toJSON() : e,\r\n );\r\n return this.#rest.post<APIMessage>(\r\n `${Routes.webhook(this.id, this.token)}${query}`,\r\n {\r\n ...body,\r\n embeds: formattedEmbeds,\r\n },\r\n );\r\n }\r\n\r\n /** Edits a previously sent webhook message. */\r\n public async editMessage(\r\n messageId: string,\r\n options: string | { content?: string; embeds?: any[] },\r\n ): Promise<any> {\r\n const payload =\r\n typeof options === \"string\" ? { content: options } : options;\r\n return this.#rest.patch(\r\n Routes.webhookMessage(this.id, this.token, messageId),\r\n payload,\r\n );\r\n }\r\n\r\n /** Deletes a previously sent webhook message. */\r\n public async deleteMessage(messageId: string): Promise<void> {\r\n await this.#rest.delete(\r\n Routes.webhookMessage(this.id, this.token, messageId),\r\n );\r\n }\r\n}\r\n",
|
|
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"
|
|
8
10
|
],
|
|
9
|
-
"mappings": "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",
|
|
10
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "7273A79583E1900164756E2164756E21",
|
|
11
13
|
"names": []
|
|
12
14
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RateLimitStore, BucketState } from "./store.js";
|
|
2
|
+
/**
|
|
3
|
+
* A Redis client interface that matches the subset of ioredis
|
|
4
|
+
* methods needed for the RedisRateLimitStore.
|
|
5
|
+
*/
|
|
6
|
+
export interface MinimalRedisClient {
|
|
7
|
+
get(key: string): Promise<string | null>;
|
|
8
|
+
mget(keys: string[]): Promise<Array<string | null>>;
|
|
9
|
+
set(key: string, value: string | number, mode?: string, duration?: number): Promise<any>;
|
|
10
|
+
del(...keys: string[]): Promise<number>;
|
|
11
|
+
}
|
|
12
|
+
export interface RedisRateLimitStoreOptions {
|
|
13
|
+
/** The Redis client instance (e.g. ioredis). */
|
|
14
|
+
client: MinimalRedisClient;
|
|
15
|
+
/** Prefix to prepend to all Redis keys. Defaults to 'lunibee:rest:' */
|
|
16
|
+
prefix?: string;
|
|
17
|
+
/** Called whenever a Redis operation fails before local fallback behavior is used. */
|
|
18
|
+
onError?: (operation: string, error: unknown) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Redis-backed implementation of RateLimitStore for distributed rate limit synchronization.
|
|
22
|
+
* Allows multiple shards or workers to share Discord API rate limits seamlessly.
|
|
23
|
+
*/
|
|
24
|
+
export declare class RedisRateLimitStore implements RateLimitStore {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(options: RedisRateLimitStoreOptions);
|
|
27
|
+
/** The most recent Redis error, if any. */
|
|
28
|
+
get lastError(): unknown;
|
|
29
|
+
/** Whether no Redis operation has failed since construction or the last successful health check. */
|
|
30
|
+
isHealthy(): boolean;
|
|
31
|
+
getBucketHash(route: string): Promise<string | undefined>;
|
|
32
|
+
setBucketHash(route: string, hash: string): Promise<void>;
|
|
33
|
+
getBucket(key: string): Promise<BucketState | undefined>;
|
|
34
|
+
updateBucket(key: string, state: BucketState): Promise<void>;
|
|
35
|
+
getGlobalReset(): Promise<number>;
|
|
36
|
+
setGlobalReset(resetAt: number): Promise<void>;
|
|
37
|
+
}
|
package/dist/rest/routes.d.ts
CHANGED
|
@@ -44,4 +44,26 @@ export declare const Routes: {
|
|
|
44
44
|
/** Returns current voice regions. @returns Voice-region route. */ readonly voiceRegions: () => string;
|
|
45
45
|
/** Returns the generic Gateway endpoint. @returns Gateway route. */ readonly gateway: () => string;
|
|
46
46
|
/** Returns the Gateway endpoint and recommended shard count for bots. @returns Gateway bot route. */ readonly gatewayBot: () => string;
|
|
47
|
+
/** Returns audit log entries for a guild. @param guildId Guild identifier. @returns Audit log route. */
|
|
48
|
+
readonly guildAuditLog: (guildId: string) => string;
|
|
49
|
+
/** Returns members in a guild (paginated). @param guildId Guild identifier. @returns Members collection route. */
|
|
50
|
+
readonly guildMembers: (guildId: string) => string;
|
|
51
|
+
/** Returns the welcome screen for a guild. @param guildId Guild identifier. */
|
|
52
|
+
readonly guildWelcomeScreen: (guildId: string) => string;
|
|
53
|
+
/** Returns the onboarding configuration for a guild. @param guildId Guild identifier. */
|
|
54
|
+
readonly guildOnboarding: (guildId: string) => string;
|
|
55
|
+
/** Returns or executes a prune operation for inactive members. @param guildId Guild identifier. @returns Prune route. */
|
|
56
|
+
readonly guildPrune: (guildId: string) => string;
|
|
57
|
+
/** Returns the stage instances collection. @returns Stage instance root route. */
|
|
58
|
+
readonly stageInstances: () => string;
|
|
59
|
+
/** Returns a stage instance by its channel. @param channelId Stage channel identifier. @returns Stage instance route. */
|
|
60
|
+
readonly stageInstanceByChannel: (channelId: string) => string;
|
|
61
|
+
/** Returns an invite by code. @param code Invite code. @returns Invite route. */
|
|
62
|
+
readonly invite: (code: string) => string;
|
|
63
|
+
/** Returns the guild vanity invite metadata. @param guildId Guild identifier. @returns Vanity URL route. */
|
|
64
|
+
readonly guildVanity: (guildId: string) => string;
|
|
65
|
+
/** Returns guild emojis, or a specific emoji. @param guildId Guild identifier. @param emojiId Optional emoji identifier. */
|
|
66
|
+
readonly guildEmoji: (guildId: string, emojiId?: string) => string;
|
|
67
|
+
/** Returns application emojis, or a specific emoji. @param applicationId Application identifier. @param emojiId Optional emoji identifier. */
|
|
68
|
+
readonly applicationEmoji: (applicationId: string, emojiId?: string) => string;
|
|
47
69
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface BucketState {
|
|
2
|
+
remaining: number;
|
|
3
|
+
resetAt: number;
|
|
4
|
+
}
|
|
5
|
+
/** Interface for distributed or local rate limit synchronization. */
|
|
6
|
+
export interface RateLimitStore {
|
|
7
|
+
/** Gets the server bucket hash for a normalized route. */
|
|
8
|
+
getBucketHash(route: string): Promise<string | undefined> | string | undefined;
|
|
9
|
+
/** Associates a route with a server bucket hash. */
|
|
10
|
+
setBucketHash(route: string, hash: string): Promise<void> | void;
|
|
11
|
+
/** Gets the current state for a bucket key. */
|
|
12
|
+
getBucket(key: string): Promise<BucketState | undefined> | BucketState | undefined;
|
|
13
|
+
/** Updates the state for a bucket key. */
|
|
14
|
+
updateBucket(key: string, state: BucketState): Promise<void> | void;
|
|
15
|
+
/** Gets the global reset timestamp. */
|
|
16
|
+
getGlobalReset(): Promise<number> | number;
|
|
17
|
+
/** Sets the global reset timestamp. */
|
|
18
|
+
setGlobalReset(resetAt: number): Promise<void> | void;
|
|
19
|
+
/**
|
|
20
|
+
* Evicts expired bucket entries. Optional: stores with native key
|
|
21
|
+
* expiry (e.g. Redis) can omit it. `maxAge` (ms) additionally drops
|
|
22
|
+
* buckets whose reset is older than that window; defaults to 0 (only
|
|
23
|
+
* already-reset buckets are removed).
|
|
24
|
+
*/
|
|
25
|
+
prune?(maxAge?: number): Promise<void> | void;
|
|
26
|
+
}
|
|
27
|
+
/** Local memory implementation of RateLimitStore. */
|
|
28
|
+
export declare class MemoryRateLimitStore implements RateLimitStore {
|
|
29
|
+
#private;
|
|
30
|
+
getBucketHash(route: string): string | undefined;
|
|
31
|
+
setBucketHash(route: string, hash: string): void;
|
|
32
|
+
getBucket(key: string): BucketState | undefined;
|
|
33
|
+
updateBucket(key: string, state: BucketState): void;
|
|
34
|
+
/** Removes buckets whose reset is in the past (or older than `maxAge` ms). */
|
|
35
|
+
prune(maxAge?: number): void;
|
|
36
|
+
getGlobalReset(): number;
|
|
37
|
+
setGlobalReset(resetAt: number): void;
|
|
38
|
+
}
|
package/dist/rest/webhook.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { APIEmbed } from "@lunibee/types";
|
|
1
|
+
import type { APIEmbed, APIMessage } from "@lunibee/types";
|
|
2
2
|
export interface WebhookClientOptions {
|
|
3
3
|
id?: string;
|
|
4
4
|
token?: string;
|
|
@@ -29,7 +29,7 @@ export declare class WebhookClient {
|
|
|
29
29
|
retries?: number;
|
|
30
30
|
});
|
|
31
31
|
/** Sends a message to the webhook. */
|
|
32
|
-
send(options: string | WebhookMessageOptions): Promise<
|
|
32
|
+
send(options: string | WebhookMessageOptions): Promise<APIMessage>;
|
|
33
33
|
/** Edits a previously sent webhook message. */
|
|
34
34
|
editMessage(messageId: string, options: string | {
|
|
35
35
|
content?: string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
/** Configuration for managing multiple shard clusters. */
|
|
3
|
+
export interface ClusterManagerOptions {
|
|
4
|
+
/** Discord bot token. */
|
|
5
|
+
token: string;
|
|
6
|
+
/** Path to the worker script to execute for each cluster. */
|
|
7
|
+
script: string;
|
|
8
|
+
/** Number of shards. Use "auto" to request Discord's recommended count. */
|
|
9
|
+
shardCount?: number | "auto";
|
|
10
|
+
/** Number of clusters to spawn. Defaults to the number of logical CPUs. */
|
|
11
|
+
clusterCount?: number;
|
|
12
|
+
/** Interval in milliseconds to automatically check for recommended shard count and re-scale if needed. */
|
|
13
|
+
autoScaleInterval?: number;
|
|
14
|
+
/** Optional handler invoked when a background auto-scale check fails. Receives the thrown error. */
|
|
15
|
+
onAutoScaleError?: (error: Error) => void;
|
|
16
|
+
/** Grace period in milliseconds to await a cluster's clean exit after SIGTERM before force-killing. Defaults to 5000. */
|
|
17
|
+
shutdownTimeout?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Information about a running cluster. */
|
|
20
|
+
export interface ClusterInfo {
|
|
21
|
+
/** Cluster identifier. */
|
|
22
|
+
id: number;
|
|
23
|
+
/** The child process running the cluster. */
|
|
24
|
+
process: ChildProcess;
|
|
25
|
+
/** The shard IDs managed by this cluster. */
|
|
26
|
+
shards: number[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Manages multiple clusters of shards, distributing shards across child processes.
|
|
30
|
+
*
|
|
31
|
+
* **Runtime requirement:** clustering relies on Node.js `child_process.fork()`
|
|
32
|
+
* and IPC. `fork()` is not fully supported under Bun, so run the cluster
|
|
33
|
+
* manager under the Node.js runtime; `spawn()` will fail on a runtime whose
|
|
34
|
+
* `fork()` is unavailable.
|
|
35
|
+
*/
|
|
36
|
+
export declare class ClusterManager {
|
|
37
|
+
#private;
|
|
38
|
+
/** Active clusters indexed by cluster ID. */
|
|
39
|
+
readonly clusters: Map<number, ClusterInfo>;
|
|
40
|
+
/** Total number of shards across all clusters. */
|
|
41
|
+
shardCount: number;
|
|
42
|
+
/** Creates a cluster manager. @param options Clustering configuration. @throws {TypeError} If token or script is missing. */
|
|
43
|
+
constructor(options: ClusterManagerOptions);
|
|
44
|
+
/** Retrieves Discord's recommended shard count. @returns Recommended shard count. @throws {Error} If discovery fails or returns invalid data. */
|
|
45
|
+
fetchRecommendedShardCount(): Promise<number>;
|
|
46
|
+
/** Spawns all clusters. @returns A promise fulfilled after clusters have launched. */
|
|
47
|
+
spawn(): Promise<void>;
|
|
48
|
+
/** Checks if the recommended shard count has changed and respawns if so. */
|
|
49
|
+
checkAutoScale(): Promise<void>;
|
|
50
|
+
/** Gracefully shuts the existing clusters down and respawns them with the new shard count. @param newShardCount The new total shard count. */
|
|
51
|
+
respawn(newShardCount: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Gracefully stops all active clusters: sends SIGTERM, awaits each child's
|
|
54
|
+
* clean exit, and force-kills (SIGKILL) only children that outlast the
|
|
55
|
+
* shutdown timeout. Clears the auto-scale timer and cluster map.
|
|
56
|
+
* @param timeoutMs Grace period per child before force-kill. Defaults to the configured `shutdownTimeout` (5000 ms).
|
|
57
|
+
*/
|
|
58
|
+
shutdownAll(timeoutMs?: number): Promise<void>;
|
|
59
|
+
/** Immediately force-kills all active cluster processes and clears the cluster map. Prefer {@link shutdownAll} for a graceful stop. */
|
|
60
|
+
killAll(): void;
|
|
61
|
+
}
|
package/dist/sharding/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Gateway } from "@lunibee/ws";
|
|
2
2
|
export { ShardBus } from "./bus.js";
|
|
3
3
|
export type { ShardMessage, ShardMessageHandler } from "./bus.js";
|
|
4
|
+
export { ClusterManager } from "./cluster.js";
|
|
5
|
+
export type { ClusterManagerOptions, ClusterInfo } from "./cluster.js";
|
|
4
6
|
/** Configuration for a sharded Gateway client. */
|
|
5
7
|
export interface ShardManagerOptions {
|
|
6
8
|
/** Bot token. */ token: string;
|
|
@@ -8,6 +10,8 @@ export interface ShardManagerOptions {
|
|
|
8
10
|
/** Number of shards. Use `"auto"` to request Discord's recommended count. */ shardCount?: number | "auto";
|
|
9
11
|
/** Gateway reconnect behavior. */ reconnect?: boolean;
|
|
10
12
|
/** Delay between shard starts in milliseconds. */ spawnDelay?: number;
|
|
13
|
+
/** Interval in milliseconds to automatically check for recommended shard count and re-scale if needed. Must be an integer >= 1000. */ autoScaleInterval?: number;
|
|
14
|
+
/** Optional handler invoked when a background auto-scale check fails. Receives the thrown error. */ onAutoScaleError?: (error: unknown) => void;
|
|
11
15
|
}
|
|
12
16
|
/** Runtime state for a managed shard. */
|
|
13
17
|
export interface ShardInfo {
|
|
@@ -25,6 +29,10 @@ export declare class ShardManager {
|
|
|
25
29
|
fetchRecommendedShardCount(): Promise<number>;
|
|
26
30
|
/** Connects all shards sequentially. A destroyed manager is reinitialized before connecting. @returns A promise fulfilled after all shards connect. @throws {Error} If a shard fails to connect. */
|
|
27
31
|
connect(): Promise<void>;
|
|
32
|
+
/** Checks if the recommended shard count has changed and reconnects if so. */
|
|
33
|
+
checkAutoScale(): Promise<void>;
|
|
34
|
+
/** Forces a reshard to a new shard count, replacing all active shards. @param count New shard count. */
|
|
35
|
+
reshard(count: number): Promise<void>;
|
|
28
36
|
/** Permanently closes the current shard set and releases Gateway resources. The next connect recreates them. @returns Nothing. */
|
|
29
37
|
destroy(): void;
|
|
30
38
|
/** Calculates the target shard ID for a Discord guild snowflake. @param guildId Guild snowflake. @returns Shard identifier. */
|