@discordkit/core 3.2.0 → 4.0.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 (178) hide show
  1. package/CHANGELOG.md +416 -0
  2. package/README.md +52 -0
  3. package/dist/index.d.mts +23 -0
  4. package/dist/index.mjs +23 -0
  5. package/dist/requests/DiscordSession.d.mts +30 -0
  6. package/dist/requests/DiscordSession.mjs +202 -0
  7. package/dist/requests/addParams.d.mts +15 -0
  8. package/dist/requests/addParams.mjs +24 -0
  9. package/dist/requests/buildURL.d.mts +7 -0
  10. package/dist/requests/buildURL.mjs +7 -0
  11. package/dist/requests/getAsset.d.mts +7 -0
  12. package/dist/requests/getAsset.mjs +6 -0
  13. package/dist/requests/index.d.mts +9 -0
  14. package/dist/requests/index.mjs +9 -0
  15. package/dist/requests/methods.d.mts +63 -0
  16. package/dist/requests/methods.mjs +10 -0
  17. package/dist/requests/request.d.mts +27 -0
  18. package/dist/requests/request.mjs +29 -0
  19. package/dist/requests/toProcedure.d.mts +40 -0
  20. package/dist/requests/toProcedure.mjs +27 -0
  21. package/dist/requests/toQuery.d.mts +36 -0
  22. package/dist/requests/toQuery.mjs +17 -0
  23. package/dist/requests/toValidated.d.mts +16 -0
  24. package/dist/requests/toValidated.mjs +25 -0
  25. package/dist/requests/{verifyKey.d.ts → verifyKey.d.mts} +4 -1
  26. package/dist/requests/verifyKey.mjs +63 -0
  27. package/dist/utils/isBetween.d.mts +4 -0
  28. package/dist/utils/isBetween.mjs +4 -0
  29. package/dist/utils/{isNonNullable.js → isNonNullable.d.mts} +5 -2
  30. package/dist/utils/isNonNullable.mjs +22 -0
  31. package/dist/utils/isNumericString.d.mts +4 -0
  32. package/dist/utils/isNumericString.mjs +4 -0
  33. package/dist/utils/isObject.d.mts +4 -0
  34. package/dist/utils/isObject.mjs +4 -0
  35. package/dist/utils/sleep.d.mts +7 -0
  36. package/dist/utils/sleep.mjs +7 -0
  37. package/dist/utils/toCamelCase.d.mts +4 -0
  38. package/dist/utils/toCamelCase.mjs +4 -0
  39. package/dist/utils/toCamelKeys.d.mts +6 -0
  40. package/dist/utils/toCamelKeys.mjs +13 -0
  41. package/dist/utils/toSnakeCase.d.mts +4 -0
  42. package/dist/utils/toSnakeCase.mjs +4 -0
  43. package/dist/utils/toSnakeKeys.d.mts +6 -0
  44. package/dist/utils/toSnakeKeys.mjs +13 -0
  45. package/dist/validations/asDigits.d.mts +12 -0
  46. package/dist/validations/asDigits.mjs +10 -0
  47. package/dist/validations/asInteger.d.mts +12 -0
  48. package/dist/validations/asInteger.mjs +10 -0
  49. package/dist/validations/bitfield.d.mts +23 -0
  50. package/dist/validations/bitfield.mjs +20 -0
  51. package/dist/validations/boundedArray.d.mts +13 -0
  52. package/dist/validations/boundedArray.mjs +9 -0
  53. package/dist/validations/boundedInteger.d.mts +13 -0
  54. package/dist/validations/boundedInteger.mjs +9 -0
  55. package/dist/validations/boundedString.d.mts +14 -0
  56. package/dist/validations/boundedString.mjs +10 -0
  57. package/dist/validations/datauri.d.mts +24 -0
  58. package/dist/validations/datauri.mjs +17 -0
  59. package/dist/validations/fileUpload.d.mts +129 -0
  60. package/dist/validations/fileUpload.mjs +114 -0
  61. package/dist/validations/hasMimeType.d.mts +16 -0
  62. package/dist/validations/hasMimeType.mjs +16 -0
  63. package/dist/validations/hasSize.d.mts +10 -0
  64. package/dist/validations/hasSize.mjs +12 -0
  65. package/dist/validations/index.d.mts +15 -0
  66. package/dist/validations/index.mjs +15 -0
  67. package/dist/validations/schema.d.mts +102 -0
  68. package/dist/validations/schema.mjs +109 -0
  69. package/dist/validations/{snowflake.d.ts → snowflake.d.mts} +9 -7
  70. package/dist/validations/snowflake.mjs +28 -0
  71. package/dist/validations/{timestamp.d.ts → timestamp.d.mts} +5 -1
  72. package/dist/validations/timestamp.mjs +6 -0
  73. package/dist/validations/toBlob.d.mts +9 -0
  74. package/dist/validations/toBlob.mjs +17 -0
  75. package/dist/validations/url.d.mts +6 -0
  76. package/dist/validations/url.mjs +5 -0
  77. package/package.json +13 -23
  78. package/dist/index.d.ts +0 -2
  79. package/dist/index.js +0 -3
  80. package/dist/index.js.map +0 -1
  81. package/dist/requests/DiscordSession.d.ts +0 -25
  82. package/dist/requests/DiscordSession.js +0 -255
  83. package/dist/requests/DiscordSession.js.map +0 -1
  84. package/dist/requests/addParams.d.ts +0 -2
  85. package/dist/requests/addParams.js +0 -11
  86. package/dist/requests/addParams.js.map +0 -1
  87. package/dist/requests/buildURL.d.ts +0 -2
  88. package/dist/requests/buildURL.js +0 -4
  89. package/dist/requests/buildURL.js.map +0 -1
  90. package/dist/requests/getAsset.d.ts +0 -2
  91. package/dist/requests/getAsset.js +0 -3
  92. package/dist/requests/getAsset.js.map +0 -1
  93. package/dist/requests/index.d.ts +0 -8
  94. package/dist/requests/index.js +0 -9
  95. package/dist/requests/index.js.map +0 -1
  96. package/dist/requests/methods.d.ts +0 -13
  97. package/dist/requests/methods.js +0 -8
  98. package/dist/requests/methods.js.map +0 -1
  99. package/dist/requests/request.d.ts +0 -2
  100. package/dist/requests/request.js +0 -30
  101. package/dist/requests/request.js.map +0 -1
  102. package/dist/requests/toProcedure.d.ts +0 -31
  103. package/dist/requests/toProcedure.js +0 -36
  104. package/dist/requests/toProcedure.js.map +0 -1
  105. package/dist/requests/toQuery.d.ts +0 -28
  106. package/dist/requests/toQuery.js +0 -9
  107. package/dist/requests/toQuery.js.map +0 -1
  108. package/dist/requests/toValidated.d.ts +0 -13
  109. package/dist/requests/toValidated.js +0 -29
  110. package/dist/requests/toValidated.js.map +0 -1
  111. package/dist/requests/verifyKey.js +0 -91
  112. package/dist/requests/verifyKey.js.map +0 -1
  113. package/dist/utils/isBetween.d.ts +0 -1
  114. package/dist/utils/isBetween.js +0 -2
  115. package/dist/utils/isBetween.js.map +0 -1
  116. package/dist/utils/isNonNullable.d.ts +0 -20
  117. package/dist/utils/isNonNullable.js.map +0 -1
  118. package/dist/utils/isNumericString.d.ts +0 -1
  119. package/dist/utils/isNumericString.js +0 -2
  120. package/dist/utils/isNumericString.js.map +0 -1
  121. package/dist/utils/isObject.d.ts +0 -1
  122. package/dist/utils/isObject.js +0 -2
  123. package/dist/utils/isObject.js.map +0 -1
  124. package/dist/utils/sleep.d.ts +0 -4
  125. package/dist/utils/sleep.js +0 -5
  126. package/dist/utils/sleep.js.map +0 -1
  127. package/dist/utils/toCamelCase.d.ts +0 -1
  128. package/dist/utils/toCamelCase.js +0 -2
  129. package/dist/utils/toCamelCase.js.map +0 -1
  130. package/dist/utils/toCamelKeys.d.ts +0 -2
  131. package/dist/utils/toCamelKeys.js +0 -16
  132. package/dist/utils/toCamelKeys.js.map +0 -1
  133. package/dist/utils/toSnakeCase.d.ts +0 -1
  134. package/dist/utils/toSnakeCase.js +0 -4
  135. package/dist/utils/toSnakeCase.js.map +0 -1
  136. package/dist/utils/toSnakeKeys.d.ts +0 -2
  137. package/dist/utils/toSnakeKeys.js +0 -16
  138. package/dist/utils/toSnakeKeys.js.map +0 -1
  139. package/dist/validations/asDigits.d.ts +0 -6
  140. package/dist/validations/asDigits.js +0 -6
  141. package/dist/validations/asDigits.js.map +0 -1
  142. package/dist/validations/asInteger.d.ts +0 -6
  143. package/dist/validations/asInteger.js +0 -6
  144. package/dist/validations/asInteger.js.map +0 -1
  145. package/dist/validations/bitfield.d.ts +0 -17
  146. package/dist/validations/bitfield.js +0 -37
  147. package/dist/validations/bitfield.js.map +0 -1
  148. package/dist/validations/boundedArray.d.ts +0 -6
  149. package/dist/validations/boundedArray.js +0 -8
  150. package/dist/validations/boundedArray.js.map +0 -1
  151. package/dist/validations/boundedInteger.d.ts +0 -6
  152. package/dist/validations/boundedInteger.js +0 -8
  153. package/dist/validations/boundedInteger.js.map +0 -1
  154. package/dist/validations/boundedString.d.ts +0 -6
  155. package/dist/validations/boundedString.js +0 -8
  156. package/dist/validations/boundedString.js.map +0 -1
  157. package/dist/validations/datauri.d.ts +0 -20
  158. package/dist/validations/datauri.js +0 -20
  159. package/dist/validations/datauri.js.map +0 -1
  160. package/dist/validations/hasMimeType.d.ts +0 -10
  161. package/dist/validations/hasMimeType.js +0 -18
  162. package/dist/validations/hasMimeType.js.map +0 -1
  163. package/dist/validations/hasSize.d.ts +0 -5
  164. package/dist/validations/hasSize.js +0 -13
  165. package/dist/validations/hasSize.js.map +0 -1
  166. package/dist/validations/index.d.ts +0 -12
  167. package/dist/validations/index.js +0 -13
  168. package/dist/validations/index.js.map +0 -1
  169. package/dist/validations/snowflake.js +0 -39
  170. package/dist/validations/snowflake.js.map +0 -1
  171. package/dist/validations/timestamp.js +0 -4
  172. package/dist/validations/timestamp.js.map +0 -1
  173. package/dist/validations/toBlob.d.ts +0 -4
  174. package/dist/validations/toBlob.js +0 -19
  175. package/dist/validations/toBlob.js.map +0 -1
  176. package/dist/validations/url.d.ts +0 -2
  177. package/dist/validations/url.js +0 -3
  178. package/dist/validations/url.js.map +0 -1
@@ -0,0 +1,202 @@
1
+ import { sleep } from "../utils/sleep.mjs";
2
+ //#region src/requests/DiscordSession.ts
3
+ const endpoint = `https://discord.com/api/v10/`;
4
+ /** @internal */
5
+ var DiscordSession = class {
6
+ endpoint = endpoint;
7
+ maxRetries = 5;
8
+ #authToken = null;
9
+ #buckets = /* @__PURE__ */ new Map();
10
+ #globalReset = 0;
11
+ #requestQueue = [];
12
+ #processingQueue = false;
13
+ #globalLimit = 50;
14
+ #globalWindow = 1e3;
15
+ #globalRequestTimestamps = [];
16
+ #invalidRequests = {
17
+ count: 0,
18
+ windowStart: Date.now()
19
+ };
20
+ #invalidRequestLimit = 1e4;
21
+ #invalidRequestWindow = 600 * 1e3;
22
+ get ready() {
23
+ return Boolean(this.#authToken);
24
+ }
25
+ constructor(authToken) {
26
+ if (authToken) this.setToken(authToken);
27
+ }
28
+ /**
29
+ * Clears the current session details, then attempts to set
30
+ * new session values
31
+ */
32
+ setToken = (token) => {
33
+ this.#authToken = null;
34
+ if (token.length === 0) throw new Error(`Must provide a non-empty string to set Auth Token`);
35
+ if (!token.startsWith(`Bot `) && !token.startsWith(`Bearer `)) throw new Error(`Token must begin with either "Bot " or "Bearer ", received: ${token}`);
36
+ this.#authToken = token;
37
+ return this;
38
+ };
39
+ clearSession = () => {
40
+ this.#authToken = null;
41
+ this.#buckets.clear();
42
+ this.#globalReset = 0;
43
+ this.#requestQueue = [];
44
+ this.#globalRequestTimestamps = [];
45
+ this.#invalidRequests = {
46
+ count: 0,
47
+ windowStart: Date.now()
48
+ };
49
+ return this;
50
+ };
51
+ getSession = () => {
52
+ const token = this.#authToken;
53
+ if (!token) throw new Error(`Auth Token must be set before requests can be made.`);
54
+ return token;
55
+ };
56
+ /**
57
+ * Queue a request to be processed with rate limiting
58
+ */
59
+ queueRequest = async (resource, method, body, options) => {
60
+ return new Promise((resolve, reject) => {
61
+ this.#requestQueue.push({
62
+ resource,
63
+ method,
64
+ body,
65
+ options,
66
+ resolve,
67
+ reject
68
+ });
69
+ this.#processQueue();
70
+ });
71
+ };
72
+ /**
73
+ * Process the request queue with rate limiting
74
+ */
75
+ #processQueue = async () => {
76
+ if (this.#processingQueue) return;
77
+ this.#processingQueue = true;
78
+ while (this.#requestQueue.length > 0) {
79
+ const now = Date.now();
80
+ if (this.#isTemporarilyBanned()) {
81
+ const waitTime = this.#invalidRequestWindow - (now - this.#invalidRequests.windowStart);
82
+ console.warn(`Temporarily banned from Discord API. Waiting ${Math.ceil(waitTime / 1e3)}s`);
83
+ await sleep(waitTime);
84
+ this.#resetInvalidRequestCounter();
85
+ }
86
+ await this.#enforceGlobalRateLimit();
87
+ if (this.#globalReset > now / 1e3) {
88
+ await sleep((this.#globalReset - now / 1e3) * 1e3);
89
+ continue;
90
+ }
91
+ const request = this.#requestQueue[0];
92
+ if (typeof request === `undefined`) break;
93
+ const bucket = this.#buckets.get(`${request.method}:${request.resource}`);
94
+ if (bucket?.remaining === 0) {
95
+ const resetTime = bucket.reset * 1e3;
96
+ if (resetTime > now) await sleep(resetTime - now);
97
+ }
98
+ this.#requestQueue.shift();
99
+ try {
100
+ const response = await this.#executeRequest(request);
101
+ request.resolve(response);
102
+ } catch (error) {
103
+ if (error instanceof Error) request.reject(error);
104
+ }
105
+ }
106
+ this.#processingQueue = false;
107
+ if (this.#requestQueue.length > 0) this.#processQueue();
108
+ };
109
+ /**
110
+ * Execute a single request and handle rate limit headers
111
+ */
112
+ #executeRequest = async (request, retryCount = 0) => {
113
+ const now = Date.now();
114
+ this.#globalRequestTimestamps.push(now);
115
+ const headers = {};
116
+ if (!request.options?.anonymous) headers.Authorization = this.getSession();
117
+ if (request.options?.reason) headers[`X-Audit-Log-Reason`] = encodeURIComponent(request.options.reason);
118
+ if (request.body && !(request.body instanceof FormData)) headers[`Content-Type`] = `application/json`;
119
+ const response = await fetch(request.resource.toString(), {
120
+ method: request.method,
121
+ body: request.body,
122
+ headers
123
+ });
124
+ this.#updateRateLimits(response);
125
+ if (response.status === 429) {
126
+ if (retryCount >= this.maxRetries) {
127
+ console.error(`Max retries (${this.maxRetries}) exceeded for ${request.resource}`);
128
+ return response;
129
+ }
130
+ const retryAfter = response.headers.get(`Retry-After`) ?? `1`;
131
+ const isGlobal = response.headers.get(`X-RateLimit-Global`) === `true`;
132
+ const scope = response.headers.get(`X-RateLimit-Scope`);
133
+ if (isGlobal || scope === `global`) {
134
+ console.warn(`Hit global rate limit`);
135
+ this.#globalReset = now / 1e3 + parseFloat(retryAfter);
136
+ }
137
+ await sleep(parseFloat(retryAfter) * 1e3);
138
+ return this.#executeRequest(request, retryCount + 1);
139
+ }
140
+ if ([401, 403].includes(response.status) || response.status === 429 && response.headers.get(`X-RateLimit-Scope`) !== `shared`) this.#trackInvalidRequest();
141
+ return response;
142
+ };
143
+ /**
144
+ * Update rate limit buckets from response headers
145
+ */
146
+ #updateRateLimits = (response) => {
147
+ const bucket = response.headers.get(`X-RateLimit-Bucket`);
148
+ const limit = response.headers.get(`X-RateLimit-Limit`);
149
+ const remaining = response.headers.get(`X-RateLimit-Remaining`);
150
+ const reset = response.headers.get(`X-RateLimit-Reset`);
151
+ const resetAfter = response.headers.get(`X-RateLimit-Reset-After`);
152
+ if (bucket && limit && remaining && reset && resetAfter) this.#buckets.set(bucket, {
153
+ limit: parseInt(limit, 10),
154
+ remaining: parseInt(remaining, 10),
155
+ reset: parseFloat(reset),
156
+ resetAfter: parseFloat(resetAfter)
157
+ });
158
+ };
159
+ /**
160
+ * Enforce global rate limit of 50 requests per second
161
+ */
162
+ #enforceGlobalRateLimit = async () => {
163
+ const now = Date.now();
164
+ this.#globalRequestTimestamps = this.#globalRequestTimestamps.filter((timestamp) => now - timestamp < this.#globalWindow);
165
+ if (this.#globalRequestTimestamps.length >= this.#globalLimit) {
166
+ const oldestTimestamp = this.#globalRequestTimestamps[0];
167
+ const waitTime = this.#globalWindow - (now - oldestTimestamp);
168
+ if (waitTime > 0) await sleep(waitTime);
169
+ }
170
+ };
171
+ /**
172
+ * Track invalid requests for Cloudflare ban prevention
173
+ */
174
+ #trackInvalidRequest = () => {
175
+ if (Date.now() - this.#invalidRequests.windowStart >= this.#invalidRequestWindow) this.#resetInvalidRequestCounter();
176
+ this.#invalidRequests.count++;
177
+ if (this.#invalidRequests.count >= this.#invalidRequestLimit) console.error(`Approaching invalid request limit! Bot may be temporarily banned.`);
178
+ };
179
+ /**
180
+ * Check if we're temporarily banned
181
+ */
182
+ #isTemporarilyBanned = () => {
183
+ if (Date.now() - this.#invalidRequests.windowStart < this.#invalidRequestWindow) return this.#invalidRequests.count >= this.#invalidRequestLimit;
184
+ return false;
185
+ };
186
+ /**
187
+ * Reset invalid request counter
188
+ */
189
+ #resetInvalidRequestCounter = () => {
190
+ this.#invalidRequests = {
191
+ count: 0,
192
+ windowStart: Date.now()
193
+ };
194
+ };
195
+ /**
196
+ * Get current queue size (useful for monitoring)
197
+ */
198
+ getQueueSize = () => this.#requestQueue.length;
199
+ };
200
+ const discord = new DiscordSession();
201
+ //#endregion
202
+ export { DiscordSession, discord, endpoint };
@@ -0,0 +1,15 @@
1
+ //#region src/requests/addParams.d.ts
2
+ type RequestParams = Partial<Record<string, number[] | string[] | boolean | number | string | null | undefined>>;
3
+ /**
4
+ * Append a params object to a URL.
5
+ *
6
+ * Array values are emitted as repeated query keys (`?id=1&id=2`), which is
7
+ * how Discord's HTTP API documents array query strings. Scalars are
8
+ * stringified once. Keys are converted from camelCase to snake_case to
9
+ * match Discord's convention.
10
+ *
11
+ * @__NO_SIDE_EFFECTS__
12
+ */
13
+ declare const addParams: (url: URL, params: RequestParams) => URL;
14
+ //#endregion
15
+ export { RequestParams, addParams };
@@ -0,0 +1,24 @@
1
+ import { isNonNullable } from "../utils/isNonNullable.mjs";
2
+ import { toSnakeCase } from "../utils/toSnakeCase.mjs";
3
+ //#region src/requests/addParams.ts
4
+ /**
5
+ * Append a params object to a URL.
6
+ *
7
+ * Array values are emitted as repeated query keys (`?id=1&id=2`), which is
8
+ * how Discord's HTTP API documents array query strings. Scalars are
9
+ * stringified once. Keys are converted from camelCase to snake_case to
10
+ * match Discord's convention.
11
+ *
12
+ * @__NO_SIDE_EFFECTS__
13
+ */
14
+ const addParams = (url, params) => {
15
+ for (const [key, value] of Object.entries(params)) {
16
+ if (!isNonNullable(value)) continue;
17
+ const snake = toSnakeCase(key);
18
+ if (Array.isArray(value)) for (const item of value) url.searchParams.append(snake, String(item));
19
+ else url.searchParams.set(snake, String(value));
20
+ }
21
+ return url;
22
+ };
23
+ //#endregion
24
+ export { addParams };
@@ -0,0 +1,7 @@
1
+ import { RequestParams } from "./addParams.mjs";
2
+
3
+ //#region src/requests/buildURL.d.ts
4
+ /** @__NO_SIDE_EFFECTS__ */
5
+ declare const buildURL: (resource: string, params?: RequestParams, base?: string) => URL;
6
+ //#endregion
7
+ export { buildURL };
@@ -0,0 +1,7 @@
1
+ import { endpoint } from "./DiscordSession.mjs";
2
+ import { addParams } from "./addParams.mjs";
3
+ //#region src/requests/buildURL.ts
4
+ /** @__NO_SIDE_EFFECTS__ */
5
+ const buildURL = (resource, params, base) => addParams(new URL(resource.replace(/^\//, ``), base ?? endpoint), params ?? {});
6
+ //#endregion
7
+ export { buildURL };
@@ -0,0 +1,7 @@
1
+ import { buildURL } from "./buildURL.mjs";
2
+
3
+ //#region src/requests/getAsset.d.ts
4
+ /** @__NO_SIDE_EFFECTS__ */
5
+ declare const getAsset: (resource: string, params?: Parameters<typeof buildURL>[1]) => string;
6
+ //#endregion
7
+ export { getAsset };
@@ -0,0 +1,6 @@
1
+ import { buildURL } from "./buildURL.mjs";
2
+ //#region src/requests/getAsset.ts
3
+ /** @__NO_SIDE_EFFECTS__ */
4
+ const getAsset = (resource, params) => buildURL(resource, params, `https://cdn.discordapp.com/`).href;
5
+ //#endregion
6
+ export { getAsset };
@@ -0,0 +1,9 @@
1
+ import { discord, endpoint } from "./DiscordSession.mjs";
2
+ import { buildURL } from "./buildURL.mjs";
3
+ import { getAsset } from "./getAsset.mjs";
4
+ import { Fetcher, FetcherCapabilities, RequestOptionsFor, get, patch, post, put, remove } from "./methods.mjs";
5
+ import { toProcedure } from "./toProcedure.mjs";
6
+ import { QueryFunction, toQuery } from "./toQuery.mjs";
7
+ import { toValidated } from "./toValidated.mjs";
8
+ import { verifyKey } from "./verifyKey.mjs";
9
+ export { Fetcher, FetcherCapabilities, QueryFunction, RequestOptionsFor, buildURL, discord, endpoint, get, getAsset, patch, post, put, remove, toProcedure, toQuery, toValidated, verifyKey };
@@ -0,0 +1,9 @@
1
+ import { discord, endpoint } from "./DiscordSession.mjs";
2
+ import { buildURL } from "./buildURL.mjs";
3
+ import { getAsset } from "./getAsset.mjs";
4
+ import { get, patch, post, put, remove } from "./methods.mjs";
5
+ import { toProcedure } from "./toProcedure.mjs";
6
+ import { toQuery } from "./toQuery.mjs";
7
+ import { toValidated } from "./toValidated.mjs";
8
+ import { verifyKey } from "./verifyKey.mjs";
9
+ export { buildURL, discord, endpoint, get, getAsset, patch, post, put, remove, toProcedure, toQuery, toValidated, verifyKey };
@@ -0,0 +1,63 @@
1
+ import { RequestBody, RequestOptions } from "./request.mjs";
2
+ import { RequestParams } from "./addParams.mjs";
3
+ import { GenericSchema, GenericSchemaAsync, InferOutput } from "valibot";
4
+
5
+ //#region src/requests/methods.d.ts
6
+ /**
7
+ * Capability flags advertised by a {@link Fetcher}. Each flag describes a
8
+ * property of the underlying Discord endpoint — not a per-call preference.
9
+ *
10
+ * The flags drive both the `options` argument the fetcher accepts and the
11
+ * headers/auth the request layer emits at runtime.
12
+ */
13
+ interface FetcherCapabilities {
14
+ /**
15
+ * `true` if the Discord endpoint MUST be called without an `Authorization`
16
+ * header. Forces callers to acknowledge the unauthenticated path via
17
+ * `{ anonymous: true }` and tells the request layer to skip the session token.
18
+ */
19
+ readonly anonymous?: boolean;
20
+ /**
21
+ * `true` if the Discord endpoint accepts the `X-Audit-Log-Reason` header.
22
+ * Allows callers to pass `{ reason: "…" }`; the header is URL-encoded and
23
+ * attached by the request layer.
24
+ */
25
+ readonly auditLogReason?: boolean;
26
+ }
27
+ /**
28
+ * The shape of the per-call `options` argument a {@link Fetcher} accepts,
29
+ * derived from the endpoint's {@link FetcherCapabilities}.
30
+ *
31
+ * - When `anonymous: true` is declared on the capability, the option is
32
+ * *required* on every call. When omitted, `anonymous` is forbidden.
33
+ * - When `auditLogReason: true` is declared, `reason` is permitted; otherwise
34
+ * it is forbidden.
35
+ */
36
+ type RequestOptionsFor<C extends FetcherCapabilities> = (C extends {
37
+ anonymous: true;
38
+ } ? {
39
+ anonymous: true;
40
+ } : {
41
+ anonymous?: never;
42
+ }) & (C extends {
43
+ auditLogReason: true;
44
+ } ? {
45
+ reason?: string;
46
+ } : {
47
+ reason?: never;
48
+ });
49
+ /**
50
+ * Decide whether the per-call `options` argument is required or optional
51
+ * based on whether any capability forces a value to be passed.
52
+ */
53
+ type RequiresOptions<C extends FetcherCapabilities> = C extends {
54
+ anonymous: true;
55
+ } ? true : false;
56
+ type Fetcher< /** A schema to validate the input arguments of a fetch call */S extends GenericSchema | GenericSchemaAsync | null = null, /** The return value expected from the fetch call */R = void, /** Endpoint capabilities; shapes the `options` argument */C extends FetcherCapabilities = {}> = S extends null ? RequiresOptions<C> extends true ? (options: RequestOptionsFor<C>) => Promise<R> : (options?: RequestOptionsFor<C>) => Promise<R> : RequiresOptions<C> extends true ? (config: InferOutput<NonNullable<S>>, options: RequestOptionsFor<C>) => Promise<R> : (config: InferOutput<NonNullable<S>>, options?: RequestOptionsFor<C>) => Promise<R>;
57
+ declare const get: <T>(url: string, params?: RequestParams, options?: RequestOptions) => Promise<T>;
58
+ declare const post: <T>(url: string, body?: RequestBody, options?: RequestOptions) => Promise<T>;
59
+ declare const put: <T>(url: string, body?: RequestBody, options?: RequestOptions) => Promise<T>;
60
+ declare const patch: <T>(url: string, body?: RequestBody, options?: RequestOptions) => Promise<T>;
61
+ declare const remove: <T = void>(url: string, options?: RequestOptions) => Promise<T>;
62
+ //#endregion
63
+ export { Fetcher, FetcherCapabilities, RequestOptionsFor, get, patch, post, put, remove };
@@ -0,0 +1,10 @@
1
+ import { buildURL } from "./buildURL.mjs";
2
+ import { request } from "./request.mjs";
3
+ //#region src/requests/methods.ts
4
+ const get = async (url, params, options) => request(buildURL(url, params), `GET`, void 0, options);
5
+ const post = async (url, body, options) => request(buildURL(url), `POST`, body, options);
6
+ const put = async (url, body, options) => request(buildURL(url), `PUT`, body, options);
7
+ const patch = async (url, body, options) => request(buildURL(url), `PATCH`, body, options);
8
+ const remove = async (url, options) => request(buildURL(url), `DELETE`, void 0, options);
9
+ //#endregion
10
+ export { get, patch, post, put, remove };
@@ -0,0 +1,27 @@
1
+ //#region src/requests/request.d.ts
2
+ type RequestBody = object | null | undefined;
3
+ /**
4
+ * Per-call request options forwarded by the typed method helpers
5
+ * (`get`/`post`/`put`/`patch`/`remove`) into the request layer.
6
+ *
7
+ * Endpoints surface these via the `Fetcher` type so the call site is
8
+ * type-checked against the endpoint's declared capabilities.
9
+ */
10
+ interface RequestOptions {
11
+ /**
12
+ * Skip the `Authorization` header for this request. Used by webhook
13
+ * and interaction endpoints whose path tokens act as authorization.
14
+ * When set, `discord.getSession()` is NOT consulted, so the session
15
+ * is allowed to be unset.
16
+ */
17
+ anonymous?: boolean;
18
+ /**
19
+ * Audit-log reason. Forwarded to Discord as the `X-Audit-Log-Reason`
20
+ * header. The value is URL-encoded by the request layer so non-ASCII
21
+ * characters survive transit.
22
+ */
23
+ reason?: string;
24
+ }
25
+ declare const request: <T>(resource: URL, method?: string, body?: RequestBody, options?: RequestOptions) => Promise<T>;
26
+ //#endregion
27
+ export { RequestBody, RequestOptions, request };
@@ -0,0 +1,29 @@
1
+ import { discord } from "./DiscordSession.mjs";
2
+ import { toCamelKeys } from "../utils/toCamelKeys.mjs";
3
+ import { toSnakeKeys } from "../utils/toSnakeKeys.mjs";
4
+ import { shouldSerializeAsMultipart, toMultipartBody } from "../validations/fileUpload.mjs";
5
+ //#region src/requests/request.ts
6
+ const request = async (resource, method = `GET`, body, options) => {
7
+ if (!options?.anonymous) discord.getSession();
8
+ /**
9
+ * Serialize the body. The `multipart()` schema wrapper stamps a
10
+ * sentinel on the parsed body when {@link FileUpload}s are present,
11
+ * which switches serialization from JSON to `multipart/form-data`.
12
+ */
13
+ const serializeBody = () => {
14
+ if (!body) return body;
15
+ try {
16
+ if (shouldSerializeAsMultipart(body)) return toMultipartBody(body, toSnakeKeys);
17
+ return JSON.stringify(toSnakeKeys(body));
18
+ } catch (cause) {
19
+ console.error(`Received malformed request body:\n\n`, { body });
20
+ throw new Error(`Failed to serialize request body!`, { cause });
21
+ }
22
+ };
23
+ const res = await discord.queueRequest(resource, method, serializeBody(), options);
24
+ if (!res.ok) throw new Error(`Request to resource '${resource.toString()}' failed:\n\n${res.statusText}`);
25
+ if (res.status === 204) return;
26
+ return toCamelKeys(await res.json());
27
+ };
28
+ //#endregion
29
+ export { request };
@@ -0,0 +1,40 @@
1
+ import { Fetcher } from "./methods.mjs";
2
+ import * as v from "valibot";
3
+ import { AnyProcedureBuilder, MutationProcedure, ProcedureType, QueryProcedure, SubscriptionProcedure } from "@trpc/server/unstable-core-do-not-import";
4
+
5
+ //#region src/requests/toProcedure.d.ts
6
+ type Result<T = void> = T extends v.GenericSchema | v.GenericSchemaAsync ? v.InferOutput<T> : T;
7
+ type Schema = v.GenericSchema | v.GenericSchemaAsync;
8
+ type ProvedureDef<I extends Schema | null = null, O extends Schema | null = null> = I extends Schema ? O extends Schema ? {
9
+ input: v.InferInput<I>;
10
+ output: v.InferOutput<O>;
11
+ meta: unknown;
12
+ } : {
13
+ input: v.InferInput<I>;
14
+ output: undefined;
15
+ meta: unknown;
16
+ } : O extends Schema ? {
17
+ input: undefined;
18
+ output: v.InferOutput<O>;
19
+ meta: unknown;
20
+ } : {
21
+ input: undefined;
22
+ output: undefined;
23
+ meta: unknown;
24
+ };
25
+ type BaseProcedure<T extends ProcedureType, I extends Schema | null = null, O extends Schema | null = null> = T extends `query` ? QueryProcedure<ProvedureDef<I, O>> : T extends `mutation` ? MutationProcedure<ProvedureDef<I, O>> : SubscriptionProcedure<ProvedureDef<I, O>>;
26
+ /**
27
+ * Given a {@link Fetcher | Fetcher} function and it's associated input and
28
+ * output Zod schemas, this produces a function which accepts a tRPC procedure
29
+ * builder of the given procedure type. This can then be used in a tRPC router
30
+ * to scaffold an API route to forward a request to Discord's API.
31
+ *
32
+ * Capability-free fetchers only — endpoints that require `{ anonymous: true }`
33
+ * or accept `{ reason: string }` cannot currently be wrapped via this helper,
34
+ * because tRPC has no natural channel for those per-call options.
35
+ *
36
+ * @__NO_SIDE_EFFECTS__
37
+ */
38
+ declare const toProcedure: <T extends ProcedureType, I extends Schema | null = null, O extends Schema | null = null>(type: T, fn: Fetcher<I extends Schema ? I : null, Result<O>>, input?: I, output?: O) => (procedure: AnyProcedureBuilder, errorHandler?: (error: unknown) => void) => BaseProcedure<T, I, O>;
39
+ //#endregion
40
+ export { toProcedure };
@@ -0,0 +1,27 @@
1
+ import { isNonNullable } from "../utils/isNonNullable.mjs";
2
+ //#region src/requests/toProcedure.ts
3
+ /**
4
+ * Given a {@link Fetcher | Fetcher} function and it's associated input and
5
+ * output Zod schemas, this produces a function which accepts a tRPC procedure
6
+ * builder of the given procedure type. This can then be used in a tRPC router
7
+ * to scaffold an API route to forward a request to Discord's API.
8
+ *
9
+ * Capability-free fetchers only — endpoints that require `{ anonymous: true }`
10
+ * or accept `{ reason: string }` cannot currently be wrapped via this helper,
11
+ * because tRPC has no natural channel for those per-call options.
12
+ *
13
+ * @__NO_SIDE_EFFECTS__
14
+ */
15
+ const toProcedure = (type, fn, input, output) => (procedure, errorHandler) => {
16
+ try {
17
+ if (isNonNullable(input) && isNonNullable(output)) return procedure.input(input).output(output)[type](async (opts) => fn(opts.input));
18
+ if (isNonNullable(input) && !isNonNullable(output)) return procedure.input(input)[type](async (opts) => fn(opts.input));
19
+ if (!isNonNullable(input) && isNonNullable(output)) return procedure.output(output)[type](async () => fn());
20
+ return procedure[type](async () => fn());
21
+ } catch (error) {
22
+ if (typeof errorHandler === `function`) errorHandler(error);
23
+ throw new Error(`Unhandled Procedure Error!`, { cause: error });
24
+ }
25
+ };
26
+ //#endregion
27
+ export { toProcedure };
@@ -0,0 +1,36 @@
1
+ import { Fetcher } from "./methods.mjs";
2
+ import { GenericSchema } from "valibot";
3
+
4
+ //#region src/requests/toQuery.d.ts
5
+ interface Register {}
6
+ type QueryKey = readonly unknown[];
7
+ type QueryMeta = Register extends {
8
+ queryMeta: infer TQueryMeta;
9
+ } ? TQueryMeta extends Record<string, unknown> ? TQueryMeta : Record<string, unknown> : Record<string, unknown>;
10
+ type FetchDirection = `backward` | `forward`;
11
+ type QueryFunctionContext<TQueryKey extends QueryKey = QueryKey, TPageParam = never> = [TPageParam] extends [never] ? {
12
+ queryKey: TQueryKey;
13
+ signal: AbortSignal;
14
+ meta: QueryMeta | undefined;
15
+ } : {
16
+ queryKey: TQueryKey;
17
+ signal: AbortSignal;
18
+ pageParam: TPageParam;
19
+ direction: FetchDirection;
20
+ meta: QueryMeta | undefined;
21
+ };
22
+ type QueryFunction<T = unknown, TQueryKey extends QueryKey = QueryKey, TPageParam = never> = (context: QueryFunctionContext<TQueryKey, TPageParam>) => Promise<T> | T;
23
+ /**
24
+ * Given a {@link Fetcher | Fetcher} function, transforms it into a curried function
25
+ * which can then be used with React-Query as a query function without
26
+ * the need for any additional boilerplate.
27
+ *
28
+ * Capability-free fetchers only — endpoints that require `{ anonymous: true }`
29
+ * or accept `{ reason: string }` cannot currently be wrapped via this helper,
30
+ * because react-query has no natural channel for those per-call options.
31
+ *
32
+ * @__NO_SIDE_EFFECTS__
33
+ */
34
+ declare const toQuery: <S extends GenericSchema | null, R, T extends Fetcher<S, R>>(fn: T) => Parameters<T>[`length`] extends 0 ? () => QueryFunction<Awaited<ReturnType<T>>> : (config: Parameters<T>[0]) => QueryFunction<Awaited<ReturnType<T>>>;
35
+ //#endregion
36
+ export { QueryFunction, toQuery };
@@ -0,0 +1,17 @@
1
+ //#region src/requests/toQuery.ts
2
+ /**
3
+ * Given a {@link Fetcher | Fetcher} function, transforms it into a curried function
4
+ * which can then be used with React-Query as a query function without
5
+ * the need for any additional boilerplate.
6
+ *
7
+ * Capability-free fetchers only — endpoints that require `{ anonymous: true }`
8
+ * or accept `{ reason: string }` cannot currently be wrapped via this helper,
9
+ * because react-query has no natural channel for those per-call options.
10
+ *
11
+ * @__NO_SIDE_EFFECTS__
12
+ */
13
+ const toQuery = (fn) => (...config) => async () => {
14
+ return fn(...config);
15
+ };
16
+ //#endregion
17
+ export { toQuery };
@@ -0,0 +1,16 @@
1
+ import { Fetcher, FetcherCapabilities } from "./methods.mjs";
2
+ import { GenericSchema, GenericSchemaAsync } from "valibot";
3
+
4
+ //#region src/requests/toValidated.d.ts
5
+ /**
6
+ * Given a {@link Fetcher | Fetcher} function and it's associated input
7
+ * and output Zod schemas, this returns a new validated {@link Fetcher | Fetcher} function which will validate it's input and result at runtime.
8
+ * This is useful in contexts where you want strong guarantees on runtime
9
+ * type-safety when dealing with raw user input in a framework agnostic
10
+ * environment.
11
+ *
12
+ * @__NO_SIDE_EFFECTS__
13
+ */
14
+ declare const toValidated: <S extends GenericSchema | GenericSchemaAsync | null = null, R = void, C extends FetcherCapabilities = {}>(fn: Fetcher<S, R, C>, input?: S | null, output?: GenericSchema<unknown, R> | GenericSchemaAsync<unknown, R>) => Fetcher<S, R, C>;
15
+ //#endregion
16
+ export { toValidated };
@@ -0,0 +1,25 @@
1
+ import { isOfKind, safeParseAsync, summarize } from "valibot";
2
+ //#region src/requests/toValidated.ts
3
+ /**
4
+ * Given a {@link Fetcher | Fetcher} function and it's associated input
5
+ * and output Zod schemas, this returns a new validated {@link Fetcher | Fetcher} function which will validate it's input and result at runtime.
6
+ * This is useful in contexts where you want strong guarantees on runtime
7
+ * type-safety when dealing with raw user input in a framework agnostic
8
+ * environment.
9
+ *
10
+ * @__NO_SIDE_EFFECTS__
11
+ */
12
+ const toValidated = (fn, input, output) => new Proxy(fn, { async apply(target, _, [config, options]) {
13
+ if (input && isOfKind(`schema`, input)) {
14
+ const { issues } = await safeParseAsync(input, config);
15
+ if (issues) throw new Error(`Failed to parse input schema: ${input.reference.name}\n\n${summarize(issues)}`);
16
+ }
17
+ const result = await target(config, options);
18
+ if (output && isOfKind(`schema`, output)) {
19
+ const { issues } = await safeParseAsync(output, result);
20
+ if (issues) throw new Error(`Failed to parse input schema: ${output.reference.name}\n\n${summarize(issues)}`);
21
+ }
22
+ return result;
23
+ } });
24
+ //#endregion
25
+ export { toValidated };
@@ -1,3 +1,4 @@
1
+ //#region src/requests/verifyKey.d.ts
1
2
  /**
2
3
  * Validates a payload from Discord against its signature and key.
3
4
  *
@@ -7,4 +8,6 @@
7
8
  * @param clientPublicKey - The public key from the Discord developer dashboard
8
9
  * @returns Whether or not validation was successful
9
10
  */
10
- export declare function verifyKey(rawBody: Uint8Array | ArrayBuffer | Buffer | string, signature: string, timestamp: string, clientPublicKey: string | CryptoKey): Promise<boolean>;
11
+ declare function verifyKey(rawBody: Uint8Array | ArrayBuffer | Buffer | string, signature: string, timestamp: string, clientPublicKey: string | CryptoKey): Promise<boolean>;
12
+ //#endregion
13
+ export { verifyKey };