Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Commit d53973f

Browse files
authored
feat(Rest): caching support (#105)
* feat(Rest): experimental caching support * feat(Rest): expose the new options on #get * fix(Rest): better cache timeout behavior
1 parent b1ca9cd commit d53973f

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

libs/rest/src/Fetch.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const discordFetch = async <D, Q>(options: DiscordFetchOptions<D, Q>) =>
7979
}
8080

8181
return fetch(url, {
82-
method: method,
82+
method,
8383
headers,
8484
body: body!,
8585
signal: controller.signal,

libs/rest/src/struct/Rest.ts

+54-3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export interface RestOptions {
3030
* @default true
3131
*/
3232
retryAfterRatelimit?: boolean;
33+
/**
34+
* Overwrites the default for `{@link RequestOptions.cacheTime}`
35+
*/
36+
cacheTime?: number;
3337
}
3438

3539
export interface Rest {
@@ -119,13 +123,31 @@ export interface RequestOptions<D, Q> {
119123
* Wether or not the library should internally set a timeout for the I/O call
120124
*/
121125
implicitAbortBehavior?: boolean;
126+
/**
127+
* Wether or not the library should cache the result of this call (ignored for non-GET requests)
128+
*/
129+
cache?: boolean;
130+
/**
131+
* If `{@link RequestOptions.cache}` is set to `true`, how long should the cache live for?
132+
* @default 10000
133+
*/
134+
cacheTime?: number;
122135
}
123136

124137
/**
125138
* Base REST class used for making requests
126139
* @noInheritDoc
127140
*/
128141
export class Rest extends EventEmitter {
142+
/**
143+
* @internal
144+
*/
145+
private readonly cache = new Map<string, any>();
146+
/**
147+
* @internal
148+
*/
149+
private readonly cacheTimeouts = new Map<string, NodeJS.Timeout>();
150+
129151
/**
130152
* Current active rate limiting Buckets
131153
*/
@@ -135,6 +157,7 @@ export class Rest extends EventEmitter {
135157
public readonly abortAfter: number;
136158
public readonly mutex: Mutex;
137159
public readonly retryAfterRatelimit: boolean;
160+
public readonly cacheTime: number;
138161

139162
/**
140163
* @param auth Your bot's Discord token
@@ -149,13 +172,15 @@ export class Rest extends EventEmitter {
149172
retries = 3,
150173
abortAfter = 15e3,
151174
mutex = new MemoryMutex(),
152-
retryAfterRatelimit = true
175+
retryAfterRatelimit = true,
176+
cacheTime = 10000,
153177
} = options;
154178

155179
this.retries = retries;
156180
this.abortAfter = abortAfter;
157181
this.mutex = mutex;
158182
this.retryAfterRatelimit = retryAfterRatelimit;
183+
this.cacheTime = cacheTime;
159184
}
160185

161186
/**
@@ -183,11 +208,36 @@ export class Rest extends EventEmitter {
183208
options.headers.set('X-Audit-Log-Reason', encodeURIComponent(options.reason));
184209
}
185210

211+
options.cacheTime ??= this.cacheTime;
212+
186213
let isRetryAfterRatelimit = false;
187214

215+
const isGet = options.method.toLowerCase() === 'get';
216+
const shouldCache = options.cache && isGet;
217+
188218
for (let retries = 0; retries <= this.retries; retries++) {
189219
try {
190-
return await bucket.make<T, D, Q>({ ...options, isRetryAfterRatelimit } as DiscordFetchOptions<D, Q>);
220+
if (shouldCache && this.cache.has(options.path)) {
221+
return this.cache.get(options.path);
222+
}
223+
224+
const data = await bucket.make<T, D, Q>({ ...options, isRetryAfterRatelimit } as DiscordFetchOptions<D, Q>);
225+
226+
if (shouldCache || (isGet && this.cache.has(options.path))) {
227+
this.cache.set(options.path, data);
228+
229+
if (this.cacheTimeouts.has(options.path)) {
230+
const timeout = this.cacheTimeouts.get(options.path)!;
231+
timeout.refresh();
232+
} else {
233+
this.cacheTimeouts.set(options.path, setTimeout(() => {
234+
this.cache.delete(options.path);
235+
this.cacheTimeouts.delete(options.path);
236+
}, options.cacheTime));
237+
}
238+
}
239+
240+
return data;
191241
} catch (e: any) {
192242
const isRatelimit = e instanceof CordisRestError && e.code === 'rateLimited';
193243
isRetryAfterRatelimit = isRatelimit;
@@ -215,7 +265,8 @@ export class Rest extends EventEmitter {
215265
* @param options Other options for the request
216266
*/
217267
/* istanbul ignore next */
218-
public get<T, Q = StringRecord>(path: string, options: { query?: Q } = {}): Promise<T> {
268+
269+
public get<T, Q = StringRecord>(path: string, options: { query?: Q; cache?: boolean; cacheTime?: number } = {}): Promise<T> {
219270
return this.make<T, never, Q>({ path, method: 'get', ...options });
220271
}
221272

0 commit comments

Comments
 (0)