lunibee 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +1 -1
  2. package/dist/builders/commands.d.ts +1 -1
  3. package/dist/builders/components.d.ts +9 -11
  4. package/dist/builders/embed.d.ts +1 -1
  5. package/dist/builders/index.d.ts +1 -1
  6. package/dist/builders/index.js +87 -1298
  7. package/dist/builders/index.js.map +3 -11
  8. package/dist/chunks/index-0wn81eng.js +2492 -0
  9. package/dist/chunks/index-0wn81eng.js.map +17 -0
  10. package/dist/chunks/index-2en489j6.js +40 -0
  11. package/dist/chunks/index-2en489j6.js.map +10 -0
  12. package/dist/chunks/index-3hyps48a.js +203 -0
  13. package/dist/chunks/index-3hyps48a.js.map +11 -0
  14. package/dist/chunks/index-57c5n3ms.js +2274 -0
  15. package/dist/chunks/index-57c5n3ms.js.map +19 -0
  16. package/dist/chunks/index-5yx92e36.js +1170 -0
  17. package/dist/chunks/index-5yx92e36.js.map +14 -0
  18. package/dist/chunks/index-690bp093.js +95 -0
  19. package/dist/chunks/index-690bp093.js.map +10 -0
  20. package/dist/chunks/index-7fp2hvmg.js +2155 -0
  21. package/dist/chunks/index-7fp2hvmg.js.map +24 -0
  22. package/dist/chunks/index-cnmsajvk.js +1119 -0
  23. package/dist/chunks/index-cnmsajvk.js.map +12 -0
  24. package/dist/chunks/index-fdvgk2sw.js +1310 -0
  25. package/dist/chunks/index-fdvgk2sw.js.map +21 -0
  26. package/dist/chunks/index-gyvps8b9.js +214 -0
  27. package/dist/chunks/index-gyvps8b9.js.map +12 -0
  28. package/dist/chunks/index-jzg4bh99.js +1533 -0
  29. package/dist/chunks/index-jzg4bh99.js.map +21 -0
  30. package/dist/chunks/index-mgw2yvrm.js +3547 -0
  31. package/dist/chunks/index-mgw2yvrm.js.map +25 -0
  32. package/dist/chunks/index-yh76b55h.js +3449 -0
  33. package/dist/chunks/index-yh76b55h.js.map +29 -0
  34. package/dist/collection/index.js +9 -1118
  35. package/dist/collection/index.js.map +3 -6
  36. package/dist/core/api.d.ts +3 -3
  37. package/dist/core/channel-create.d.ts +2 -2
  38. package/dist/core/commands.d.ts +3 -3
  39. package/dist/core/events.d.ts +4 -4
  40. package/dist/core/handles.d.ts +4 -4
  41. package/dist/core/index.d.ts +25 -20
  42. package/dist/core/index.js +68 -10130
  43. package/dist/core/index.js.map +3 -65
  44. package/dist/core/permissions.d.ts +1 -1
  45. package/dist/core/redis-cache.d.ts +1 -1
  46. package/dist/formatters/index.js +41 -110
  47. package/dist/formatters/index.js.map +3 -4
  48. package/dist/handlers/index.js +5 -37
  49. package/dist/handlers/index.js.map +3 -4
  50. package/dist/index.d.ts +21 -21
  51. package/dist/index.js +641 -16325
  52. package/dist/index.js.map +3 -92
  53. package/dist/managers/advanced.d.ts +3 -3
  54. package/dist/managers/application.d.ts +2 -2
  55. package/dist/managers/base.d.ts +1 -1
  56. package/dist/managers/emoji.d.ts +2 -2
  57. package/dist/managers/guild-resources.d.ts +6 -6
  58. package/dist/managers/guild.d.ts +3 -3
  59. package/dist/managers/index.d.ts +4 -8
  60. package/dist/managers/index.js +51 -4060
  61. package/dist/managers/index.js.map +3 -29
  62. package/dist/managers/member.d.ts +2 -2
  63. package/dist/managers/message.d.ts +14 -6
  64. package/dist/managers/role.d.ts +2 -2
  65. package/dist/managers/thread.d.ts +3 -3
  66. package/dist/managers/user.d.ts +7 -4
  67. package/dist/rest/index.d.ts +10 -35
  68. package/dist/rest/index.js +57 -2184
  69. package/dist/rest/index.js.map +3 -18
  70. package/dist/rest/limiter.d.ts +9 -1
  71. package/dist/rest/webhook.d.ts +2 -8
  72. package/dist/sharding/bus.d.ts +2 -2
  73. package/dist/sharding/cluster.d.ts +45 -5
  74. package/dist/sharding/context.d.ts +45 -3
  75. package/dist/sharding/gateway-bot.d.ts +2 -1
  76. package/dist/sharding/health.d.ts +1 -1
  77. package/dist/sharding/identify.d.ts +12 -0
  78. package/dist/sharding/index.d.ts +44 -11
  79. package/dist/sharding/index.js +60 -4620
  80. package/dist/sharding/index.js.map +3 -33
  81. package/dist/sharding/supervisor.d.ts +4 -2
  82. package/dist/structures/audit-log.d.ts +1 -1
  83. package/dist/structures/base.d.ts +6 -6
  84. package/dist/structures/channels.d.ts +1 -1
  85. package/dist/structures/embed.d.ts +1 -1
  86. package/dist/structures/index.d.ts +14 -14
  87. package/dist/structures/index.js +109 -2382
  88. package/dist/structures/index.js.map +3 -16
  89. package/dist/structures/interactions.d.ts +13 -9
  90. package/dist/structures/options.d.ts +1 -1
  91. package/dist/structures/permissions.d.ts +2 -2
  92. package/dist/structures/resources.d.ts +10 -10
  93. package/dist/structures/sticker.d.ts +1 -1
  94. package/dist/types/gateway.d.ts +2 -79
  95. package/dist/types/index.d.ts +0 -6
  96. package/dist/types/index.js +41 -272
  97. package/dist/types/index.js.map +3 -6
  98. package/dist/utils/index.js +23 -209
  99. package/dist/utils/index.js.map +3 -5
  100. package/dist/voice/connection.d.ts +200 -0
  101. package/dist/voice/crypto.d.ts +50 -0
  102. package/dist/voice/dave.d.ts +85 -0
  103. package/dist/voice/effects.d.ts +31 -0
  104. package/dist/voice/emitter.d.ts +14 -0
  105. package/dist/voice/gateway.d.ts +59 -0
  106. package/dist/voice/index.d.ts +25 -228
  107. package/dist/voice/index.js +111 -442
  108. package/dist/voice/index.js.map +3 -4
  109. package/dist/voice/libopus.d.ts +88 -0
  110. package/dist/voice/networking.d.ts +80 -0
  111. package/dist/voice/ogg.d.ts +24 -0
  112. package/dist/voice/opus.d.ts +18 -0
  113. package/dist/voice/player.d.ts +79 -0
  114. package/dist/voice/protocol.d.ts +76 -0
  115. package/dist/voice/receiver.d.ts +59 -0
  116. package/dist/voice/resource.d.ts +102 -0
  117. package/dist/voice/udp.d.ts +40 -0
  118. package/dist/ws/close-codes.d.ts +1 -1
  119. package/dist/ws/index.d.ts +25 -4
  120. package/dist/ws/index.js +47 -1336
  121. package/dist/ws/index.js.map +3 -17
  122. package/dist/ws/protocol.d.ts +1 -1
  123. package/dist/ws/send-budget.d.ts +0 -2
  124. package/dist/ws/state.d.ts +0 -10
  125. package/package.json +19 -16
@@ -0,0 +1,2155 @@
1
+ // @bun
2
+ // packages/rest/package.json
3
+ var package_default = {
4
+ name: "@lunibee/rest",
5
+ version: "0.4.0",
6
+ type: "module",
7
+ module: "./src/index.ts",
8
+ types: "./src/index.ts",
9
+ exports: {
10
+ ".": "./src/index.ts"
11
+ },
12
+ dependencies: {
13
+ "@lunibee/types": "workspace:*"
14
+ }
15
+ };
16
+
17
+ // packages/rest/src/store.ts
18
+ var GLOBAL_WINDOW_MS = 1050;
19
+ function takeGlobalSlot(log, now, limit, window = GLOBAL_WINDOW_MS) {
20
+ while (log.length > 0 && log[0] <= now - window)
21
+ log.shift();
22
+ if (log.length >= limit)
23
+ return Math.max(1, log[0] + window - now + 1);
24
+ log.push(now);
25
+ return 0;
26
+ }
27
+ var PROBE_POLL_MS = 50;
28
+ var DEFAULT_PROBE_MS = 16000;
29
+ var BUCKET_LINGER_MS = 60000;
30
+ var WINDOW_TOLERANCE_MS = 50;
31
+ function reserveState(state, now, probeMs) {
32
+ if (state && state.resetAt > now) {
33
+ if (state.remaining > 0)
34
+ return {
35
+ reservation: {
36
+ granted: true,
37
+ known: true,
38
+ resetAt: state.resetAt
39
+ },
40
+ next: { ...state, remaining: state.remaining - 1 }
41
+ };
42
+ const wait = state.resetAt - now;
43
+ return {
44
+ reservation: {
45
+ granted: false,
46
+ retryAfterMs: state.provisional ? Math.min(wait, PROBE_POLL_MS) : wait,
47
+ reason: state.probe ? "probe" : "bucket"
48
+ }
49
+ };
50
+ }
51
+ const resetAt = now + probeMs;
52
+ if (state?.limit !== undefined && state.limit > 0)
53
+ return {
54
+ reservation: { granted: true, known: true, resetAt },
55
+ next: {
56
+ remaining: state.limit - 1,
57
+ resetAt,
58
+ limit: state.limit,
59
+ provisional: true
60
+ }
61
+ };
62
+ return {
63
+ reservation: { granted: true, known: false, resetAt },
64
+ next: { remaining: 0, resetAt, provisional: true, probe: true }
65
+ };
66
+ }
67
+ function reconcileState(state, update, now) {
68
+ const incoming = update.resetAfterMs === undefined ? undefined : Math.ceil(now + update.resetAfterMs);
69
+ const active = !!state && state.resetAt > now && !state.probe;
70
+ let next;
71
+ if (active) {
72
+ next = { ...state };
73
+ const stale = update.window !== undefined && next.window !== undefined && update.window < next.window - WINDOW_TOLERANCE_MS;
74
+ if (!stale) {
75
+ if (update.remaining !== undefined)
76
+ next.remaining = Math.min(next.remaining, update.remaining);
77
+ if (incoming !== undefined) {
78
+ const newer = update.window !== undefined && next.window !== undefined && update.window > next.window + WINDOW_TOLERANCE_MS;
79
+ next.resetAt = next.provisional || newer ? incoming : Math.max(next.resetAt, incoming);
80
+ delete next.provisional;
81
+ }
82
+ if (update.window !== undefined)
83
+ next.window = Math.max(next.window ?? 0, update.window);
84
+ }
85
+ } else {
86
+ if (update.remaining === undefined && update.retryAfterMs === undefined) {
87
+ return state?.probe ? null : undefined;
88
+ }
89
+ next = {
90
+ remaining: update.remaining ?? 0,
91
+ resetAt: incoming ?? now
92
+ };
93
+ if (state?.limit !== undefined)
94
+ next.limit = state.limit;
95
+ if (update.window !== undefined)
96
+ next.window = update.window;
97
+ }
98
+ if (update.limit !== undefined && update.limit > 0)
99
+ next.limit = update.limit;
100
+ if (update.retryAfterMs !== undefined) {
101
+ next.remaining = 0;
102
+ next.resetAt = Math.max(next.resetAt, Math.ceil(now + update.retryAfterMs));
103
+ delete next.provisional;
104
+ }
105
+ return next;
106
+ }
107
+
108
+ class MemoryRateLimitStore2 {
109
+ #buckets = new Map;
110
+ #routeBuckets = new Map;
111
+ #globals = new Map;
112
+ #globalLogs = new Map;
113
+ static #PRUNE_INTERVAL = 60000;
114
+ #lastPruneAt = 0;
115
+ static #MAX_ENTRIES = 1e4;
116
+ reservesGlobal = true;
117
+ constructor() {}
118
+ static #evict(map) {
119
+ while (map.size > MemoryRateLimitStore2.#MAX_ENTRIES)
120
+ map.delete(map.keys().next().value);
121
+ }
122
+ getBucketHash(route) {
123
+ return this.#routeBuckets.get(route);
124
+ }
125
+ setBucketHash(route, hash) {
126
+ this.#routeBuckets.delete(route);
127
+ this.#routeBuckets.set(route, hash);
128
+ MemoryRateLimitStore2.#evict(this.#routeBuckets);
129
+ }
130
+ getBucket(key) {
131
+ return this.#buckets.get(key);
132
+ }
133
+ reserve(key, options = {}) {
134
+ const now = Date.now();
135
+ if (options.global !== undefined) {
136
+ const globalWait = this.getGlobalReset(options.global) - now;
137
+ if (globalWait > 0)
138
+ return {
139
+ granted: false,
140
+ retryAfterMs: globalWait,
141
+ reason: "global"
142
+ };
143
+ }
144
+ const limit = options.global === undefined ? 0 : options.globalLimit ?? 0;
145
+ let log;
146
+ if (limit > 0) {
147
+ log = this.#globalLogs.get(options.global);
148
+ if (!log) {
149
+ this.#globalLogs.set(options.global, log = []);
150
+ MemoryRateLimitStore2.#evict(this.#globalLogs);
151
+ }
152
+ const wait = takeGlobalSlot(log, now, limit);
153
+ if (wait > 0)
154
+ return { granted: false, retryAfterMs: wait, reason: "global" };
155
+ log.pop();
156
+ }
157
+ const { reservation, next } = reserveState(this.#buckets.get(key), now, options.probeMs ?? DEFAULT_PROBE_MS);
158
+ if (log && reservation.granted)
159
+ log.push(now);
160
+ if (next)
161
+ this.#write(key, next);
162
+ return reservation;
163
+ }
164
+ reconcile(key, update) {
165
+ const next = reconcileState(this.#buckets.get(key), update, Date.now());
166
+ if (next === null)
167
+ this.#buckets.delete(key);
168
+ else if (next)
169
+ this.#write(key, next);
170
+ }
171
+ release(key, grant, refund) {
172
+ const state = this.#buckets.get(key);
173
+ if (!state || state.resetAt !== grant.resetAt)
174
+ return;
175
+ if (state.probe)
176
+ this.#buckets.delete(key);
177
+ else if (refund)
178
+ state.remaining = Math.min(state.remaining + 1, state.limit ?? Number.POSITIVE_INFINITY);
179
+ }
180
+ updateBucket(key, state) {
181
+ this.#write(key, state);
182
+ }
183
+ #write(key, state) {
184
+ this.#buckets.delete(key);
185
+ this.#buckets.set(key, state);
186
+ MemoryRateLimitStore2.#evict(this.#buckets);
187
+ const now = Date.now();
188
+ if (now - this.#lastPruneAt >= MemoryRateLimitStore2.#PRUNE_INTERVAL) {
189
+ this.#lastPruneAt = now;
190
+ this.prune(BUCKET_LINGER_MS);
191
+ }
192
+ }
193
+ prune(maxAge = 0) {
194
+ const cutoff = Date.now() - Math.max(0, maxAge);
195
+ for (const [key, state] of this.#buckets) {
196
+ if (state.resetAt <= cutoff)
197
+ this.#buckets.delete(key);
198
+ }
199
+ for (const [scope, resetAt] of this.#globals)
200
+ if (resetAt <= Date.now())
201
+ this.#globals.delete(scope);
202
+ for (const [scope, log] of this.#globalLogs)
203
+ if ((log.at(-1) ?? 0) <= Date.now() - GLOBAL_WINDOW_MS)
204
+ this.#globalLogs.delete(scope);
205
+ }
206
+ getGlobalReset(scope = "") {
207
+ return this.#globals.get(scope) ?? 0;
208
+ }
209
+ setGlobalReset(resetAt, scope = "") {
210
+ this.#globals.set(scope, resetAt);
211
+ MemoryRateLimitStore2.#evict(this.#globals);
212
+ }
213
+ extendGlobalReset(scope, waitMs) {
214
+ const resetAt = Date.now() + Math.max(0, waitMs);
215
+ if (resetAt > this.getGlobalReset(scope))
216
+ this.setGlobalReset(resetAt, scope);
217
+ }
218
+ get size() {
219
+ return this.#buckets.size;
220
+ }
221
+ }
222
+
223
+ // packages/rest/src/errors.ts
224
+ function kindFromStatus(status) {
225
+ if (status === 429)
226
+ return "rateLimited";
227
+ if (status === 401)
228
+ return "authentication";
229
+ if (status === 403)
230
+ return "permission";
231
+ if (status === 404)
232
+ return "notFound";
233
+ if (status === 400 || status === 422)
234
+ return "validation";
235
+ if (status >= 500)
236
+ return "server";
237
+ if (status === 0)
238
+ return "network";
239
+ return "client";
240
+ }
241
+ function redactPath2(path) {
242
+ return path.replace(/^(\/webhooks\/[^/?]+\/)[^/?]+/i, "$1:token").replace(/^(\/interactions\/[^/?]+\/)[^/?]+/i, "$1:token");
243
+ }
244
+ var TOKEN_IN_TEXT = /(\/(?:webhooks|interactions)\/[^/\s?"'`]+\/)[^/\s?"'`]+/gi;
245
+ function redactText(text) {
246
+ return text.replace(TOKEN_IN_TEXT, "$1:token");
247
+ }
248
+ function sanitizeCause(error) {
249
+ if (!(error instanceof Error))
250
+ return;
251
+ const copy = new Error(redactText(error.message));
252
+ copy.name = error.name;
253
+ if (error.stack)
254
+ copy.stack = redactText(error.stack);
255
+ for (const key of ["code", "errno", "syscall"]) {
256
+ const value = error[key];
257
+ if (typeof value === "string" || typeof value === "number")
258
+ copy[key] = value;
259
+ }
260
+ return copy;
261
+ }
262
+ var HINTS = {
263
+ 50001: "The bot cannot see this resource: check that it is in the guild and has View Channel there.",
264
+ 50013: "The bot lacks a permission for this action, or the target role or member is at or above the bot's highest role."
265
+ };
266
+
267
+ class RESTError2 extends Error {
268
+ kind;
269
+ status;
270
+ code;
271
+ errors;
272
+ method;
273
+ path;
274
+ hint;
275
+ constructor(message, status, code, errors, options = {}) {
276
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
277
+ this.name = "RESTError";
278
+ this.status = status;
279
+ this.kind = options.kind ?? kindFromStatus(status);
280
+ this.code = code;
281
+ this.hint = code === undefined ? undefined : HINTS[code];
282
+ this.errors = errors;
283
+ this.method = options.method;
284
+ this.path = options.path === undefined ? undefined : redactPath2(options.path);
285
+ }
286
+ get retryable() {
287
+ return this.kind === "rateLimited" || this.kind === "server" || this.kind === "network" || this.kind === "timeout" || this.kind === "coordination" || this.kind === "overloaded";
288
+ }
289
+ }
290
+ function abortError2(path, reason) {
291
+ return new RESTError2(reason instanceof Error ? reason.message : "Discord REST request was aborted", 0, undefined, undefined, { path, cause: reason, kind: "aborted" });
292
+ }
293
+ var MAX_TIMER_MS = 2147483647;
294
+ async function sleep(delay, signal, path) {
295
+ if (delay <= 0)
296
+ return;
297
+ delay = Math.min(delay, MAX_TIMER_MS);
298
+ await new Promise((resolve, reject) => {
299
+ const finish = (error) => {
300
+ clearTimeout(timer);
301
+ signal?.removeEventListener("abort", onAbort);
302
+ if (error)
303
+ reject(error);
304
+ else
305
+ resolve();
306
+ };
307
+ const onAbort = () => finish(abortError2(path, signal?.reason));
308
+ const timer = setTimeout(finish, delay);
309
+ if (signal?.aborted)
310
+ return onAbort();
311
+ signal?.addEventListener("abort", onAbort, { once: true });
312
+ });
313
+ }
314
+ async function abortable(promise, signal, path) {
315
+ if (!signal)
316
+ return promise;
317
+ if (signal.aborted)
318
+ throw abortError2(path, signal.reason);
319
+ return new Promise((resolve, reject) => {
320
+ const onAbort = () => reject(abortError2(path, signal.reason));
321
+ signal.addEventListener("abort", onAbort, { once: true });
322
+ promise.then(() => {
323
+ signal.removeEventListener("abort", onAbort);
324
+ resolve();
325
+ }, (error) => {
326
+ signal.removeEventListener("abort", onAbort);
327
+ reject(error);
328
+ });
329
+ });
330
+ }
331
+
332
+ // packages/rest/src/route.ts
333
+ import { createHash } from "crypto";
334
+ function normalizeRoutePath2(path) {
335
+ const queryIndex = path.indexOf("?");
336
+ const routePath = queryIndex === -1 ? path : path.slice(0, queryIndex);
337
+ return redactPath2(routePath).replace(/\/\d+(?=\/|$)/g, "/:id").replace(/\/reactions\/[^/]+/i, "/reactions/:emoji");
338
+ }
339
+ function majorParameter2(path) {
340
+ const match = /^\/(channels|guilds|webhooks)\/(\d+)(?:\/([^/?]+))?/i.exec(path);
341
+ if (!match)
342
+ return "@none";
343
+ if (match[1].toLowerCase() === "webhooks" && match[3] !== undefined)
344
+ return `${match[2]}:${tokenDigest(match[3])}`;
345
+ return match[2];
346
+ }
347
+ function tokenDigest(token) {
348
+ return createHash("sha256").update(token).digest("hex").slice(0, 16);
349
+ }
350
+ function scopeBucket2(hashOrRoute, major) {
351
+ return `${hashOrRoute}|${major}`;
352
+ }
353
+ function createRouteKey2(method, path) {
354
+ const normalizedMethod = method.toUpperCase();
355
+ return {
356
+ method: normalizedMethod,
357
+ path,
358
+ route: `${normalizedMethod}:${normalizeRoutePath2(path)}`,
359
+ major: majorParameter2(path)
360
+ };
361
+ }
362
+ function serializeQuery2(query) {
363
+ if (query === undefined)
364
+ return "";
365
+ if (typeof query === "string")
366
+ return query.length === 0 || query.startsWith("?") ? query : `?${query}`;
367
+ const params = query instanceof URLSearchParams ? query : new URLSearchParams(Object.entries(query).flatMap(([key, value]) => value === undefined || value === null ? [] : [[key, String(value)]]));
368
+ const serialized = params.toString();
369
+ return serialized.length === 0 ? "" : `?${serialized}`;
370
+ }
371
+
372
+ // packages/rest/src/scheduler.ts
373
+ class RequestScheduler2 {
374
+ static MAX_REMAPS = 2;
375
+ #queues = new Map;
376
+ #waiting = new Map;
377
+ #queued = 0;
378
+ #maxQueued;
379
+ #maxQueuedPerBucket;
380
+ constructor(options = {}) {
381
+ this.#maxQueued = options.maxQueued ?? Number.POSITIVE_INFINITY;
382
+ this.#maxQueuedPerBucket = options.maxQueuedPerBucket ?? Number.POSITIVE_INFINITY;
383
+ if (!(this.#maxQueued >= 0) || !(this.#maxQueuedPerBucket >= 0))
384
+ throw new RangeError("Queue limits must be non-negative numbers.");
385
+ }
386
+ get size() {
387
+ return this.#queues.size;
388
+ }
389
+ get queued() {
390
+ return this.#queued;
391
+ }
392
+ async runResolved(resolveKey, signal, path, task) {
393
+ let key = await resolveKey();
394
+ for (let hop = 0;hop < RequestScheduler2.MAX_REMAPS; hop++) {
395
+ const outcome = await this.run(key, signal, path, async (release) => {
396
+ const current = await resolveKey();
397
+ if (current !== key)
398
+ return { done: false, key: current };
399
+ return { done: true, value: await task(key, release) };
400
+ });
401
+ if (outcome.done)
402
+ return outcome.value;
403
+ key = outcome.key;
404
+ }
405
+ return this.run(key, signal, path, (release) => task(key, release));
406
+ }
407
+ async run(key, signal, path, task) {
408
+ const previous = this.#queues.get(key);
409
+ const waiting = this.#waiting.get(key) ?? 0;
410
+ if (previous && (this.#queued >= this.#maxQueued || waiting >= this.#maxQueuedPerBucket))
411
+ throw new RESTError2("The REST request queue is full; request not sent.", 0, undefined, undefined, { path, kind: "overloaded" });
412
+ let open;
413
+ const gate = new Promise((resolve) => {
414
+ open = resolve;
415
+ });
416
+ this.#queues.set(key, gate);
417
+ let released = false;
418
+ let started = false;
419
+ const release = () => {
420
+ if (released)
421
+ return;
422
+ released = true;
423
+ if (!started && previous)
424
+ previous.then(open, open);
425
+ else
426
+ open();
427
+ if (this.#queues.get(key) === gate)
428
+ this.#queues.delete(key);
429
+ };
430
+ try {
431
+ if (previous) {
432
+ this.#queued++;
433
+ this.#waiting.set(key, waiting + 1);
434
+ try {
435
+ await abortable(previous, signal, path);
436
+ } finally {
437
+ this.#queued--;
438
+ const left = (this.#waiting.get(key) ?? 1) - 1;
439
+ if (left > 0)
440
+ this.#waiting.set(key, left);
441
+ else
442
+ this.#waiting.delete(key);
443
+ }
444
+ } else
445
+ await abortable(Promise.resolve(), signal, path);
446
+ started = true;
447
+ return await task(release);
448
+ } finally {
449
+ release();
450
+ }
451
+ }
452
+ }
453
+
454
+ // packages/rest/src/headers.ts
455
+ function headerNumber2(headers, name) {
456
+ const raw = headers.get(name);
457
+ if (raw === null || raw.trim() === "")
458
+ return;
459
+ const value = Number(raw);
460
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
461
+ }
462
+ var BUCKET_HASH = /^[A-Za-z0-9:_\-.]{1,64}$/;
463
+ var MAX_COUNT = 1e5;
464
+ function count(value) {
465
+ return value === undefined || value > MAX_COUNT ? undefined : Math.floor(value);
466
+ }
467
+ function parseRateLimitHeaders2(headers) {
468
+ const remaining = headerNumber2(headers, "X-RateLimit-Remaining");
469
+ const limit = headerNumber2(headers, "X-RateLimit-Limit");
470
+ const resetAfter = headerNumber2(headers, "X-RateLimit-Reset-After");
471
+ const reset = headerNumber2(headers, "X-RateLimit-Reset");
472
+ const retryAfter = headerNumber2(headers, "Retry-After");
473
+ const bucket = headers.get("X-RateLimit-Bucket")?.trim();
474
+ const scope = headers.get("X-RateLimit-Scope")?.trim().toLowerCase();
475
+ return {
476
+ limit: count(limit),
477
+ remaining: count(remaining),
478
+ resetAfterMs: resetAfter === undefined ? undefined : resetAfter * 1000,
479
+ resetAtMs: reset === undefined ? undefined : reset * 1000,
480
+ bucket: bucket && BUCKET_HASH.test(bucket) ? bucket : undefined,
481
+ scope: scope === "user" || scope === "global" || scope === "shared" ? scope : undefined,
482
+ global: headers.get("X-RateLimit-Global")?.trim().toLowerCase() === "true",
483
+ retryAfterMs: retryAfter === undefined ? undefined : retryAfter * 1000
484
+ };
485
+ }
486
+
487
+ // packages/rest/src/telemetry.ts
488
+ function emitTelemetry(sink, event) {
489
+ if (!sink)
490
+ return;
491
+ try {
492
+ sink.emit(event);
493
+ } catch {}
494
+ }
495
+ var LATENCY_BOUNDS = [
496
+ 1,
497
+ 5,
498
+ 10,
499
+ 25,
500
+ 50,
501
+ 100,
502
+ 250,
503
+ 500,
504
+ 1000,
505
+ 2500,
506
+ 5000,
507
+ 1e4,
508
+ 30000,
509
+ 60000
510
+ ];
511
+
512
+ class RESTMetrics2 {
513
+ #counters = new Map;
514
+ #gauges = new Map;
515
+ #histograms = new Map;
516
+ #routes = new Set;
517
+ #maxRoutes;
518
+ constructor(options = {}) {
519
+ this.#maxRoutes = options.maxRoutes ?? 500;
520
+ }
521
+ #route(route) {
522
+ if (this.#routes.has(route))
523
+ return route;
524
+ if (this.#routes.size >= this.#maxRoutes)
525
+ return "other";
526
+ this.#routes.add(route);
527
+ return route;
528
+ }
529
+ #inc(name, labels = {}, by = 1) {
530
+ const key = metricKey(name, labels);
531
+ this.#counters.set(key, (this.#counters.get(key) ?? 0) + by);
532
+ }
533
+ #observe(name, value, labels = {}) {
534
+ const key = metricKey(name, labels);
535
+ let h = this.#histograms.get(key);
536
+ if (!h) {
537
+ h = {
538
+ counts: new Array(LATENCY_BOUNDS.length + 1).fill(0),
539
+ sum: 0,
540
+ count: 0
541
+ };
542
+ this.#histograms.set(key, h);
543
+ }
544
+ let i = LATENCY_BOUNDS.findIndex((bound) => value <= bound);
545
+ if (i === -1)
546
+ i = LATENCY_BOUNDS.length;
547
+ h.counts[i]++;
548
+ h.sum += value;
549
+ h.count++;
550
+ }
551
+ emit(event) {
552
+ switch (event.type) {
553
+ case "response":
554
+ this.#inc("responses_total", {
555
+ method: event.method,
556
+ route: this.#route(event.route),
557
+ status: String(event.status)
558
+ });
559
+ this.#observe("response_latency_ms", event.durationMs, {
560
+ method: event.method
561
+ });
562
+ break;
563
+ case "request":
564
+ this.#inc("requests_total", {
565
+ method: event.method,
566
+ outcome: event.ok ? "ok" : "error"
567
+ });
568
+ this.#observe("request_latency_ms", event.durationMs, {
569
+ method: event.method
570
+ });
571
+ break;
572
+ case "rateLimited":
573
+ this.#inc("rate_limited_total", {
574
+ scope: event.scope,
575
+ route: this.#route(event.route)
576
+ });
577
+ break;
578
+ case "wait":
579
+ this.#inc("waits_total", { reason: event.reason });
580
+ this.#observe("wait_ms", event.durationMs, {
581
+ reason: event.reason
582
+ });
583
+ break;
584
+ case "reservation":
585
+ this.#inc("reservations_total", {
586
+ outcome: event.granted ? "granted" : "refused"
587
+ });
588
+ this.#observe("reservation_latency_ms", event.latencyMs);
589
+ break;
590
+ case "retry":
591
+ this.#inc("retries_total", { reason: event.reason });
592
+ break;
593
+ case "cancelled":
594
+ this.#inc("cancellations_total", { stage: event.stage });
595
+ break;
596
+ case "overloaded":
597
+ this.#inc("overloaded_total");
598
+ break;
599
+ case "tokenRejected":
600
+ this.#inc("token_rejected_total");
601
+ break;
602
+ case "networkError":
603
+ this.#inc("network_errors_total", {
604
+ timeout: String(event.timedOut)
605
+ });
606
+ break;
607
+ case "bucket":
608
+ this.#inc("bucket_changes_total", { change: event.change });
609
+ break;
610
+ case "store":
611
+ this.#inc("store_commands_total", {
612
+ operation: event.operation,
613
+ ok: String(event.ok)
614
+ });
615
+ this.#observe("store_latency_ms", event.latencyMs, {
616
+ operation: event.operation
617
+ });
618
+ break;
619
+ case "mode":
620
+ this.#inc("mode_changes_total", { mode: event.mode });
621
+ this.#gauges.set("degraded", event.mode === "degraded-local" || event.mode === "unavailable" ? 1 : 0);
622
+ break;
623
+ case "queue":
624
+ this.#gauges.set("queued", event.queued);
625
+ this.#gauges.set("in_flight", event.inFlight);
626
+ break;
627
+ }
628
+ }
629
+ snapshot() {
630
+ const histograms = {};
631
+ for (const [key, h] of this.#histograms) {
632
+ const buckets = {};
633
+ let running = 0;
634
+ LATENCY_BOUNDS.forEach((bound, i) => {
635
+ running += h.counts[i];
636
+ buckets[String(bound)] = running;
637
+ });
638
+ buckets["+Inf"] = h.count;
639
+ histograms[key] = { count: h.count, sum: h.sum, buckets };
640
+ }
641
+ return {
642
+ counters: Object.fromEntries(this.#counters),
643
+ gauges: Object.fromEntries(this.#gauges),
644
+ histograms
645
+ };
646
+ }
647
+ reset() {
648
+ this.#counters.clear();
649
+ this.#gauges.clear();
650
+ this.#histograms.clear();
651
+ this.#routes.clear();
652
+ }
653
+ toPrometheus(prefix = "lunibee_rest") {
654
+ const lines = [];
655
+ const typed = new Set;
656
+ const type = (name, kind) => {
657
+ if (typed.has(name))
658
+ return;
659
+ typed.add(name);
660
+ lines.push(`# TYPE ${name} ${kind}`);
661
+ };
662
+ for (const [key, value] of this.#counters) {
663
+ const name = `${prefix}_${key.split("{")[0]}`;
664
+ type(name, "counter");
665
+ lines.push(`${prefix}_${key} ${value}`);
666
+ }
667
+ for (const [key, value] of this.#gauges) {
668
+ type(`${prefix}_${key}`, "gauge");
669
+ lines.push(`${prefix}_${key} ${value}`);
670
+ }
671
+ for (const [key, h] of this.#histograms) {
672
+ const [base, rawLabels = ""] = key.split("{");
673
+ const labels = rawLabels.replace(/\}$/, "");
674
+ const name = `${prefix}_${base}`;
675
+ type(name, "histogram");
676
+ const join = (extra) => `{${[labels, extra].filter(Boolean).join(",")}}`;
677
+ let running = 0;
678
+ LATENCY_BOUNDS.forEach((bound, i) => {
679
+ running += h.counts[i];
680
+ lines.push(`${name}_bucket${join(`le="${bound}"`)} ${running}`);
681
+ });
682
+ lines.push(`${name}_bucket${join('le="+Inf"')} ${h.count}`);
683
+ lines.push(`${name}_sum${labels ? `{${labels}}` : ""} ${h.sum}`);
684
+ lines.push(`${name}_count${labels ? `{${labels}}` : ""} ${h.count}`);
685
+ }
686
+ return `${lines.join(`
687
+ `)}
688
+ `;
689
+ }
690
+ }
691
+ function metricKey(name, labels) {
692
+ const entries = Object.entries(labels);
693
+ if (entries.length === 0)
694
+ return name;
695
+ return `${name}{${entries.map(([k, v]) => `${k}="${v.replace(/["\\\n]/g, "_")}"`).join(",")}}`;
696
+ }
697
+
698
+ // packages/rest/src/limiter.ts
699
+ var DEFAULT_MAX_BACKOFF_MS2 = 600000;
700
+ var MAX_IDLE_REFUSALS = 16;
701
+ var MAX_CACHED_ROUTES = 1e4;
702
+
703
+ class RateLimiter2 {
704
+ #store;
705
+ #maxBackoff;
706
+ #telemetry;
707
+ #probeMs;
708
+ #routes = new Map;
709
+ #globalLimit;
710
+ #localGlobal = new Map;
711
+ constructor(store, options = {}) {
712
+ this.#store = store;
713
+ this.#maxBackoff = options.maxBackoff ?? DEFAULT_MAX_BACKOFF_MS2;
714
+ if (!(this.#maxBackoff >= 0))
715
+ throw new RangeError("maxBackoff must be a non-negative number.");
716
+ this.#probeMs = options.probeMs ?? DEFAULT_PROBE_MS;
717
+ if (!(this.#probeMs > 0))
718
+ throw new RangeError("probeMs must be a positive number.");
719
+ this.#telemetry = options.telemetry;
720
+ this.#globalLimit = options.globalLimit ?? 0;
721
+ if (!(this.#globalLimit >= 0) || !Number.isFinite(this.#globalLimit))
722
+ throw new RangeError("globalLimit must be a non-negative number.");
723
+ }
724
+ get maxBackoff() {
725
+ return this.#maxBackoff;
726
+ }
727
+ #capWait(ms) {
728
+ return Math.min(Math.max(0, ms), this.#maxBackoff);
729
+ }
730
+ get store() {
731
+ return this.#store;
732
+ }
733
+ get reserves() {
734
+ return typeof this.#store.reserve === "function";
735
+ }
736
+ #cacheRoute(route, hash) {
737
+ this.#routes.delete(route);
738
+ this.#routes.set(route, hash);
739
+ if (this.#routes.size > MAX_CACHED_ROUTES)
740
+ this.#routes.delete(this.#routes.keys().next().value);
741
+ }
742
+ async resolveBucketKey(route, major, identity) {
743
+ let hash = this.#routes.get(`${route}\x00${major}`) ?? this.#routes.get(route);
744
+ if (hash === undefined) {
745
+ hash = await this.#store.getBucketHash(route);
746
+ if (hash !== undefined)
747
+ this.#cacheRoute(route, hash);
748
+ }
749
+ const key = scopeBucket2(hash ?? route, major);
750
+ return identity === undefined ? key : `${identity}/${key}`;
751
+ }
752
+ async#call(path, operation, call) {
753
+ try {
754
+ return await call();
755
+ } catch (error) {
756
+ if (error instanceof RESTError2)
757
+ throw error;
758
+ throw new RESTError2(`The rate-limit store failed (${operation}); request not sent.`, 0, undefined, undefined, { path, kind: "coordination", cause: sanitizeCause(error) });
759
+ }
760
+ }
761
+ async acquire(initialKey, signal, path, options = {}) {
762
+ const store = this.#store;
763
+ const global = "global" in options ? options.global : "";
764
+ if (!store.reserve) {
765
+ const bucket = await this.#call(path, "getBucket", () => store.getBucket(initialKey));
766
+ if (bucket) {
767
+ const delay = bucket.resetAt - Date.now();
768
+ if (bucket.remaining <= 0 && delay > 0)
769
+ await this.#wait("bucket", delay, signal, path);
770
+ }
771
+ await this.#waitGlobal(global ?? "", signal, path);
772
+ if (global !== undefined)
773
+ await this.#localGlobalSlot(global, signal, path);
774
+ return {
775
+ reserved: false,
776
+ known: !!bucket && bucket.resetAt > Date.now()
777
+ };
778
+ }
779
+ const started = performance.now();
780
+ let idle = 0;
781
+ let bucketKey = initialKey;
782
+ for (;; ) {
783
+ if (global !== undefined && !store.reservesGlobal) {
784
+ await this.#waitGlobal(global, signal, path);
785
+ await this.#localGlobalSlot(global, signal, path);
786
+ }
787
+ const asked = performance.now();
788
+ const reservation = await this.#call(path, "reserve", () => store.reserve(bucketKey, {
789
+ global,
790
+ probeMs: this.#probeMs,
791
+ globalLimit: this.#globalLimit || undefined
792
+ }));
793
+ if (!reservation || typeof reservation !== "object" || typeof reservation.granted !== "boolean")
794
+ throw new RESTError2("The rate-limit store returned an invalid reservation; request not sent.", 0, undefined, undefined, { path, kind: "coordination" });
795
+ if (reservation.granted) {
796
+ let known = reservation.known;
797
+ if (known === undefined) {
798
+ const bucket = await this.#call(path, "getBucket", () => store.getBucket(bucketKey));
799
+ known = !!bucket && bucket.resetAt > Date.now();
800
+ }
801
+ emitTelemetry(this.#telemetry, {
802
+ type: "reservation",
803
+ granted: true,
804
+ known,
805
+ latencyMs: performance.now() - asked
806
+ });
807
+ return {
808
+ reserved: true,
809
+ known,
810
+ resetAt: reservation.resetAt,
811
+ ...bucketKey === initialKey ? {} : { key: bucketKey }
812
+ };
813
+ }
814
+ emitTelemetry(this.#telemetry, {
815
+ type: "reservation",
816
+ granted: false,
817
+ known: true,
818
+ latencyMs: performance.now() - asked
819
+ });
820
+ let wait = Number(reservation.retryAfterMs);
821
+ if (!(wait > 0)) {
822
+ if (++idle > MAX_IDLE_REFUSALS)
823
+ throw new RESTError2("The rate-limit store keeps refusing without a wait; request not sent.", 0, undefined, undefined, { path, kind: "coordination" });
824
+ wait = Math.min(100, 2 ** idle);
825
+ } else
826
+ idle = 0;
827
+ if (performance.now() - started + wait > 2 * this.#maxBackoff)
828
+ throw new RESTError2(`Waited for the rate limit longer than twice maxBackoff (${Math.ceil(this.#maxBackoff / 1000)}s); request not sent.`, 429, undefined, undefined, { path });
829
+ await this.#wait(reservation.reason ?? "bucket", wait, signal, path);
830
+ if (reservation.reason === "probe" && options.resolve)
831
+ bucketKey = await this.#call(path, "getBucketHash", options.resolve);
832
+ }
833
+ }
834
+ async#wait(reason, delay, signal, path) {
835
+ const ms = Math.min(delay, this.#maxBackoff);
836
+ await sleep(ms, signal, path);
837
+ emitTelemetry(this.#telemetry, {
838
+ type: "wait",
839
+ reason,
840
+ durationMs: ms
841
+ });
842
+ }
843
+ async#localGlobalSlot(scope, signal, path) {
844
+ if (!this.#globalLimit)
845
+ return;
846
+ let log = this.#localGlobal.get(scope);
847
+ if (!log) {
848
+ this.#localGlobal.set(scope, log = []);
849
+ if (this.#localGlobal.size > MAX_CACHED_ROUTES)
850
+ this.#localGlobal.delete(this.#localGlobal.keys().next().value);
851
+ }
852
+ for (;; ) {
853
+ const wait = takeGlobalSlot(log, Date.now(), this.#globalLimit);
854
+ if (wait === 0)
855
+ return;
856
+ await this.#wait("global", wait, signal, path);
857
+ }
858
+ }
859
+ async#waitGlobal(scope, signal, path) {
860
+ const globalDelay = await this.#call(path, "getGlobalReset", () => this.#store.getGlobalReset(scope)) - Date.now();
861
+ if (globalDelay > 0)
862
+ await this.#wait("global", globalDelay, signal, path);
863
+ }
864
+ async noteGlobalReset(resetAt, scope = "") {
865
+ const waitMs = this.#capWait(resetAt - Date.now());
866
+ if (this.#store.extendGlobalReset) {
867
+ await this.#store.extendGlobalReset(scope, waitMs);
868
+ return;
869
+ }
870
+ const current = await this.#store.getGlobalReset(scope);
871
+ const target = Date.now() + waitMs;
872
+ if (target > current)
873
+ await this.#store.setGlobalReset(target, scope);
874
+ }
875
+ retryAfterMs(response, body = {}) {
876
+ const fromBody = body.retry_after;
877
+ if (typeof fromBody === "number" && Number.isFinite(fromBody) && fromBody >= 0)
878
+ return fromBody * 1000;
879
+ const headers = parseRateLimitHeaders2(response.headers);
880
+ return headers.retryAfterMs ?? headers.resetAfterMs ?? 1000;
881
+ }
882
+ async applyResponse(response, bucketKey, route, major, options = {}) {
883
+ const headers = parseRateLimitHeaders2(response.headers);
884
+ let currentKey = bucketKey;
885
+ if (headers.bucket) {
886
+ const previous = this.#routes.get(route);
887
+ this.#cacheRoute(`${route}\x00${major}`, headers.bucket);
888
+ if (previous !== headers.bucket) {
889
+ await this.#store.setBucketHash(route, headers.bucket);
890
+ this.#cacheRoute(route, headers.bucket);
891
+ emitTelemetry(this.#telemetry, {
892
+ type: "bucket",
893
+ change: previous === undefined ? "discovered" : "remapped",
894
+ route
895
+ });
896
+ }
897
+ const key = scopeBucket2(headers.bucket, major);
898
+ currentKey = options.identity === undefined ? key : `${options.identity}/${key}`;
899
+ }
900
+ const limited = response.status === 429;
901
+ const retryAfterMs = limited ? this.#capWait(this.retryAfterMs(response, options.body)) : undefined;
902
+ const global = limited && (options.body?.global === true || headers.global || headers.scope === "global");
903
+ const onGlobal = global && options.global !== undefined;
904
+ let resetAfterMs = headers.resetAfterMs;
905
+ if (resetAfterMs === undefined && headers.resetAtMs !== undefined)
906
+ resetAfterMs = Math.max(0, headers.resetAtMs - Date.now());
907
+ const update = {
908
+ remaining: headers.remaining,
909
+ resetAfterMs: resetAfterMs === undefined ? undefined : this.#capWait(resetAfterMs),
910
+ limit: headers.limit,
911
+ window: headers.resetAtMs !== undefined && Math.abs(headers.resetAtMs - Date.now()) <= this.#maxBackoff ? headers.resetAtMs : undefined,
912
+ retryAfterMs: limited && !onGlobal ? retryAfterMs : undefined
913
+ };
914
+ if (this.#store.reconcile)
915
+ await this.#store.reconcile(currentKey, update);
916
+ else {
917
+ const next = reconcileState(await this.#store.getBucket(currentKey), update, Date.now());
918
+ if (next === null)
919
+ await this.#store.updateBucket(currentKey, {
920
+ remaining: 0,
921
+ resetAt: 0
922
+ });
923
+ else if (next)
924
+ await this.#store.updateBucket(currentKey, next);
925
+ }
926
+ if (currentKey !== bucketKey && options.grant?.reserved && this.#store.release)
927
+ await this.#store.release(bucketKey, options.grant, false);
928
+ if (onGlobal)
929
+ await this.noteGlobalReset(Date.now() + retryAfterMs, options.global);
930
+ return currentKey;
931
+ }
932
+ async releaseGrant(bucketKey, grant, notSent) {
933
+ if (!grant.reserved || !this.#store.release)
934
+ return;
935
+ try {
936
+ await this.#store.release(bucketKey, grant, notSent);
937
+ } catch {}
938
+ }
939
+ }
940
+
941
+ // packages/rest/src/identity.ts
942
+ import { createHash as createHash2 } from "crypto";
943
+ function tokenFingerprint2(token) {
944
+ return createHash2("sha256").update(token).digest("hex").slice(0, 16);
945
+ }
946
+ function requestIdentity2(path, fingerprint) {
947
+ if (/^\/interactions\//i.test(path))
948
+ return { bucket: "shared" };
949
+ if (/^\/webhooks\/[^/?]+\/[^/?]+/i.test(path))
950
+ return { bucket: "shared", global: fingerprint ?? "anon" };
951
+ if (!fingerprint)
952
+ return { bucket: "anon", global: "anon" };
953
+ return { bucket: fingerprint, global: fingerprint };
954
+ }
955
+
956
+ // packages/rest/src/transport.ts
957
+ var LOOPBACK = /^(?:localhost|127(?:\.\d{1,3}){3}|\[::1\])$/i;
958
+ function parseBaseURL(raw, allowInsecure = false) {
959
+ let url;
960
+ try {
961
+ url = new URL(raw);
962
+ } catch {
963
+ throw new TypeError("baseURL must be an absolute http(s) URL.");
964
+ }
965
+ if (url.protocol !== "https:" && url.protocol !== "http:")
966
+ throw new TypeError("baseURL must use https.");
967
+ if (url.username || url.password)
968
+ throw new TypeError("baseURL must not contain credentials.");
969
+ if (url.search || url.hash || /[?#]/.test(raw))
970
+ throw new TypeError("baseURL must not contain a query or fragment.");
971
+ if (url.protocol === "http:" && !allowInsecure && !LOOPBACK.test(url.hostname))
972
+ throw new TypeError("baseURL must use https: the bot token would travel in cleartext (allowInsecureBaseURL: true overrides this for a trusted network).");
973
+ return url.href.replace(/\/$/, "");
974
+ }
975
+
976
+ class TransportError2 extends Error {
977
+ timedOut;
978
+ constructor(message, timedOut, cause) {
979
+ super(message, cause === undefined ? undefined : { cause });
980
+ this.name = "TransportError";
981
+ this.timedOut = timedOut;
982
+ }
983
+ }
984
+
985
+ class HttpTransport2 {
986
+ #baseURL;
987
+ #timeout;
988
+ #fetch;
989
+ constructor(options = {}) {
990
+ this.#baseURL = parseBaseURL(options.baseURL ?? "https://discord.com/api/v10", options.allowInsecureBaseURL);
991
+ this.#timeout = Math.max(1, options.timeout ?? 15000);
992
+ this.#fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
993
+ }
994
+ get baseURL() {
995
+ return this.#baseURL;
996
+ }
997
+ get timeout() {
998
+ return this.#timeout;
999
+ }
1000
+ async send(request) {
1001
+ if (request.signal?.aborted)
1002
+ throw abortError2(request.path, request.signal.reason);
1003
+ const controller = new AbortController;
1004
+ const onAbort = () => controller.abort(request.signal?.reason);
1005
+ request.signal?.addEventListener("abort", onAbort, { once: true });
1006
+ const timer = setTimeout(() => controller.abort(new DOMException("REST request timeout", "TimeoutError")), this.#timeout);
1007
+ try {
1008
+ return await this.#fetch(`${this.#baseURL}${request.path}`, {
1009
+ method: request.method,
1010
+ headers: request.headers,
1011
+ body: request.body,
1012
+ signal: controller.signal,
1013
+ redirect: "error"
1014
+ });
1015
+ } catch (error) {
1016
+ if (error instanceof RESTError2)
1017
+ throw error;
1018
+ const timedOut = error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError");
1019
+ throw new TransportError2(timedOut ? `Discord REST request timed out after ${this.#timeout}ms` : "Discord REST request failed", timedOut, sanitizeCause(error));
1020
+ } finally {
1021
+ clearTimeout(timer);
1022
+ request.signal?.removeEventListener("abort", onAbort);
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ // packages/rest/src/decoder.ts
1028
+ class ResponseDecoder2 {
1029
+ constructor() {}
1030
+ async read(response) {
1031
+ if (response.status === 204)
1032
+ return;
1033
+ const contentType = response.headers.get("content-type") ?? "";
1034
+ if (contentType.includes("application/json"))
1035
+ return response.json().catch(() => {
1036
+ return;
1037
+ });
1038
+ return response.text().catch(() => {
1039
+ return;
1040
+ });
1041
+ }
1042
+ errorData(payload) {
1043
+ if (!payload || typeof payload !== "object")
1044
+ return {};
1045
+ const data = payload;
1046
+ return {
1047
+ message: typeof data.message === "string" ? data.message : undefined,
1048
+ code: typeof data.code === "number" ? data.code : undefined,
1049
+ retry_after: typeof data.retry_after === "number" ? data.retry_after : undefined,
1050
+ global: data.global === true
1051
+ };
1052
+ }
1053
+ retryAfter(response, data) {
1054
+ const raw = response.headers.get("Retry-After");
1055
+ const header = raw === null ? Number.NaN : Number(raw);
1056
+ const retryAfter = data.retry_after ?? (Number.isFinite(header) ? header : 1);
1057
+ return Math.max(0, retryAfter);
1058
+ }
1059
+ }
1060
+
1061
+ // packages/rest/src/routes.ts
1062
+ var Routes2 = {
1063
+ user: () => "/users/@me",
1064
+ userById: (userId) => `/users/${snowflake(userId, "User ID")}`,
1065
+ guild: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}`,
1066
+ currentUserGuild: (guildId) => `/users/@me/guilds/${snowflake(guildId, "Guild ID")}`,
1067
+ sticker: (stickerId) => `/stickers/${snowflake(stickerId, "Sticker ID")}`,
1068
+ messageReactionUser: (channelId, messageId, emoji, userId) => `${Routes2.messageReactions(channelId, messageId, emoji)}/${snowflake(userId, "User ID")}`,
1069
+ guildPreview: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/preview`,
1070
+ guildActiveThreads: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/threads/active`,
1071
+ guildWebhooks: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/webhooks`,
1072
+ guildInvites: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/invites`,
1073
+ guildChannels: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/channels`,
1074
+ guildMember: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/${snowflake(userId, "User ID")}`,
1075
+ guildRoles: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/roles`,
1076
+ guildRole: (guildId, roleId) => `/guilds/${snowflake(guildId, "Guild ID")}/roles/${snowflake(roleId, "Role ID")}`,
1077
+ guildMemberRole: (guildId, userId, roleId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/${snowflake(userId, "User ID")}/roles/${snowflake(roleId, "Role ID")}`,
1078
+ guildBans: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/bans`,
1079
+ guildBan: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/bans/${snowflake(userId, "User ID")}`,
1080
+ channel: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}`,
1081
+ channelMessages: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages`,
1082
+ message: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/${snowflake(messageId, "Message ID")}`,
1083
+ crosspostMessage: (channelId, messageId) => `${Routes2.message(channelId, messageId)}/crosspost`,
1084
+ messageReactions: (channelId, messageId, emoji) => `${Routes2.message(channelId, messageId)}/reactions/${encodeURIComponent(emoji)}`,
1085
+ messageReactionMe: (channelId, messageId, emoji) => `${Routes2.messageReactions(channelId, messageId, emoji)}/@me`,
1086
+ messageReactionsAll: (channelId, messageId) => `${Routes2.message(channelId, messageId)}/reactions`,
1087
+ channelMessagesPins: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/pins`,
1088
+ channelMessagesPin: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/pins/${snowflake(messageId, "Message ID")}`,
1089
+ messageThread: (channelId, messageId) => `${Routes2.message(channelId, messageId)}/threads`,
1090
+ channelBulkDelete: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/messages/bulk-delete`,
1091
+ channelWebhooks: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/webhooks`,
1092
+ channelInvites: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/invites`,
1093
+ webhook: (webhookId, token) => `/webhooks/${snowflake(webhookId, "Webhook ID")}${token ? `/${encodeURIComponent(token)}` : ""}`,
1094
+ webhookMessage: (webhookId, token, messageId) => `/webhooks/${snowflake(webhookId, "Webhook ID")}/${encodeURIComponent(token)}/messages/${snowflake(messageId, "Message ID")}`,
1095
+ applicationCommands: (applicationId) => `/applications/${snowflake(applicationId, "Application ID")}/commands`,
1096
+ applicationCommand: (applicationId, commandId) => `/applications/${snowflake(applicationId, "Application ID")}/commands/${snowflake(commandId, "Command ID")}`,
1097
+ guildApplicationCommands: (applicationId, guildId) => `/applications/${snowflake(applicationId, "Application ID")}/guilds/${snowflake(guildId, "Guild ID")}/commands`,
1098
+ guildApplicationCommand: (applicationId, guildId, commandId) => `/applications/${snowflake(applicationId, "Application ID")}/guilds/${snowflake(guildId, "Guild ID")}/commands/${snowflake(commandId, "Command ID")}`,
1099
+ interactionCallback: (interactionId, token) => `/interactions/${snowflake(interactionId, "Interaction ID")}/${encodeURIComponent(token)}/callback`,
1100
+ interactionOriginalResponse: (applicationId, token) => `/webhooks/${snowflake(applicationId, "Application ID")}/${encodeURIComponent(token)}/messages/@original`,
1101
+ channelPublicArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads/archived/public`,
1102
+ channelPrivateArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads/archived/private`,
1103
+ channelJoinedPrivateArchivedThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/users/@me/threads/archived/private`,
1104
+ threadMember: (threadId, userId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members/${userOrMe(userId)}`,
1105
+ channelTyping: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/typing`,
1106
+ channelThreads: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/threads`,
1107
+ threadMembers: (threadId) => `/channels/${snowflake(threadId, "Thread ID")}/thread-members`,
1108
+ guildScheduledEvents: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events`,
1109
+ guildScheduledEvent: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}`,
1110
+ guildScheduledEventUsers: (guildId, eventId) => `/guilds/${snowflake(guildId, "Guild ID")}/scheduled-events/${snowflake(eventId, "Event ID")}/users`,
1111
+ channelPermission: (channelId, overwriteId) => `/channels/${snowflake(channelId, "Channel ID")}/permissions/${snowflake(overwriteId, "Overwrite ID")}`,
1112
+ guildAutoModerationRules: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules`,
1113
+ guildAutoModerationRule: (guildId, ruleId) => `/guilds/${snowflake(guildId, "Guild ID")}/auto-moderation/rules/${snowflake(ruleId, "Rule ID")}`,
1114
+ voiceRegions: () => "/voice/regions",
1115
+ gateway: () => "/gateway",
1116
+ gatewayBot: () => "/gateway/bot",
1117
+ guildAuditLog: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/audit-logs`,
1118
+ guildMembers: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members`,
1119
+ guildMembersSearch: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/search`,
1120
+ guildMemberMe: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/members/@me`,
1121
+ guildBulkBan: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/bulk-ban`,
1122
+ guildVoiceState: (guildId, userId) => `/guilds/${snowflake(guildId, "Guild ID")}/voice-states/${userOrMe(userId)}`,
1123
+ guildIntegration: (guildId, integrationId) => `/guilds/${snowflake(guildId, "Guild ID")}/integrations/${snowflake(integrationId, "Integration ID")}`,
1124
+ userGuilds: () => "/users/@me/guilds",
1125
+ stickerPacks: (packId) => packId ? `/sticker-packs/${snowflake(packId, "Sticker pack ID")}` : "/sticker-packs",
1126
+ guildWelcomeScreen: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/welcome-screen`,
1127
+ guildOnboarding: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/onboarding`,
1128
+ guildPrune: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/prune`,
1129
+ stageInstances: () => "/stage-instances",
1130
+ stageInstanceByChannel: (channelId) => `/stage-instances/${snowflake(channelId, "Channel ID")}`,
1131
+ invite: (code) => `/invites/${encodeURIComponent(code)}`,
1132
+ guildVanity: (guildId) => `/guilds/${snowflake(guildId, "Guild ID")}/vanity-url`,
1133
+ guildEmoji: (guildId, emojiId) => emojiId ? `/guilds/${snowflake(guildId, "Guild ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/emojis`,
1134
+ applicationEmoji: (applicationId, emojiId) => emojiId ? `/applications/${snowflake(applicationId, "Application ID")}/emojis/${snowflake(emojiId, "Emoji ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/emojis`,
1135
+ pollExpire: (channelId, messageId) => `/channels/${snowflake(channelId, "Channel ID")}/polls/${snowflake(messageId, "Message ID")}/expire`,
1136
+ pollAnswerVoters: (channelId, messageId, answerId) => `/channels/${snowflake(channelId, "Channel ID")}/polls/${snowflake(messageId, "Message ID")}/answers/${integer(answerId, "Answer ID")}`,
1137
+ guildSticker: (guildId, stickerId) => stickerId ? `/guilds/${snowflake(guildId, "Guild ID")}/stickers/${snowflake(stickerId, "Sticker ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/stickers`,
1138
+ sendSoundboardSound: (channelId) => `/channels/${snowflake(channelId, "Channel ID")}/send-soundboard-sound`,
1139
+ soundboardDefaultSounds: () => "/soundboard-default-sounds",
1140
+ guildSoundboardSound: (guildId, soundId) => soundId ? `/guilds/${snowflake(guildId, "Guild ID")}/soundboard-sounds/${snowflake(soundId, "Sound ID")}` : `/guilds/${snowflake(guildId, "Guild ID")}/soundboard-sounds`,
1141
+ applicationSkus: (applicationId) => `/applications/${snowflake(applicationId, "Application ID")}/skus`,
1142
+ applicationEntitlement: (applicationId, entitlementId) => entitlementId ? `/applications/${snowflake(applicationId, "Application ID")}/entitlements/${snowflake(entitlementId, "Entitlement ID")}` : `/applications/${snowflake(applicationId, "Application ID")}/entitlements`,
1143
+ consumeEntitlement: (applicationId, entitlementId) => `/applications/${snowflake(applicationId, "Application ID")}/entitlements/${snowflake(entitlementId, "Entitlement ID")}/consume`,
1144
+ skuSubscription: (skuId, subscriptionId) => subscriptionId ? `/skus/${snowflake(skuId, "SKU ID")}/subscriptions/${snowflake(subscriptionId, "Subscription ID")}` : `/skus/${snowflake(skuId, "SKU ID")}/subscriptions`
1145
+ };
1146
+ function userOrMe(value) {
1147
+ return value === "@me" ? value : snowflake(value, "User ID");
1148
+ }
1149
+ function integer(value, field) {
1150
+ if (!Number.isInteger(value) || value < 0)
1151
+ throw new TypeError(`${field} must be a non-negative integer.`);
1152
+ return String(value);
1153
+ }
1154
+ function snowflake(value, field) {
1155
+ if (!/^\d{1,20}$/.test(value))
1156
+ throw new TypeError(`${field} must be a valid Discord snowflake.`);
1157
+ return value;
1158
+ }
1159
+ // packages/rest/src/error-codes.ts
1160
+ var RESTErrorCode2 = {
1161
+ UnknownAccount: 10001,
1162
+ UnknownChannel: 10003,
1163
+ UnknownGuild: 10004,
1164
+ UnknownInvite: 10006,
1165
+ UnknownMember: 10007,
1166
+ UnknownMessage: 10008,
1167
+ UnknownPermissionOverwrite: 10009,
1168
+ UnknownRole: 10011,
1169
+ UnknownUser: 10013,
1170
+ UnknownEmoji: 10014,
1171
+ UnknownWebhook: 10015,
1172
+ UnknownBan: 10026,
1173
+ UnknownSticker: 10060,
1174
+ UnknownInteraction: 10062,
1175
+ UnknownScheduledEvent: 10070,
1176
+ BotsCannotUseEndpoint: 20001,
1177
+ MaxGuilds: 30001,
1178
+ MaxPins: 30003,
1179
+ MaxRoles: 30005,
1180
+ MaxWebhooks: 30007,
1181
+ MaxEmojis: 30008,
1182
+ MaxReactions: 30010,
1183
+ MaxChannels: 30013,
1184
+ InteractionAlreadyAcknowledged: 40060,
1185
+ MissingAccess: 50001,
1186
+ CannotExecuteInDM: 50003,
1187
+ CannotEditOthersMessage: 50005,
1188
+ CannotSendEmptyMessage: 50006,
1189
+ CannotSendMessagesToUser: 50007,
1190
+ MissingPermissions: 50013,
1191
+ InvalidToken: 50014,
1192
+ MessageTooOldToBulkDelete: 50034,
1193
+ InvalidFormBody: 50035,
1194
+ ThreadArchived: 50083,
1195
+ CannotReplyWithoutReadHistory: 160002
1196
+ };
1197
+ // packages/rest/src/redis.ts
1198
+ var LUA_PRELUDE = `
1199
+ local function now_ms()
1200
+ local t = redis.call('TIME')
1201
+ return tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)
1202
+ end
1203
+ local function load(key)
1204
+ local raw = redis.call('GET', key)
1205
+ if not raw then return nil end
1206
+ local ok, s = pcall(cjson.decode, raw)
1207
+ if ok and type(s) == 'table' and type(s.resetAt) == 'number'
1208
+ and type(s.remaining) == 'number' then return s end
1209
+ return nil
1210
+ end
1211
+ local function save(key, s, now, linger)
1212
+ local ttl = math.max(1, math.ceil(s.resetAt - now)) + linger
1213
+ redis.call('SET', key, cjson.encode(s), 'PX', ttl)
1214
+ end
1215
+ `;
1216
+ var RESERVE_SCRIPT = `${LUA_PRELUDE}
1217
+ local now = now_ms()
1218
+ local glimit, gwindow = tonumber(ARGV[4]) or 0, tonumber(ARGV[5]) or 1000
1219
+ if #KEYS > 1 then
1220
+ local g = tonumber(redis.call('GET', KEYS[2]) or '0') or 0
1221
+ if g > now then return {0, math.ceil(g - now), 'global'} end
1222
+ end
1223
+ if #KEYS < 3 then glimit = 0 end
1224
+ -- A sliding log of send times: at most glimit in any gwindow span.
1225
+ if glimit > 0 then
1226
+ redis.call('ZREMRANGEBYSCORE', KEYS[3], '-inf', now - gwindow)
1227
+ if redis.call('ZCARD', KEYS[3]) >= glimit then
1228
+ local oldest = redis.call('ZRANGE', KEYS[3], 0, 0, 'WITHSCORES')
1229
+ return {0, math.max(1, tonumber(oldest[2]) + gwindow - now + 1), 'global'}
1230
+ end
1231
+ end
1232
+ local function count()
1233
+ if glimit > 0 then
1234
+ local seq = redis.call('ZCARD', KEYS[3])
1235
+ redis.call('ZADD', KEYS[3], now, now .. ':' .. seq)
1236
+ redis.call('PEXPIRE', KEYS[3], gwindow + 1000)
1237
+ end
1238
+ end
1239
+ local probe, poll, linger = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
1240
+ local s = load(KEYS[1])
1241
+ if s and s.resetAt > now then
1242
+ if s.remaining > 0 then
1243
+ s.remaining = s.remaining - 1
1244
+ save(KEYS[1], s, now, linger)
1245
+ count()
1246
+ return {1, s.resetAt, 1}
1247
+ end
1248
+ local wait = s.resetAt - now
1249
+ if s.provisional then wait = math.min(wait, poll) end
1250
+ local reason = 'bucket'
1251
+ if s.probe then reason = 'probe' end
1252
+ return {0, math.ceil(wait), reason}
1253
+ end
1254
+ local resetAt = now + probe
1255
+ if s and type(s.limit) == 'number' and s.limit > 0 then
1256
+ save(KEYS[1], {remaining = s.limit - 1, resetAt = resetAt, limit = s.limit, provisional = true}, now, linger)
1257
+ count()
1258
+ return {1, resetAt, 1}
1259
+ end
1260
+ save(KEYS[1], {remaining = 0, resetAt = resetAt, provisional = true, probe = true}, now, 0)
1261
+ count()
1262
+ return {1, resetAt, 0}
1263
+ `;
1264
+ var RECONCILE_SCRIPT = `${LUA_PRELUDE}
1265
+ local now = now_ms()
1266
+ local rem, after, limit = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3])
1267
+ local win, retry, linger = tonumber(ARGV[4]), tonumber(ARGV[5]), tonumber(ARGV[6])
1268
+ local tol = 50
1269
+ local incoming = nil
1270
+ if after then incoming = math.ceil(now + after) end
1271
+ local s = load(KEYS[1])
1272
+ local n
1273
+ if s and s.resetAt > now and not s.probe then
1274
+ n = s
1275
+ local stale = win and n.window and win < n.window - tol
1276
+ if not stale then
1277
+ if rem then n.remaining = math.min(n.remaining, rem) end
1278
+ if incoming then
1279
+ local newer = win and n.window and win > n.window + tol
1280
+ if n.provisional or newer then n.resetAt = incoming
1281
+ else n.resetAt = math.max(n.resetAt, incoming) end
1282
+ n.provisional = nil
1283
+ end
1284
+ if win then n.window = math.max(n.window or 0, win) end
1285
+ end
1286
+ else
1287
+ if not rem and not retry then
1288
+ if s and s.probe then redis.call('DEL', KEYS[1]) end
1289
+ return 0
1290
+ end
1291
+ n = {remaining = rem or 0, resetAt = incoming or now}
1292
+ if s and type(s.limit) == 'number' then n.limit = s.limit end
1293
+ if win then n.window = win end
1294
+ end
1295
+ if limit and limit > 0 then n.limit = limit end
1296
+ if retry then
1297
+ n.remaining = 0
1298
+ n.resetAt = math.max(n.resetAt, math.ceil(now + retry))
1299
+ n.provisional = nil
1300
+ end
1301
+ save(KEYS[1], n, now, linger)
1302
+ return 1
1303
+ `;
1304
+ var EXTEND_GLOBAL_SCRIPT = `${LUA_PRELUDE}
1305
+ local now = now_ms()
1306
+ local target = math.ceil(now + tonumber(ARGV[1]))
1307
+ local cur = tonumber(redis.call('GET', KEYS[1]) or '0') or 0
1308
+ if target > cur then
1309
+ redis.call('SET', KEYS[1], tostring(target), 'PX', math.max(1, target - now))
1310
+ return target - now
1311
+ end
1312
+ return math.max(0, cur - now)
1313
+ `;
1314
+ var RELEASE_SCRIPT = `${LUA_PRELUDE}
1315
+ local s = load(KEYS[1])
1316
+ if not s or s.resetAt ~= tonumber(ARGV[1]) then return 0 end
1317
+ if s.probe then
1318
+ redis.call('DEL', KEYS[1])
1319
+ return 1
1320
+ end
1321
+ if ARGV[2] == '1' then
1322
+ local cap = s.remaining + 1
1323
+ if type(s.limit) == 'number' then cap = math.min(cap, s.limit) end
1324
+ s.remaining = cap
1325
+ redis.call('SET', KEYS[1], cjson.encode(s), 'KEEPTTL')
1326
+ return 1
1327
+ end
1328
+ return 0
1329
+ `;
1330
+ var VERIFY_SCRIPT = "return redis.call('TIME')[1]";
1331
+ function fromBunRedis2(client) {
1332
+ return {
1333
+ get: (key) => client.send("GET", [key]),
1334
+ mget: (keys) => client.send("MGET", keys),
1335
+ set: (key, value, mode, duration) => client.send("SET", mode === undefined ? [key, String(value)] : [key, String(value), mode, String(duration)]),
1336
+ del: (...keys) => client.send("DEL", keys),
1337
+ eval: (script, numKeys, ...args) => client.send("EVAL", [script, String(numKeys), ...args.map(String)])
1338
+ };
1339
+ }
1340
+ var SCHEMA = "v1";
1341
+
1342
+ class RedisRateLimitStore2 {
1343
+ #client;
1344
+ #prefix;
1345
+ #onErrorCallback;
1346
+ #policy;
1347
+ #timeout;
1348
+ #telemetry;
1349
+ #atomic;
1350
+ #local = new MemoryRateLimitStore2;
1351
+ #lastError;
1352
+ #warnedSlots = false;
1353
+ #lastFailureAt;
1354
+ #failures = 0;
1355
+ #mode;
1356
+ constructor(options) {
1357
+ this.#client = options.client;
1358
+ this.#prefix = `${options.prefix ?? "lunibee:rest:"}${SCHEMA}:`;
1359
+ this.#onErrorCallback = options.onError;
1360
+ this.#policy = options.failurePolicy ?? "degraded-local";
1361
+ if (this.#policy !== "degraded-local" && this.#policy !== "fail-closed")
1362
+ throw new TypeError('failurePolicy must be "degraded-local" or "fail-closed".');
1363
+ this.#timeout = options.commandTimeout ?? 2000;
1364
+ if (!(this.#timeout > 0))
1365
+ throw new RangeError("commandTimeout must be a positive number.");
1366
+ this.#telemetry = options.telemetry;
1367
+ this.#atomic = typeof options.client.eval === "function";
1368
+ if (options.requireAtomic && !this.#atomic)
1369
+ throw new TypeError("requireAtomic: the Redis client has no eval method, so reservations cannot be atomic.");
1370
+ this.#mode = this.#atomic ? "distributed" : "observational";
1371
+ if (this.#atomic) {
1372
+ this.reserve = (key, opts) => this.#reserve(key, opts);
1373
+ this.reconcile = (key, update) => this.#reconcile(key, update);
1374
+ this.extendGlobalReset = (scope, waitMs) => this.#extendGlobal(scope, waitMs);
1375
+ this.release = (key, grant, refund) => this.#release(key, grant, refund);
1376
+ this.reservesGlobal = true;
1377
+ }
1378
+ }
1379
+ get lastError() {
1380
+ return this.#lastError;
1381
+ }
1382
+ isHealthy() {
1383
+ return this.#lastError === undefined;
1384
+ }
1385
+ get mode() {
1386
+ return this.#mode;
1387
+ }
1388
+ get supportsReservation() {
1389
+ return this.#atomic;
1390
+ }
1391
+ diagnostics() {
1392
+ const error = this.#lastError;
1393
+ return {
1394
+ mode: this.#mode,
1395
+ failurePolicy: this.#policy,
1396
+ atomic: this.#atomic,
1397
+ failures: this.#failures,
1398
+ lastFailureAt: this.#lastFailureAt,
1399
+ lastError: error === undefined ? undefined : String(sanitizeCause(error)?.message ?? error)
1400
+ };
1401
+ }
1402
+ async verify() {
1403
+ if (!this.#atomic)
1404
+ throw new TypeError("The Redis client has no eval method: reservations cannot be atomic.");
1405
+ try {
1406
+ await this.#withTimeout(this.#client.eval(VERIFY_SCRIPT, 0));
1407
+ } catch (error) {
1408
+ throw new RESTError2("The Redis rate-limit store cannot run its scripts.", 0, undefined, undefined, { kind: "coordination", cause: sanitizeCause(error) });
1409
+ }
1410
+ }
1411
+ #setMode(mode) {
1412
+ if (mode === this.#mode)
1413
+ return;
1414
+ const previous = this.#mode;
1415
+ this.#mode = mode;
1416
+ emitTelemetry(this.#telemetry, { type: "mode", mode, previous });
1417
+ console.warn(mode === "degraded-local" ? "[lunibee/rest] Redis rate-limit store unreachable: limiting locally in this process until it answers." : mode === "unavailable" ? "[lunibee/rest] Redis rate-limit store unreachable: requests fail until it answers (fail-closed)." : "[lunibee/rest] Redis rate-limit store reachable again: limits are shared.");
1418
+ }
1419
+ #withTimeout(promise) {
1420
+ let timer;
1421
+ return Promise.race([
1422
+ promise,
1423
+ new Promise((_, reject) => {
1424
+ timer = setTimeout(() => reject(new Error(`Redis command timed out after ${this.#timeout}ms`)), this.#timeout);
1425
+ })
1426
+ ]).finally(() => clearTimeout(timer));
1427
+ }
1428
+ async#run(operation, command, fallback, enforcing) {
1429
+ const started = performance.now();
1430
+ try {
1431
+ const value = await this.#withTimeout(command());
1432
+ emitTelemetry(this.#telemetry, {
1433
+ type: "store",
1434
+ operation,
1435
+ latencyMs: performance.now() - started,
1436
+ ok: true
1437
+ });
1438
+ this.#lastError = undefined;
1439
+ this.#setMode(this.#atomic ? "distributed" : "observational");
1440
+ return value;
1441
+ } catch (error) {
1442
+ emitTelemetry(this.#telemetry, {
1443
+ type: "store",
1444
+ operation,
1445
+ latencyMs: performance.now() - started,
1446
+ ok: false
1447
+ });
1448
+ this.#lastError = error;
1449
+ this.#lastFailureAt = Date.now();
1450
+ this.#failures++;
1451
+ if (!this.#warnedSlots && /CROSSSLOT/.test(String(error))) {
1452
+ this.#warnedSlots = true;
1453
+ console.warn('[lunibee/rest] Redis Cluster refused a script whose keys hash to different slots (CROSSSLOT): use a hash-tagged prefix such as "{mybot}:rest:".');
1454
+ }
1455
+ try {
1456
+ this.#onErrorCallback?.(operation, error);
1457
+ } catch {}
1458
+ if (this.#policy === "fail-closed") {
1459
+ this.#setMode("unavailable");
1460
+ if (enforcing)
1461
+ throw new RESTError2(`The Redis rate-limit store is unavailable (${operation}); request not sent (failurePolicy: "fail-closed").`, 0, undefined, undefined, { kind: "coordination", cause: sanitizeCause(error) });
1462
+ } else
1463
+ this.#setMode("degraded-local");
1464
+ return fallback();
1465
+ }
1466
+ }
1467
+ #bucketKey(key) {
1468
+ return `${this.#prefix}bucket:${key}`;
1469
+ }
1470
+ #globalKey(scope) {
1471
+ return `${this.#prefix}global:${scope}`;
1472
+ }
1473
+ async getBucketHash(route) {
1474
+ return this.#run("getBucketHash", async () => await this.#client.get(`${this.#prefix}route:${route}`) ?? undefined, () => this.#local.getBucketHash(route), false);
1475
+ }
1476
+ async setBucketHash(route, hash) {
1477
+ this.#local.setBucketHash(route, hash);
1478
+ await this.#run("setBucketHash", async () => {
1479
+ await this.#client.set(`${this.#prefix}route:${route}`, hash, "EX", 604800);
1480
+ }, () => {
1481
+ return;
1482
+ }, false);
1483
+ }
1484
+ async getBucket(key) {
1485
+ return this.#run("getBucket", async () => {
1486
+ const data = await this.#client.get(this.#bucketKey(key));
1487
+ if (!data)
1488
+ return;
1489
+ try {
1490
+ return JSON.parse(data);
1491
+ } catch {
1492
+ return;
1493
+ }
1494
+ }, () => this.#local.getBucket(key), true);
1495
+ }
1496
+ async updateBucket(key, state) {
1497
+ this.#local.updateBucket(key, state);
1498
+ const ttl = Math.ceil(state.resetAt - Date.now());
1499
+ await this.#run("updateBucket", async () => {
1500
+ if (ttl <= 0)
1501
+ await this.#client.del(this.#bucketKey(key));
1502
+ else
1503
+ await this.#client.set(this.#bucketKey(key), JSON.stringify(state), "PX", ttl);
1504
+ }, () => {
1505
+ return;
1506
+ }, false);
1507
+ }
1508
+ reserve;
1509
+ reconcile;
1510
+ extendGlobalReset;
1511
+ release;
1512
+ reservesGlobal;
1513
+ async#reserve(key, options = {}) {
1514
+ const keys = [this.#bucketKey(key)];
1515
+ if (options.global !== undefined) {
1516
+ keys.push(this.#globalKey(options.global));
1517
+ if (options.globalLimit)
1518
+ keys.push(`${this.#prefix}global-log:${options.global}`);
1519
+ }
1520
+ return this.#run("reserve", async () => {
1521
+ const result = await this.#client.eval(RESERVE_SCRIPT, keys.length, ...keys, options.probeMs ?? DEFAULT_PROBE_MS, PROBE_POLL_MS, BUCKET_LINGER_MS, options.globalLimit ?? 0, GLOBAL_WINDOW_MS);
1522
+ const reservation = parseReservation(result);
1523
+ if (reservation.granted && reservation.known && this.#local.getBucket(key))
1524
+ this.#local.reserve(key, { probeMs: options.probeMs });
1525
+ return reservation;
1526
+ }, () => this.#local.reserve(key, options), true);
1527
+ }
1528
+ async#reconcile(key, update) {
1529
+ this.#local.reconcile(key, update);
1530
+ const arg = (value) => value === undefined ? "" : String(value);
1531
+ await this.#run("reconcile", async () => {
1532
+ await this.#client.eval(RECONCILE_SCRIPT, 1, this.#bucketKey(key), arg(update.remaining), arg(update.resetAfterMs), arg(update.limit), arg(update.window), arg(update.retryAfterMs), BUCKET_LINGER_MS);
1533
+ }, () => {
1534
+ return;
1535
+ }, false);
1536
+ }
1537
+ async#extendGlobal(scope, waitMs) {
1538
+ this.#local.extendGlobalReset(scope, waitMs);
1539
+ await this.#run("extendGlobalReset", async () => {
1540
+ await this.#client.eval(EXTEND_GLOBAL_SCRIPT, 1, this.#globalKey(scope), Math.max(0, Math.ceil(waitMs)));
1541
+ }, () => {
1542
+ return;
1543
+ }, false);
1544
+ }
1545
+ async#release(key, grant, refund) {
1546
+ this.#local.release(key, grant, refund);
1547
+ if (grant.resetAt === undefined)
1548
+ return;
1549
+ await this.#run("release", async () => {
1550
+ await this.#client.eval(RELEASE_SCRIPT, 1, this.#bucketKey(key), grant.resetAt, refund ? "1" : "0");
1551
+ }, () => {
1552
+ return;
1553
+ }, false);
1554
+ }
1555
+ async getGlobalReset(scope = "") {
1556
+ return this.#run("getGlobalReset", async () => {
1557
+ const resetAt = await this.#client.get(this.#globalKey(scope));
1558
+ return resetAt ? Number(resetAt) || 0 : 0;
1559
+ }, () => this.#local.getGlobalReset(scope), true);
1560
+ }
1561
+ async setGlobalReset(resetAt, scope = "") {
1562
+ this.#local.setGlobalReset(resetAt, scope);
1563
+ const ttl = Math.ceil(resetAt - Date.now());
1564
+ await this.#run("setGlobalReset", async () => {
1565
+ if (ttl <= 0)
1566
+ await this.#client.del(this.#globalKey(scope));
1567
+ else
1568
+ await this.#client.set(this.#globalKey(scope), String(resetAt), "PX", ttl);
1569
+ }, () => {
1570
+ return;
1571
+ }, false);
1572
+ }
1573
+ }
1574
+ function parseReservation(result) {
1575
+ if (!Array.isArray(result) || result.length < 3)
1576
+ throw new Error("Unexpected reply from the reservation script.");
1577
+ const [granted, value, flag] = result;
1578
+ const n = Number(value);
1579
+ if (!Number.isFinite(n))
1580
+ throw new Error("Unexpected reply from the reservation script.");
1581
+ if (Number(granted) === 1)
1582
+ return { granted: true, known: Number(flag) === 1, resetAt: n };
1583
+ const reason = String(flag);
1584
+ return {
1585
+ granted: false,
1586
+ retryAfterMs: Math.max(0, n),
1587
+ reason: reason === "global" || reason === "probe" ? reason : "bucket"
1588
+ };
1589
+ }
1590
+ // packages/rest/src/webhook.ts
1591
+ class WebhookClient2 {
1592
+ id;
1593
+ token;
1594
+ #rest;
1595
+ constructor(options, restOptions) {
1596
+ if (options.url) {
1597
+ const match = options.url.match(/^https:\/\/(?:(?:ptb|canary)\.)?discord(?:app)?\.com\/api(?:\/v\d+)?\/webhooks\/(\d{1,20})\/([A-Za-z0-9_-]+)\/?(?:[?#].*)?$/);
1598
+ if (!match)
1599
+ throw new Error("Invalid Discord webhook URL format.");
1600
+ this.id = match[1];
1601
+ this.token = match[2];
1602
+ } else if (options.id && options.token) {
1603
+ this.id = options.id;
1604
+ this.token = options.token;
1605
+ } else {
1606
+ throw new Error("WebhookClient requires either a webhook 'url' or both 'id' and 'token'.");
1607
+ }
1608
+ this.#rest = new REST2(restOptions);
1609
+ }
1610
+ #prepare(options, base = {}) {
1611
+ const payload = typeof options === "string" ? { content: options } : options;
1612
+ const { thread_id, files, ...body } = payload;
1613
+ const params = new URLSearchParams(base);
1614
+ if (thread_id)
1615
+ params.set("thread_id", String(thread_id));
1616
+ const json = { ...body };
1617
+ if (body.embeds)
1618
+ json.embeds = body.embeds.map((e) => ("toJSON" in e) ? e.toJSON() : e);
1619
+ if (body.components)
1620
+ json.components = body.components.map((c) => ("toJSON" in c) ? c.toJSON() : c);
1621
+ const text = params.toString();
1622
+ return { query: text ? `?${text}` : "", json, files };
1623
+ }
1624
+ async send(options) {
1625
+ const { query, json, files } = this.#prepare(options, { wait: "true" });
1626
+ const path = `${Routes2.webhook(this.id, this.token)}${query}`;
1627
+ return files?.length ? this.#rest.postWithFiles(path, json, files) : this.#rest.post(path, json);
1628
+ }
1629
+ async fetchMessage(messageId, options = {}) {
1630
+ const query = options.threadId ? `?${new URLSearchParams({ thread_id: options.threadId })}` : "";
1631
+ return this.#rest.get(`${Routes2.webhookMessage(this.id, this.token, messageId)}${query}`);
1632
+ }
1633
+ async editMessage(messageId, options) {
1634
+ const { query, json, files } = this.#prepare(options);
1635
+ const path = `${Routes2.webhookMessage(this.id, this.token, messageId)}${query}`;
1636
+ return files?.length ? this.#rest.patchWithFiles(path, json, files) : this.#rest.patch(path, json);
1637
+ }
1638
+ async deleteMessage(messageId, options = {}) {
1639
+ const query = options.threadId ? `?${new URLSearchParams({ thread_id: options.threadId })}` : "";
1640
+ await this.#rest.delete(`${Routes2.webhookMessage(this.id, this.token, messageId)}${query}`);
1641
+ }
1642
+ }
1643
+
1644
+ // packages/rest/src/index.ts
1645
+ var LIBRARY_VERSION = package_default.version;
1646
+ var LIBRARY_URL = "https://github.com/Ekretos/lunibee";
1647
+ var USER_AGENT = `DiscordBot (${LIBRARY_URL}, ${LIBRARY_VERSION})`;
1648
+ function assertSafePath(path) {
1649
+ if (/[\u0000-\u0020\u007f#]/.test(path))
1650
+ throw new TypeError("REST paths must not contain control characters, spaces or '#'.");
1651
+ const end = path.indexOf("?");
1652
+ const pathname = end === -1 ? path : path.slice(0, end);
1653
+ if (pathname.includes("\\"))
1654
+ throw new TypeError("REST paths must not contain backslashes.");
1655
+ if (pathname.includes("//"))
1656
+ throw new TypeError("REST paths must not contain empty segments ('//').");
1657
+ for (const segment of pathname.split("/")) {
1658
+ let decoded;
1659
+ try {
1660
+ decoded = decodeURIComponent(segment);
1661
+ } catch {
1662
+ throw new TypeError("REST path has malformed percent-encoding.");
1663
+ }
1664
+ const bare = decoded.replace(/[\u0000-\u0020\u007f]/g, "");
1665
+ if (bare === "." || bare === ".." || /[\\/]/.test(decoded) && decoded.split(/[\\/]/).some((s) => {
1666
+ const part = s.replace(/[\u0000-\u0020\u007f]/g, "");
1667
+ return part === "." || part === "..";
1668
+ }))
1669
+ throw new TypeError("REST paths must not contain '.' or '..' segments.");
1670
+ }
1671
+ }
1672
+ var WRAPPER_KEYS = new Set([
1673
+ "body",
1674
+ "files",
1675
+ "query",
1676
+ "headers",
1677
+ "reason",
1678
+ "signal",
1679
+ "auth",
1680
+ "timeout"
1681
+ ]);
1682
+ function wrapperError(verb, body) {
1683
+ if (typeof body !== "object" || body === null || Array.isArray(body))
1684
+ return;
1685
+ const keys = Object.keys(body);
1686
+ if ((keys.includes("body") || keys.includes("files")) && keys.every((key) => WRAPPER_KEYS.has(key)))
1687
+ return new TypeError(`rest.${verb}(path, { body, \u2026 }) was removed in 0.4.0: call rest.${verb}(path, body, options). Run \`lunibee migrate\` to find the calls.`);
1688
+ return;
1689
+ }
1690
+ function createRetryPolicy2(maxRetries = 2) {
1691
+ if (!Number.isFinite(maxRetries) || maxRetries < 0)
1692
+ throw new TypeError("maxRetries must be a non-negative finite number.");
1693
+ return {
1694
+ maxRetries: Math.floor(maxRetries),
1695
+ shouldRetry(method, status) {
1696
+ const normalized = method.toUpperCase();
1697
+ const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(normalized);
1698
+ if (status === 0)
1699
+ return idempotent;
1700
+ return status === 429 || status >= 500 && status <= 599 && idempotent;
1701
+ },
1702
+ getDelay(attempt, retryAfter) {
1703
+ if (retryAfter !== undefined)
1704
+ return Math.max(0, retryAfter * 1000);
1705
+ return Math.min(1e4, 500 * 2 ** attempt) + Math.random() * 250;
1706
+ }
1707
+ };
1708
+ }
1709
+
1710
+ class QueueWaitExceeded extends Error {
1711
+ ms;
1712
+ constructor(ms) {
1713
+ super("maxQueueWait exceeded");
1714
+ this.ms = ms;
1715
+ }
1716
+ }
1717
+ function checkWait(ms) {
1718
+ if (ms === undefined)
1719
+ return;
1720
+ if (!(ms >= 0) || !Number.isFinite(ms))
1721
+ throw new RangeError("maxQueueWait must be a non-negative number.");
1722
+ return ms;
1723
+ }
1724
+
1725
+ class REST2 {
1726
+ #retryPolicy;
1727
+ #token;
1728
+ #fingerprint;
1729
+ #telemetry;
1730
+ #closing = new AbortController;
1731
+ #inFlight = 0;
1732
+ #idle;
1733
+ #rejected;
1734
+ #stopOnInvalidToken;
1735
+ #maxQueueWait;
1736
+ #invalid = new Array(10).fill(0);
1737
+ #invalidMinute = 0;
1738
+ #drained;
1739
+ #limiter;
1740
+ #scheduler;
1741
+ #transport;
1742
+ #decoder = new ResponseDecoder2;
1743
+ #hooks = {};
1744
+ #concurrentBuckets;
1745
+ constructor(options = {}) {
1746
+ this.#token = options.token;
1747
+ this.#fingerprint = options.token ? tokenFingerprint2(options.token) : undefined;
1748
+ this.#telemetry = options.telemetry;
1749
+ this.#scheduler = new RequestScheduler2({
1750
+ maxQueued: options.maxQueued,
1751
+ maxQueuedPerBucket: options.maxQueuedPerBucket
1752
+ });
1753
+ this.#retryPolicy = options.retryPolicy ?? createRetryPolicy2(options.retries ?? 2);
1754
+ this.#transport = options.transport ?? new HttpTransport2({
1755
+ baseURL: options.baseURL,
1756
+ allowInsecureBaseURL: options.allowInsecureBaseURL,
1757
+ timeout: options.timeout
1758
+ });
1759
+ const store = options.store ?? new MemoryRateLimitStore2;
1760
+ this.#concurrentBuckets = (options.concurrentBuckets ?? true) && typeof store.reserve === "function";
1761
+ this.#limiter = new RateLimiter2(store, {
1762
+ concurrent: this.#concurrentBuckets,
1763
+ maxBackoff: options.maxBackoff,
1764
+ probeMs: this.#transport.timeout > 0 ? this.#transport.timeout + 1000 : undefined,
1765
+ telemetry: options.telemetry,
1766
+ globalLimit: options.globalLimit === false ? undefined : options.globalLimit ?? 50
1767
+ });
1768
+ this.#stopOnInvalidToken = options.stopOnInvalidToken ?? true;
1769
+ this.#maxQueueWait = checkWait(options.maxQueueWait);
1770
+ if (options.hooks)
1771
+ this.#hooks = options.hooks;
1772
+ }
1773
+ get store() {
1774
+ return this.#limiter.store;
1775
+ }
1776
+ setHooks(hooks) {
1777
+ this.#hooks = hooks;
1778
+ return this;
1779
+ }
1780
+ setToken(token) {
1781
+ if (!token.trim())
1782
+ throw new TypeError("A Discord bot token is required.");
1783
+ this.#token = token;
1784
+ this.#fingerprint = tokenFingerprint2(token);
1785
+ this.#rejected = undefined;
1786
+ }
1787
+ diagnostics() {
1788
+ const store = this.#limiter.store;
1789
+ return {
1790
+ mode: store.mode ?? (store instanceof MemoryRateLimitStore2 ? "local" : "distributed"),
1791
+ queued: this.#scheduler.queued,
1792
+ inFlight: this.#inFlight,
1793
+ buckets: this.#scheduler.size,
1794
+ closed: this.#closing.signal.aborted,
1795
+ invalidRequests: this.#invalidCount(0),
1796
+ tokenRejected: this.#rejected !== undefined && this.#rejected === this.#fingerprint
1797
+ };
1798
+ }
1799
+ async close() {
1800
+ if (!this.#closing.signal.aborted)
1801
+ this.#closing.abort(new Error("The REST client was closed."));
1802
+ if (this.#inFlight === 0)
1803
+ return;
1804
+ this.#drained ??= new Promise((resolve) => {
1805
+ this.#idle = resolve;
1806
+ });
1807
+ await this.#drained;
1808
+ }
1809
+ #invalidCount(add) {
1810
+ const minute = Math.floor(Date.now() / 60000);
1811
+ const elapsed = Math.min(10, minute - this.#invalidMinute);
1812
+ for (let i = 1;i <= elapsed; i++)
1813
+ this.#invalid[(this.#invalidMinute + i) % 10] = 0;
1814
+ if (elapsed > 0)
1815
+ this.#invalidMinute = minute;
1816
+ this.#invalid[minute % 10] += add;
1817
+ return this.#invalid.reduce((a, b) => a + b, 0);
1818
+ }
1819
+ #queueChanged() {
1820
+ emitTelemetry(this.#telemetry, {
1821
+ type: "queue",
1822
+ queued: this.#scheduler.queued,
1823
+ inFlight: this.#inFlight
1824
+ });
1825
+ }
1826
+ async request(method, path, body, options = {}) {
1827
+ if (typeof method !== "string" || !method.toUpperCase())
1828
+ throw new TypeError("REST method is required.");
1829
+ if (typeof path !== "string" || !path.startsWith("/"))
1830
+ throw new TypeError("REST paths must start with '/'.");
1831
+ assertSafePath(path);
1832
+ const route = createRouteKey2(method, path);
1833
+ const files = options.files;
1834
+ if (files && files.length > 0 && !(body instanceof FormData))
1835
+ body = this.#fileForm(body, await Promise.all(files.map(async (file) => ({
1836
+ ...file,
1837
+ data: await file.data
1838
+ }))));
1839
+ const query = serializeQuery2(options.query);
1840
+ const requestPath = query ? `${path}${query}` : path;
1841
+ if (this.#closing.signal.aborted)
1842
+ throw abortError2(path, this.#closing.signal.reason);
1843
+ const identity = requestIdentity2(path, options.auth !== false && this.#token ? this.#fingerprint : undefined);
1844
+ if (identity.bucket === this.#fingerprint && this.#rejected === this.#fingerprint)
1845
+ throw new RESTError2("Discord rejected this bot token (401); request not sent. Call setToken() with a valid token.", 401, undefined, undefined, { method: route.method, path, kind: "authentication" });
1846
+ const queueWait = checkWait(options.maxQueueWait) ?? this.#maxQueueWait;
1847
+ let deadline;
1848
+ const sources = [this.#closing.signal];
1849
+ if (options.signal)
1850
+ sources.push(options.signal);
1851
+ if (queueWait !== undefined) {
1852
+ const expired = new AbortController;
1853
+ deadline = setTimeout(() => expired.abort(new QueueWaitExceeded(queueWait)), queueWait);
1854
+ sources.push(expired.signal);
1855
+ }
1856
+ const waitSignal = sources.length === 1 ? sources[0] : AbortSignal.any(sources);
1857
+ const state = { attempts: 0, status: 0 };
1858
+ const started = performance.now();
1859
+ this.#inFlight++;
1860
+ this.#queueChanged();
1861
+ try {
1862
+ return await this.#scheduler.runResolved(() => this.#limiter.resolveBucketKey(route.route, route.major, identity.bucket), waitSignal, path, (bucketKey, release) => {
1863
+ this.#queueChanged();
1864
+ return this.#attempts(route, requestPath, body, options, { identity, waitSignal, state }, bucketKey, release);
1865
+ });
1866
+ } catch (caught) {
1867
+ let error = caught;
1868
+ if (error instanceof RESTError2 && error.kind === "aborted" && error.cause instanceof QueueWaitExceeded)
1869
+ error = new RESTError2(`The request waited longer than maxQueueWait (${error.cause.ms}ms) before it could be sent.`, 0, undefined, undefined, { method: route.method, path, kind: "overloaded" });
1870
+ if (error instanceof RESTError2) {
1871
+ if (error.kind === "aborted" && state.attempts === 0)
1872
+ emitTelemetry(this.#telemetry, {
1873
+ type: "cancelled",
1874
+ stage: "queued"
1875
+ });
1876
+ else if (error.kind === "overloaded")
1877
+ emitTelemetry(this.#telemetry, { type: "overloaded" });
1878
+ if (state.status === 0)
1879
+ state.status = error.status;
1880
+ }
1881
+ throw error;
1882
+ } finally {
1883
+ clearTimeout(deadline);
1884
+ emitTelemetry(this.#telemetry, {
1885
+ type: "request",
1886
+ method: route.method,
1887
+ route: route.route,
1888
+ status: state.status,
1889
+ attempts: state.attempts,
1890
+ durationMs: performance.now() - started,
1891
+ ok: state.status >= 200 && state.status < 300
1892
+ });
1893
+ if (--this.#inFlight === 0 && this.#idle) {
1894
+ this.#idle();
1895
+ this.#idle = undefined;
1896
+ this.#drained = undefined;
1897
+ }
1898
+ this.#queueChanged();
1899
+ }
1900
+ }
1901
+ async#attempts(route, requestPath, body, options, context, initialBucketKey, release) {
1902
+ const { method, path } = route;
1903
+ const { identity, waitSignal, state } = context;
1904
+ const label = route.route;
1905
+ let bucketKey = initialBucketKey;
1906
+ for (let attempt = 0;attempt <= this.#retryPolicy.maxRetries; attempt++) {
1907
+ let grant;
1908
+ try {
1909
+ grant = await this.#limiter.acquire(bucketKey, waitSignal, path, {
1910
+ global: identity.global,
1911
+ resolve: () => this.#limiter.resolveBucketKey(route.route, route.major, identity.bucket)
1912
+ });
1913
+ if (grant.key)
1914
+ bucketKey = grant.key;
1915
+ } catch (error) {
1916
+ if (error instanceof RESTError2 && error.kind === "aborted")
1917
+ emitTelemetry(this.#telemetry, {
1918
+ type: "cancelled",
1919
+ stage: "waiting"
1920
+ });
1921
+ throw error;
1922
+ }
1923
+ if (this.#concurrentBuckets && grant.known)
1924
+ release();
1925
+ if (waitSignal.aborted) {
1926
+ await this.#limiter.releaseGrant(bucketKey, grant, true);
1927
+ emitTelemetry(this.#telemetry, {
1928
+ type: "cancelled",
1929
+ stage: "waiting"
1930
+ });
1931
+ throw abortError2(path, waitSignal.reason);
1932
+ }
1933
+ this.#emit(this.#hooks.onRequest, { method, path, attempt });
1934
+ state.attempts = attempt + 1;
1935
+ const requestStart = Date.now();
1936
+ let response;
1937
+ try {
1938
+ response = await this.#transport.send({
1939
+ method,
1940
+ path: requestPath,
1941
+ headers: this.#headers(body, options),
1942
+ body: this.#encodeBody(body),
1943
+ signal: options.signal
1944
+ });
1945
+ } catch (error) {
1946
+ const notSent = error instanceof RESTError2 && error.kind === "aborted";
1947
+ await this.#limiter.releaseGrant(bucketKey, grant, notSent);
1948
+ if (error instanceof RESTError2) {
1949
+ if (error.kind === "aborted")
1950
+ emitTelemetry(this.#telemetry, {
1951
+ type: "cancelled",
1952
+ stage: "transport"
1953
+ });
1954
+ throw error;
1955
+ }
1956
+ if (options.signal?.aborted) {
1957
+ emitTelemetry(this.#telemetry, {
1958
+ type: "cancelled",
1959
+ stage: "transport"
1960
+ });
1961
+ throw abortError2(path, options.signal.reason);
1962
+ }
1963
+ const failure = error instanceof TransportError2 ? error : new TransportError2("Discord REST request failed", false, sanitizeCause(error));
1964
+ emitTelemetry(this.#telemetry, {
1965
+ type: "networkError",
1966
+ timedOut: failure.timedOut
1967
+ });
1968
+ if (!failure.timedOut && attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, 0)) {
1969
+ const delay = this.#retryPolicy.getDelay(attempt);
1970
+ this.#emit(this.#hooks.onRetry, {
1971
+ method,
1972
+ path,
1973
+ attempt,
1974
+ status: 0,
1975
+ delayMs: delay
1976
+ });
1977
+ emitTelemetry(this.#telemetry, {
1978
+ type: "retry",
1979
+ route: label,
1980
+ reason: "network",
1981
+ attempt
1982
+ });
1983
+ await this.#retrySleep(delay, waitSignal, path);
1984
+ continue;
1985
+ }
1986
+ throw new RESTError2(failure.message, 0, undefined, undefined, {
1987
+ method,
1988
+ path,
1989
+ cause: failure.cause ?? failure,
1990
+ kind: failure.timedOut ? "timeout" : "network"
1991
+ });
1992
+ }
1993
+ state.status = response.status;
1994
+ if (response.status === 401 || response.status === 403 || response.status === 429 && response.headers.get("X-RateLimit-Scope") !== "shared")
1995
+ this.#invalidCount(1);
1996
+ if (response.status === 401 && this.#stopOnInvalidToken && identity.bucket === this.#fingerprint) {
1997
+ this.#rejected = this.#fingerprint;
1998
+ emitTelemetry(this.#telemetry, { type: "tokenRejected" });
1999
+ }
2000
+ const durationMs = Date.now() - requestStart;
2001
+ this.#emit(this.#hooks.onResponse, {
2002
+ method,
2003
+ path,
2004
+ status: response.status,
2005
+ durationMs
2006
+ });
2007
+ emitTelemetry(this.#telemetry, {
2008
+ type: "response",
2009
+ method,
2010
+ route: label,
2011
+ status: response.status,
2012
+ durationMs
2013
+ });
2014
+ const payload = await this.#decoder.read(response);
2015
+ const data = response.ok ? {} : this.#decoder.errorData(payload);
2016
+ bucketKey = await this.#limiter.applyResponse(response, bucketKey, route.route, route.major, {
2017
+ identity: identity.bucket,
2018
+ global: identity.global,
2019
+ body: data,
2020
+ grant
2021
+ });
2022
+ if (response.ok)
2023
+ return response.status === 204 ? undefined : payload;
2024
+ const limited = response.status === 429;
2025
+ const retryAfterMs = limited ? this.#limiter.retryAfterMs(response, data) : undefined;
2026
+ if (limited) {
2027
+ const global = data.global === true || response.headers.get("X-RateLimit-Global") === "true";
2028
+ const scope = response.headers.get("X-RateLimit-Scope")?.toLowerCase();
2029
+ emitTelemetry(this.#telemetry, {
2030
+ type: "rateLimited",
2031
+ route: label,
2032
+ scope: global ? "global" : scope === "user" || scope === "shared" ? scope : "unknown",
2033
+ retryAfterMs
2034
+ });
2035
+ this.#emit(this.#hooks.onRateLimit, {
2036
+ method,
2037
+ path,
2038
+ retryAfterMs,
2039
+ global,
2040
+ bucket: response.headers.get("X-RateLimit-Bucket") ?? await this.#limiter.store.getBucketHash(route.route)
2041
+ });
2042
+ }
2043
+ if (attempt < this.#retryPolicy.maxRetries && this.#retryPolicy.shouldRetry(method, response.status)) {
2044
+ const delay = this.#retryPolicy.getDelay(attempt, limited ? retryAfterMs / 1000 : undefined);
2045
+ if (delay > this.#limiter.maxBackoff)
2046
+ throw new RESTError2(`Discord asked to wait ${Math.ceil(delay / 1000)}s before retrying, longer than maxBackoff (${Math.ceil(this.#limiter.maxBackoff / 1000)}s).`, response.status, data.code, payload, { method, path });
2047
+ this.#emit(this.#hooks.onRetry, {
2048
+ method,
2049
+ path,
2050
+ attempt,
2051
+ status: response.status,
2052
+ delayMs: delay
2053
+ });
2054
+ emitTelemetry(this.#telemetry, {
2055
+ type: "retry",
2056
+ route: label,
2057
+ reason: limited ? "rateLimited" : "server",
2058
+ attempt
2059
+ });
2060
+ await this.#retrySleep(limited ? delay - retryAfterMs : delay, waitSignal, path);
2061
+ continue;
2062
+ }
2063
+ throw new RESTError2(data.message ?? response.statusText ?? `Discord REST request failed with status ${response.status}`, response.status, data.code, payload, { method, path });
2064
+ }
2065
+ throw new RESTError2("Discord REST request exhausted its retry attempts", 0, undefined, undefined, { method, path });
2066
+ }
2067
+ async#retrySleep(delay, signal, path) {
2068
+ try {
2069
+ await sleep(delay, signal, path);
2070
+ } catch (error) {
2071
+ emitTelemetry(this.#telemetry, {
2072
+ type: "cancelled",
2073
+ stage: "retry"
2074
+ });
2075
+ throw error;
2076
+ }
2077
+ }
2078
+ #headers(body, options) {
2079
+ const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
2080
+ const headers = { ...options.headers };
2081
+ const set = (name, value) => {
2082
+ for (const key of Object.keys(headers))
2083
+ if (key.toLowerCase() === name.toLowerCase())
2084
+ delete headers[key];
2085
+ headers[name] = value;
2086
+ };
2087
+ if (options.auth !== false && this.#token)
2088
+ set("Authorization", `Bot ${this.#token}`);
2089
+ set("User-Agent", USER_AGENT);
2090
+ if (options.reason !== undefined)
2091
+ set("X-Audit-Log-Reason", encodeURIComponent(options.reason));
2092
+ if (!isFormData)
2093
+ set("Content-Type", "application/json");
2094
+ return headers;
2095
+ }
2096
+ #encodeBody(body) {
2097
+ if (typeof FormData !== "undefined" && body instanceof FormData)
2098
+ return body;
2099
+ return body === undefined ? undefined : JSON.stringify(body);
2100
+ }
2101
+ get(path, options) {
2102
+ return this.request("GET", path, options?.body, options);
2103
+ }
2104
+ post(path, body, options) {
2105
+ const removed = wrapperError("post", body);
2106
+ if (removed)
2107
+ return Promise.reject(removed);
2108
+ return this.request("POST", path, body, options);
2109
+ }
2110
+ patch(path, body, options) {
2111
+ const removed = wrapperError("patch", body);
2112
+ if (removed)
2113
+ return Promise.reject(removed);
2114
+ return this.request("PATCH", path, body, options);
2115
+ }
2116
+ put(path, body, options) {
2117
+ const removed = wrapperError("put", body);
2118
+ if (removed)
2119
+ return Promise.reject(removed);
2120
+ return this.request("PUT", path, body, options);
2121
+ }
2122
+ delete(path, options) {
2123
+ return this.request("DELETE", path, options?.body, options);
2124
+ }
2125
+ #fileForm(payload, files) {
2126
+ const form = new FormData;
2127
+ form.append("payload_json", JSON.stringify(payload));
2128
+ for (let i = 0;i < files.length; i++) {
2129
+ const file = files[i];
2130
+ const blob = file.data instanceof Blob ? file.data : new Blob([file.data], {
2131
+ type: file.contentType ?? "application/octet-stream"
2132
+ });
2133
+ form.append(`files[${i}]`, blob, file.name.replace(/[\r\n"\\]/g, "_"));
2134
+ }
2135
+ return form;
2136
+ }
2137
+ postWithFiles(path, payload, files, options) {
2138
+ return this.request("POST", path, payload, { ...options, files });
2139
+ }
2140
+ patchWithFiles(path, payload, files, options) {
2141
+ return this.request("PATCH", path, payload, { ...options, files });
2142
+ }
2143
+ #emit(hook, ctx) {
2144
+ if (!hook)
2145
+ return;
2146
+ const withPath = ctx;
2147
+ if (typeof withPath.path === "string")
2148
+ ctx = { ...ctx, path: redactPath2(withPath.path) };
2149
+ Promise.resolve().then(() => hook(ctx)).catch(() => {});
2150
+ }
2151
+ }
2152
+
2153
+ export { MemoryRateLimitStore2, redactPath2, RESTError2, abortError2, normalizeRoutePath2, majorParameter2, scopeBucket2, createRouteKey2, serializeQuery2, RequestScheduler2, headerNumber2, parseRateLimitHeaders2, RESTMetrics2, DEFAULT_MAX_BACKOFF_MS2, RateLimiter2, tokenFingerprint2, requestIdentity2, TransportError2, HttpTransport2, ResponseDecoder2, Routes2, RESTErrorCode2, fromBunRedis2, RedisRateLimitStore2, WebhookClient2, createRetryPolicy2, REST2 };
2154
+
2155
+ //# debugId=D0A76CBA183E199B64756E2164756E21